16.13. Examples

This section includes three examples of node kits. The first example uses two SoShapeKits. The second example, with detailed comments, uses an SoWrapperKit( C++ | Java | .NET ) and an SoSceneKit( C++ | Java | .NET ) that contains an SoLightKit( C++ | Java | .NET ) and an SoCameraKit( C++ | Java | .NET ). The third example uses various node kits as well as an SoEventCallback( C++ | Java | .NET ) with an associated function for animating the balance scale.

Example 16.1, “ Simple Use of Node Kits uses node kits to create two 3D words and shows use of node kit methods to access the fields of the “material” and “transform” parts of the shape kits. It uses a calculator engine and an elapsed time engine to make the words change color and fly around the screen. Figure 16.13, “ Using an SoShapeKit with Engines shows two images from this example.

Example 16.1.  Simple Use of Node Kits


C++
#include <Inventor/engines/SoCalculator.h>
#include <Inventor/engines/SoElapsedTime.h>
#include <Inventor/nodekits/SoShapeKit.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoText3.h>
#include <Inventor/nodes/SoTransform.h>

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>

main(int , char **argv)
{
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   SoSeparator *root = new SoSeparator;
   root->ref();

   // Create shape kits with the words "HAPPY" and "NICE"
   SoShapeKit *happyKit = new SoShapeKit;
   root->addChild(happyKit);
   happyKit->setPart("shape", new SoText3);
   happyKit->set("shape { parts ALL string \"HAPPY\"}");
   happyKit->set("font { size 2}");

   SoShapeKit *niceKit = new SoShapeKit;
   root->addChild(niceKit);
   niceKit->setPart("shape", new SoText3);
   niceKit->set("shape { parts ALL string \"NICE\"}");
   niceKit->set("font { size 2}");

   // Create the Elapsed Time engine
   SoElapsedTime *myTimer = new SoElapsedTime;
   myTimer->ref();
  

.NET
using OIV.Inventor.Nodes;
using OIV.Inventor.Win;
using OIV.Inventor.Win.Viewers;
using OIV.Inventor.Engines;
using OIV.Inventor.Nodekits;

namespace _14_1_FrolickingWords
{
    public partial class MainForm : Form
    {
        SoElapsedTime myTimer;
        SoCalculator happyCalc;
        SoCalculator niceCalc;
        SoWinExaminerViewer myViewer;

        public MainForm()
        {
            InitializeComponent();
            CreateSample();
        }

        public void CreateSample()
        {
            SoSeparator root = new SoSeparator();

            // Create shape kits with the words "HAPPY" and "NICE"
            SoShapeKit happyKit = new SoShapeKit();
            root.AddChild(happyKit);
            happyKit.SetPart("shape", new SoText3());
            happyKit.Set("shape { parts ALL string \"HAPPY\"}");
            happyKit.Set("font { size 2}");

            SoShapeKit niceKit = new SoShapeKit();
            root.AddChild(niceKit);
            niceKit.SetPart("shape", new SoText3());
            niceKit.Set("shape { parts ALL string \"NICE\"}");
            niceKit.Set("font { size 2}");

            // Create the Elapsed Time engine
            myTimer = new SoElapsedTime();

Java
import tools.*;

import com.openinventor.inventor.awt.*;
import com.openinventor.inventor.nodes.*;
import com.openinventor.inventor.engines.*;
import com.openinventor.inventor.nodekits.*;

import java.awt.*;

public class Main extends DemoInventor
{

  public static void main(String[] args)
  {
    Main applet = new Main();
    DemoInventor.isAnApplet = false;
    applet.start();
    demoMain(applet, "Frolicking Words");
  }

  public void start()
  {
    super.start();

    // Create shape kits with the words "HAPPY" and "NICE"
    SoShapeKit happyKit = new SoShapeKit();
    happyKit.setPart("shape", new SoText3());
    happyKit.set("shape { parts ALL string \"HAPPY\"}");
    happyKit.set("font {size 2}");

    SoShapeKit niceKit = new SoShapeKit();
    niceKit.setPart("shape", new SoText3());
    niceKit.set("shape { parts ALL string \"NICE\"}");
    niceKit.set("font {size 2}");

    // Create the Elapsed Time engine
    SoElapsedTime myTimer = new SoElapsedTime();



C++
   // Create two calculators - one for HAPPY, one for NICE.
   SoCalculator *happyCalc = new SoCalculator;
   happyCalc->ref();
   happyCalc->a.connectFrom(&myTimer->timeOut);
   happyCalc->expression = "ta=cos(2*a); tb=sin(2*a);\
      oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);         \
      oB = vec3f(fabs(ta)+.1,fabs(.5*fabs(tb))+.1,1);\
      oC = vec3f(fabs(ta),fabs(tb),.5)";

   // The second calculator uses different arguments to
   // sin() and cos(), so it moves out of phase.
   SoCalculator *niceCalc = new SoCalculator;
   niceCalc->ref();
   niceCalc->a.connectFrom(&myTimer->timeOut);
   niceCalc->expression = "ta=cos(2*a+2); tb=sin(2*a+2);\
      oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);            \
      oB = vec3f(fabs(ta)+.1,fabs(.5*fabs(tb))+.1,1);   \
      oC = vec3f(fabs(ta),fabs(tb),.5)";

   // Connect the transforms from the calculators...
   SoTransform *happyXf
      = (SoTransform *) happyKit->getPart("transform",TRUE);
   happyXf->translation.connectFrom(&happyCalc->oA);
   happyXf->scaleFactor.connectFrom(&happyCalc->oB);
   SoTransform *niceXf
      = (SoTransform *) niceKit->getPart("transform",TRUE);
   niceXf->translation.connectFrom(&niceCalc->oA);
   niceXf->scaleFactor.connectFrom(&niceCalc->oB);

   // Connect the materials from the calculators...
   SoMaterial *happyMtl
      = (SoMaterial *) happyKit->getPart("material",TRUE);
   happyMtl->diffuseColor.connectFrom(&happyCalc->oC);
   SoMaterial *niceMtl
      = (SoMaterial *) niceKit->getPart("material",TRUE);
   niceMtl->diffuseColor.connectFrom(&niceCalc->oC);

   SoXtExaminerViewer *myViewer = new
            SoXtExaminerViewer(myWindow);
   myViewer->setSceneGraph(root);
   myViewer->setTitle("Frolicking Words");
   myViewer->viewAll();
   myViewer->show();

   SoXt::show(myWindow);
   SoXt::mainLoop();
}
  

.NET
            // Create two calculator - one for HAPPY, one for NICE.
            happyCalc = new SoCalculator();
            happyCalc.a.ConnectFrom(myTimer.timeOut);
            happyCalc.expression.SetValue("ta=cos(2*a); tb=sin(2*a);
                                           oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);
                                           oB = vec3f(fabs(ta)+.1,fabs(.5*fabs(tb))+.1,1);
                                           oC = vec3f(fabs(ta),fabs(tb),.5)");

            // The second calculator uses different arguments to
            // sin() and cos(), so it moves out of phase.
            niceCalc = new SoCalculator();
            niceCalc.a.ConnectFrom(myTimer.timeOut);
            niceCalc.expression.SetValue("ta=cos(2*a+2); tb=sin(2*a+2);
                                          oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);
                                          oB = vec3f(fabs(ta)+.1,fabs(.5*fabs(tb))+.1,1);
                                          oC = vec3f(fabs(ta),fabs(tb),.5)");

            // Connect the transforms from the calculators...
            SoTransform happyXf = (SoTransform)happyKit.GetPart("transform", true);
            happyXf.translation.ConnectFrom(happyCalc.oA);
            happyXf.scaleFactor.ConnectFrom(happyCalc.oB);

            SoTransform niceXf = (SoTransform)niceKit.GetPart("transform", true);
            niceXf.translation.ConnectFrom(niceCalc.oA);
            niceXf.scaleFactor.ConnectFrom(niceCalc.oB);

            // Connect the materials from the calculators...
            SoMaterial happyMtl = (SoMaterial)happyKit.GetPart("material", true);
            happyMtl.diffuseColor.ConnectFrom(happyCalc.oC);

            SoMaterial niceMtl = (SoMaterial)niceKit.GetPart("material", true);
            niceMtl.diffuseColor.ConnectFrom(niceCalc.oC);

            myViewer = new SoWinExaminerViewer(this, "", true,
                   SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER);
            myViewer.SetSceneGraph(root);
            myViewer.SetTitle("Frolicking Words");
            myViewer.ViewAll();
        }
    }
}

Java

    // Create two calculator - one for HAPPY, one for NICE.
    SoCalculator happyCalc = new SoCalculator();
    happyCalc.a.connectFrom(myTimer.timeOut);
    happyCalc.expression.setValue("ta = cos(2*a);" + "tb = sin(2*a);" +
                                  "oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);" +
                                  "oB = vec3f(fabs(ta)+.1,fabs(0.5*fabs(tb))+0.1,1);" +
                                  "oC = vec3f(fabs(ta),fabs(tb),0.5)");

    // The second calculator uses different arguments to
    // sin() and cos(), so it moves out of phase.
    SoCalculator niceCalc = new SoCalculator();
    niceCalc.a.connectFrom(myTimer.timeOut);
    niceCalc.expression.setValue("ta = cos(2*a+2);" + "tb = sin(2*a+2);" +
                                 "oA = vec3f(3*pow(ta,3),3*pow(tb,3),1);" +
                                 "oB = vec3f(fabs(ta)+.1,fabs(.5*fabs(tb))+.1,1);" +
                                 "oC = vec3f(fabs(ta),fabs(tb),.5)");

    // Connect the transforms from the calculators...
    SoTransform happyXf = (SoTransform) happyKit.getPart("transform", true);
    happyXf.translation.connectFrom(happyCalc.oA);
    happyXf.scaleFactor.connectFrom(happyCalc.oB);
    SoTransform niceXf = (SoTransform) niceKit.getPart("transform", true);
    niceXf.translation.connectFrom(niceCalc.oA);
    niceXf.scaleFactor.connectFrom(niceCalc.oB);

    // Connect the materials from the calculators...
    SoMaterial happyMtl = (SoMaterial) happyKit.getPart("material", true);
    happyMtl.diffuseColor.connectFrom(happyCalc.oC);
    SoMaterial niceMtl = (SoMaterial) niceKit.getPart("material", true);
    niceMtl.diffuseColor.connectFrom(niceCalc.oC);

    SoSeparator root = new SoSeparator();
    { // Assemble scene graph
      root.addChild(happyKit);
      root.addChild(niceKit);
    }

    SwSimpleViewer myViewer = new SwSimpleViewer();
    myViewer.setSceneGraph(root);
    myViewer.viewAll();

    setLayout(new BorderLayout());
    add(myViewer, BorderLayout.CENTER);
  }
}

Example 16.2, “ Using Node Kits and Editors reads in a desk from a file and puts it in the “contents” part of an SoWrapperKit. It adds a directional light editor to the light in the scene and a material editor to the desk, as shown in Figure 16.14, “ Using an SoSceneKit with Directional Light and Material Editors. The scene is organized using an SoSceneKit, which contains lists for grouping lights (“lightList”), cameras (“cameraList”), and objects (“childList”) in a scene.




C++
void main(int , char **argv)
{
   // Initialize Inventor and Xt
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   // SCENE!
   SoSceneKit *myScene = new SoSceneKit;
   myScene->ref();

   // LIGHTS! Add an SoLightKit to the "lightList." The 
   // SoLightKit creates an SoDirectionalLight by default.
   myScene->setPart("lightList[0]", new SoLightKit);

   // CAMERA!! Add an SoCameraKit to the "cameraList." The 
   // SoCameraKit creates an SoPerspectiveCamera by default.
   myScene->setPart("cameraList[0]", new SoCameraKit);
   myScene->setCameraNumber(0);

   // Read an object from file. 
   SoInput myInput;
   if (!myInput.openFile("desk.iv")) 
      return (1);
   SoSeparator *fileContents = SoDB::readAll(&myInput);
   if (fileContents == NULL) return (1);
   // OBJECT!! Create an SoWrapperKit and set its contents to
   // be what you read from file.
   SoWrapperKit *myDesk = new SoWrapperKit();
   myDesk->setPart("contents", fileContents);
   myScene->setPart("childList[0]", myDesk);
   // Give the desk a good starting color
   myDesk->set("material { diffuseColor .8 .3 .1 }");

   // MATERIAL EDITOR!!  Attach it to myDesk's material node.
   // Use the SO_GET_PART macro to get this part from myDesk.
   SoXtMaterialEditor *mtlEditor = new SoXtMaterialEditor();
   SoMaterial *mtl = SO_GET_PART(myDesk,"material",SoMaterial);
   mtlEditor->attach(mtl);
   mtlEditor->setTitle("Material of Desk");
   mtlEditor->show();

   // DIRECTIONAL LIGHT EDITOR!! Attach it to the 
   // SoDirectionalLight node within the SoLightKit we made.
   SoXtDirectionalLightEditor *ltEditor = 
                 new SoXtDirectionalLightEditor();
   SoPath *ltPath = myScene->createPathToPart(
      "lightList[0].light", TRUE);
   ltEditor->attach(ltPath);
   ltEditor->setTitle("Lighting of Desk");
   ltEditor->show();

   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);

   // Set up Camera with ViewAll...
   // -- use the SO_GET_PART macro to get the camera node.
   // -- viewall is a method on the 'camera' part of 
   //    the cameraKit, not on the cameraKit itself.  So the part
   //    we ask for is not 'cameraList[0]' (which is of type 
   //    SoPerspectiveCameraKit), but 
   //    'cameraList[0].camera' (which is of type 
   //    SoPerspectiveCamera).
   SoPerspectiveCamera *myCamera = SO_GET_PART(myScene,
      "cameraList[0].camera", SoPerspectiveCamera);
   SbViewportRegion myRegion(myRenderArea->getSize());
   myCamera->viewAll(myScene, myRegion);
   myRenderArea->setSceneGraph(myScene);
   myRenderArea->setTitle("Main Window: Desk In A Scene Kit");
   myRenderArea->show();

   SoXt::show(myWindow);
   SoXt::mainLoop();
}
  

.NET
public void CreateSample()
{
    SoSceneKit myScene = new SoSceneKit();

    // Add an SoLightKit to the "lightList." The 
    // SoLightKit creates an SoDirectionalLight by default.
    myScene.SetPart("lightList[0]", new SoLightKit());

    // Add an SoCameraKit to the "cameraList." The 
    // SoCameraKit creates an SoPerspectiveCamera by default.
    myScene.SetPart("cameraList[0]", new SoCameraKit());
    myScene.SetCameraNumber(0);

    // Read an object from file. 
    SoInput myInput = new SoInput();
    myInput.OpenFile("../../../../../data/desk.iv");

    SoSeparator fileContents = SoDB.ReadAll(myInput);

    // Create an SoWrapperKit and set its contents to
    // be what you read from file.
    SoWrapperKit myDesk = new SoWrapperKit();
    myDesk.SetPart("contents", fileContents);
    myScene.SetPart("childList[0]", myDesk);

    // Give the desk a good starting color
    myDesk.Set("appearance.material { diffuseColor .8 .3 .1 }");

    // Attach it to myDesk's material node.
    // Use the SO_GET_PART macro to get this part from myDesk.
    mtlEditor = new SoWinMaterialEditor(this, "", false);
    SoMaterial mtl = (SoMaterial)myDesk.GetPart("appearance.material", true);
    mtlEditor.SetTitle("Material of Desk");
    mtlEditor.Show();
    mtlEditor.Attach(mtl);

    // Attach it to the 
    // SoDirectionalLight node within the SoLightKit we made.
    ltEditor = new SoWinDirectionalLightEditor(this, "", false);
    SoPath ltPath = myScene.CreatePathToPart("lightList[0].light", true);
    ltEditor.SetTitle("Lighting of Desk");
    ltEditor.Show();
    ltEditor.Attach(ltPath);

    myRenderArea = new SoWinRenderArea(this, "", true, true, true);
    // Set up Camera with ViewAll...
    // -- use the SO_GET_PART macro to get the camera node.
    // -- viewall is a method on the 'camera' part of 
    //    the cameraKit, not on the cameraKit itself.  So the part
    //    we ask for is not 'cameraList[0]' (which is of type 
    //    SoPerspectiveCameraKit), but 
    //    'cameraList[0].camera' (which is of type 
    //    SoPerspectiveCamera).
    SoPerspectiveCamera myCamera = (SoPerspectiveCamera)myScene.GetPart(
                                           "cameraList[0].camera", true);
    SbViewportRegion myRegion = new SbViewportRegion(myRenderArea.GetSize());
    myCamera.ViewAll(myScene, myRegion);

    myRenderArea.SetSceneGraph(myScene);
    myRenderArea.SetTitle("Main Window: Desk In A Scene Kit");
}

Java
          
        

Example 16.3, “ Using Node Kits to Create a Motion Hierarchy creates a balance scale using node kits and their motion hierarchies. Figure 16.15, “ A Balance Scale Created with Node Kits shows the balance scale created by this example.


Example 16.3.  Using Node Kits to Create a Motion Hierarchy


C++
// This example illustrates the creation of motion hierarchies
// using nodekits by creating a model of a balance-style scale.

// It adds an SoEventCallback to the "callback" list in the 
// nodekit called 'support.'
// The callback will have the following response to events:
// Pressing right arrow key == lower the right pan
// Pressing left arrow key  == lower the left pan
// The pans are lowered by animating three rotations in the 
// motion hierarchy.
// Use an SoText2Kit to print instructions to the user as part
// of the scene.

#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/nodekits/SoCameraKit.h>
#include <Inventor/nodekits/SoLightKit.h>
#include <Inventor/nodekits/SoSceneKit.h>
#include <Inventor/nodekits/SoShapeKit.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoCylinder.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/nodes/SoText2.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>

// Callback Function for Animating the Balance Scale.
// --used to make the balance tip back and forth
// --Note: this routine is only called in response to KeyPress
//   events since the call 'setEventInterest(KeyPressMask)' is
//   made on the SoEventCallback node that uses it.
// --The routine checks if the key pressed was left arrow (which
//   is XK_Left in X-windows talk), or right arrow (which is
//   XK_Right)
// --The balance is made to tip by rotating the beam part of the
//   scale (to tip it) and then compensating (making the strings
//   vertical again) by rotating the string parts in the opposite
//   direction.
void
tipTheBalance(
   void *userData, // The nodekit representing 'support', the
                   // fulcrum of the balance. Passed in during
                   // main routine, below. 
   SoEventCallback *eventCB)
{
   const SoEvent *ev = eventCB->getEvent();
   
   // Which Key was pressed?
   // If Right or Left Arrow key, then continue...
   if (SO_KEY_PRESS_EVENT(ev, RIGHT_ARROW) || 
        SO_KEY_PRESS_EVENT(ev, LEFT_ARROW)) {
      SoShapeKit  *support, *beam1, *string1, *string2;
      SbRotation  startRot, beamIncrement, stringIncrement;

      // Get the different nodekits from the userData.
      support = (SoShapeKit *) userData;

    // These three parts are extracted based on knowledge of
    // the motion hierarchy (see the diagram in the main
    // routine.
      beam1   = (SoShapeKit *)support->getPart("childList[0]",TRUE);
      string1 = (SoShapeKit *)  beam1->getPart("childList[0]",TRUE);
      string2 = (SoShapeKit *)  beam1->getPart("childList[1]",TRUE);

      //Set angular increments to be .1 Radians about the Z-Axis
      //The strings rotate opposite the beam, and the two types
      //of key press produce opposite effects.
      if (SO_KEY_PRESS_EVENT(ev, RIGHT_ARROW)) {
         beamIncrement.setValue(SbVec3f(0, 0, 1), -.1);
         stringIncrement.setValue(SbVec3f(0, 0, 1), .1);
      } 
      else {
         beamIncrement.setValue(SbVec3f(0, 0, 1), .1);
         stringIncrement.setValue(SbVec3f(0, 0, 1), -.1);
      }

      // Use SO_GET_PART to find the transform for each of the 
      // rotating parts and modify their rotations.

      SoTransform *xf;
      xf = SO_GET_PART(beam1, "transform", SoTransform);
      startRot = xf->rotation.getValue();
      xf->rotation.setValue(startRot *  beamIncrement);

      xf = SO_GET_PART(string1, "transform", SoTransform);
      startRot = xf->rotation.getValue();
      xf->rotation.setValue(startRot *  stringIncrement);

      xf = SO_GET_PART(string2, "transform", SoTransform);
      startRot = xf->rotation.getValue();
      xf->rotation.setValue(startRot *  stringIncrement);

      eventCB->setHandled();
   }
}

main(int , char **argv)
{
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   SoSceneKit *myScene = new SoSceneKit;
   myScene->ref();

   myScene->setPart("lightList[0]", new SoLightKit);
   myScene->setPart("cameraList[0]", new SoCameraKit);
   myScene->setCameraNumber(0);

   // Create the Balance Scale -- put each part in the 
   // childList of its parent, to build up this hierarchy:
   //
   //                    myScene
   //                       |
   //                     support
   //                       |
   //                     beam
   //                       |
   //                   --------
   //                   |       |
   //                string1  string2
   //                   |       |
   //                tray1     tray2

   SoShapeKit *support = new SoShapeKit();
   support->setPart("shape", new SoCone);
   support->set("shape { height 3 bottomRadius .3 }");
   myScene->setPart("childList[0]", support);

   SoShapeKit *beam = new SoShapeKit();
   beam->setPart("shape", new SoCube);
   beam->set("shape { width 3 height .2 depth .2 }");
   beam->set("transform { translation 0 1.5 0 } ");
   support->setPart("childList[0]", beam);

   SoShapeKit *string1 = new SoShapeKit;
   string1->setPart("shape", new SoCylinder);
   string1->set("shape { radius .05 height 2}");
   string1->set("transform { translation -1.5 -1 0 }");
   string1->set("transform { center 0 1 0 }");
   beam->setPart("childList[0]", string1);

   SoShapeKit *string2 = new SoShapeKit;
   string2->setPart("shape", new SoCylinder);
   string2->set("shape { radius .05 height 2}");
   string2->set("transform { translation 1.5 -1 0 } ");
   string2->set("transform { center 0 1 0 } ");
   beam->setPart("childList[1]", string2);

   SoShapeKit *tray1 = new SoShapeKit;
   tray1->setPart("shape", new SoCylinder);
   tray1->set("shape { radius .75 height .1 }");
   tray1->set("transform { translation 0 -1 0 } ");
   string1->setPart("childList[0]", tray1);

   SoShapeKit *tray2 = new SoShapeKit;
   tray2->setPart("shape", new SoCylinder);
   tray2->set("shape { radius .75 height .1 }");
   tray2->set("transform { translation 0 -1 0 } ");
   string2->setPart("childList[0]", tray2);

   // Add EventCallback so Balance Responds to Events
   SoEventCallback *myCallbackNode = new SoEventCallback;
   myCallbackNode->addEventCallback(
        SoKeyboardEvent::getClassTypeId(), 
            	tipTheBalance, support); 
   support->setPart("callbackList[0]", myCallbackNode);

   // Add Instructions as Text in the Scene...
   SoShapeKit *myText = new SoShapeKit;
   myText->setPart("shape", new SoText2);
   myText->set("shape { string \"Press Left or Right Arrow Key\" }");
   myText->set("shape { justification CENTER }");
   myText->set("font { name \"Helvetica-Bold\" }");
   myText->set("font { size 16.0 }");
   myText->set("transform { translation 0 -2 0 }");
   myScene->setPart("childList[1]", myText);

   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);

   // Get camera from scene and tell it to viewAll...
   SbViewportRegion myRegion(myRenderArea->getSize());
   SoPerspectiveCamera *myCamera = SO_GET_PART(myScene,
      "cameraList[0].camera", SoPerspectiveCamera);
   myCamera->viewAll(myScene, myRegion);

   myRenderArea->setSceneGraph(myScene);
   myRenderArea->setTitle("Balance Scale Made of Nodekits");
   myRenderArea->show();

   SoXt::show(myWindow);
   SoXt::mainLoop();
}
    

.NET
using OIV.Inventor.Nodes;
using OIV.Inventor.Win;
using OIV.Inventor.Actions;
using OIV.Inventor.Nodekits;
using OIV.Inventor.Events;
using OIV.Inventor;

namespace _14_3_Balance
{
    public partial class MainForm : Form
    {
        SoWinRenderArea myRenderArea;

        public MainForm()
        {
            InitializeComponent();
            CreateSample();
        }

        // Callback Function for Animating the Balance Scale.
        // -- used to make the balance tip back and forth
        // -- Note: this routine is only called in response to KeyPress
        //    events since the call 'SetEventInterest(KeyPressMask)' is
        //    made on the SoEventCallback node that uses it.
        // -- The routine checks if the key pressed was left arrow, or right arrow
        // -- The balance is made to tip by rotating the beam part of the
        //    scale (to tip it) and then compensating (making the strings
        //    vertical again) by rotating the string parts in the opposite
        //    direction.
        void TipTheBalance(SoEventCallback eventCB, Object data)
        {
            SoKeyboardEvent evt = (SoKeyboardEvent)eventCB.GetEvent();
            if (!SoKeyboardEvent.IsKeyPressEvent(evt, evt.GetKey())) return;

            // Which Key was pressed?
            // If Right or Left Arrow key, then continue...
            if ((evt.GetKey() == SoKeyboardEvent.Keys.RIGHT_ARROW) ||
            (evt.GetKey() == SoKeyboardEvent.Keys.LEFT_ARROW))
            {
                SoShapeKit support, beam1, string1, string2;
                SbRotation startRot, beamIncrement, stringIncrement;

                // Get the different nodekits from the userData.
                support = (SoShapeKit)data;

                // These three parts are extracted based on knowledge of the
                // motion hierarchy (see the diagram in the main routine.
                beam1 = (SoShapeKit)support.GetPart("childList[0]", true);
                string1 = (SoShapeKit)beam1.GetPart("childList[0]", true);
                string2 = (SoShapeKit)beam1.GetPart("childList[1]", true);

                // Set angular increments to be .1 Radians about the Z-Axis
                // The strings rotate opposite the beam, and the two types
                // of key press produce opposite effects.
                if (evt.GetKey() == SoKeyboardEvent.Keys.RIGHT_ARROW)
                {
                    beamIncrement.SetValue(new SbVec3f(0.0f, 0.0f, 1.0f), -0.1f);
                    stringIncrement.SetValue(new SbVec3f(0.0f, 0.0f, 1.0f), 0.1f);
                }
                else
                {
                    beamIncrement.SetValue(new SbVec3f(0.0f, 0.0f, 1.0f), 0.1f);
                    stringIncrement.SetValue(new SbVec3f(0.0f, 0.0f, 1.0f), -0.1f);
                }

                // Use SO_GET_PART to find the transform for each of the 
                // rotating parts and modify their rotations.

                SoTransform xf;
                xf = (SoTransform)beam1.GetPart("transform", true);
                startRot = xf.rotation.GetValue();
                xf.rotation.SetValue(startRot * beamIncrement);

                xf = (SoTransform)string1.GetPart("transform", true);
                startRot = xf.rotation.GetValue();
                xf.rotation.SetValue(startRot * stringIncrement);

                xf = (SoTransform)string2.GetPart("transform", true);
                startRot = xf.rotation.GetValue();
                xf.rotation.SetValue(startRot * stringIncrement);

                eventCB.SetHandled();
            }
        }

        public void CreateSample()
        {
            SoSceneKit myScene = new SoSceneKit();

            myScene.SetPart("lightList[0]", new SoLightKit());
            myScene.SetPart("cameraList[0]", new SoCameraKit());
            myScene.SetCameraNumber(0);

            // Create the Balance Scale -- put each part in the 
            // childList of its parent, to build up this hierarchy:
            //
            //                    myScene
            //                       |
            //                     support
            //                       |
            //                     beam
            //                       |
            //                   --------
            //                   |       |
            //                string1  string2
            //                   |       |
            //                tray1     tray2

            SoShapeKit support = new SoShapeKit();
            support.SetPart("shape", new SoCone());
            support.Set("shape { height 3 bottomRadius .3 }");
            myScene.SetPart("childList[0]", support);

            SoShapeKit beam = new SoShapeKit();
            beam.SetPart("shape", new SoCube());
            beam.Set("shape { width 3 height .2 depth .2 }");
            beam.Set("transform { translation 0 1.5 0 } ");
            support.SetPart("childList[0]", beam);

            SoShapeKit string1 = new SoShapeKit();
            string1.SetPart("shape", new SoCylinder());
            string1.Set("shape { radius .05 height 2}");
            string1.Set("transform { translation -1.5 -1 0 }");
            string1.Set("transform { center 0 1 0 }");
            beam.SetPart("childList[0]", string1);

            SoShapeKit string2 = new SoShapeKit();
            string2.SetPart("shape", new SoCylinder());
            string2.Set("shape { radius .05 height 2}");
            string2.Set("transform { translation 1.5 -1 0 } ");
            string2.Set("transform { center 0 1 0 } ");
            beam.SetPart("childList[1]", string2);

            SoShapeKit tray1 = new SoShapeKit();
            tray1.SetPart("shape", new SoCylinder());
            tray1.Set("shape { radius .75 height .1 }");
            tray1.Set("transform { translation 0 -1 0 } ");
            string1.SetPart("childList[0]", tray1);

            SoShapeKit tray2 = new SoShapeKit();
            tray2.SetPart("shape", new SoCylinder());
            tray2.Set("shape { radius .75 height .1 }");
            tray2.Set("transform { translation 0 -1 0 } ");
            string2.SetPart("childList[0]", tray2);

            // Add EventCallback so Balance Responds to Events
            SoEventCallback myCallbackNode = new SoEventCallback();
            myCallbackNode.AddEventCallback(typeof(SoKeyboardEvent),
                                            TipTheBalance, support);
            support.SetPart("callbackList[0]", myCallbackNode);

            // Add Instructions as Text in the Scene...
            SoShapeKit myText = new SoShapeKit();
            myText.SetPart("shape", new SoText2());
            myText.Set("shape { string \"Press Left or Right Arrow Key\" }");
            myText.Set("shape { justification CENTER }");
            myText.Set("font { name \"Helvetica-Bold\" }");
            myText.Set("font { size 16.0 }");
            myText.Set("transform { translation 0 -2 0 }");
            myScene.SetPart("childList[1]", myText);

            myRenderArea = new SoWinRenderArea(this, "", true, true, true);

            // Get camera from scene and tell it to viewAll...
            SoPerspectiveCamera myCamera = (SoPerspectiveCamera)myScene.GetPart(
                                                   "cameraList[0].camera", true);
            myCamera.ViewAll(myScene, myRenderArea.GetViewportRegion());

            myRenderArea.SetSceneGraph(myScene);
            myRenderArea.SetTitle("Balance Scale Made of Nodekits");
        }
    }
}
  

Java
import tools.*;

import com.openinventor.inventor.nodekits.*;
import com.openinventor.inventor.nodes.*;
import com.openinventor.inventor.awt.*;
import com.openinventor.inventor.misc.callbacks.*;
import com.openinventor.inventor.events.*;
import com.openinventor.inventor.*;

import java.awt.*;

public class Main extends DemoInventor
{

  public static void main(String[] args)
  {
    Main applet = new Main();
    applet.isAnApplet = false;
    applet.start();
    demoMain(applet, "Balance Scale Made of Nodekits");
  }

  public void start()
  {
    super.start();

    SoCameraKit camKit = new SoCameraKit();
    SoDirectionalLight myLight = new SoDirectionalLight();
    SoSeparatorKit myScene = new SoSeparatorKit();

    // Create the Balance Scale -- put each part in the
    // childList of its parent, to build up this hierarchy:
    //
    // myScene
    // |
    // support
    // |
    // beam
    // |
    // --------
    // | |
    // string1 string2
    // | |
    // tray1 tray2

    SoShapeKit support = new SoShapeKit();
    support.setPart("shape", new SoCone());
    support.set("shape { height 3 bottomRadius .3 }");
    myScene.setPart("childList[0]", support);

    SoShapeKit beam = new SoShapeKit();
    beam.setPart("shape", new SoCube());
    beam.set("shape { width 3 height .2 depth .2 }");
    beam.set("transform { translation 0 1.5 0 } ");
    support.setPart("childList[0]", beam);

    SoShapeKit string1 = new SoShapeKit();
    string1.setPart("shape", new SoCylinder());
    string1.set("shape { radius .05 height 2}");
    string1.set("transform { translation -1.5 -1 0 }");
    string1.set("transform { center 0 1 0 }");
    beam.setPart("childList[0]", string1);

    SoShapeKit string2 = new SoShapeKit();
    string2.setPart("shape", new SoCylinder());
    string2.set("shape { radius .05 height 2}");
    string2.set("transform { translation 1.5 -1 0 } ");
    string2.set("transform { center 0 1 0 } ");
    beam.setPart("childList[1]", string2);

    SoShapeKit tray1 = new SoShapeKit();
    tray1.setPart("shape", new SoCylinder());
    tray1.set("shape { radius .75 height .1 }");
    tray1.set("transform { translation 0 -1 0 } ");
    string1.setPart("childList[0]", tray1);

    SoShapeKit tray2 = new SoShapeKit();
    tray2.setPart("shape", new SoCylinder());
    tray2.set("shape { radius .75 height .1 }");
    tray2.set("transform { translation 0 -1 0 } ");
    string2.setPart("childList[0]", tray2);

    // Add EventCallback so Balance Responds to Events
    SoEventCallback myCallbackNode = new SoEventCallback();
    myCallbackNode.addEventCallback(SoKeyboardEvent.class, new TipTheBalance(support));
    support.setPart("callbackList[0]", myCallbackNode);

    // Add Instructions as Text in the Scene...
    SoShapeKit myText = new SoShapeKit();
    myText.setPart("shape", new SoText2());
    myText.set("shape { string \"Press Left or Right Arrow Key\" }");
    myText.set("shape { justification CENTER }");
    myText.set("font { name \"Helvetica-Bold\" }");
    myText.set("font { size 16.0 }");
    myText.set("transform { translation 0 -2 0 }");
    myScene.setPart("childList[1]", myText);

    SoSeparator root = new SoSeparator();
    { // Assemble scene graph
      root.addChild(camKit);
      root.addChild(myLight);
      root.addChild(myScene);
    }

    SwRenderArea myRenderArea = new SwRenderArea();

    // Get camera from scene and tell it to viewAll...
    SoPerspectiveCamera myCamera = (SoPerspectiveCamera) camKit.getPart("camera");
    SbViewportRegion myRegion = new SbViewportRegion(myRenderArea.getSize());
    myCamera.viewAll(root, myRegion);

    myRenderArea.setSceneGraph(root);

    setLayout(new BorderLayout());
    add(myRenderArea, BorderLayout.CENTER);
  }

  class TipTheBalance extends SoEventCallbackCB
  {

    SoShapeKit m_shape;

    public TipTheBalance(SoShapeKit sh)
    {
      m_shape = sh;
    }

    public void invoke(SoEventCallback eventCB)
    {
      SoEvent ev = eventCB.getEvent();

      // Which Key was pressed?
      // If Right or Left Arrow key, then continue...
      if (SoKeyboardEvent.isKeyPressEvent(ev, SoKeyboardEvent.Keys.RIGHT_ARROW) ||
          SoKeyboardEvent.isKeyPressEvent(ev, SoKeyboardEvent.Keys.LEFT_ARROW))
      {
        SoShapeKit beam1, string1, string2;
        SbRotation startRot;
        SbRotation beamIncrement = new SbRotation();
        SbRotation stringIncrement = new SbRotation();

        // Get the different nodekits from the userData.
        // These three parts are extracted based on knowledge of the
        // motion hierarchy (see the diagram in the main routine.
        beam1 = (SoShapeKit) m_shape.getPart("childList[0]", true);
        string1 = (SoShapeKit) beam1.getPart("childList[0]", true);
        string2 = (SoShapeKit) beam1.getPart("childList[1]", true);

        // Set angular increments to be .1 Radians about the Z-Axis
        // The strings rotate opposite the beam, and the two types
        // of key press produce opposite effects.
        if (SoKeyboardEvent.isKeyPressEvent(ev, SoKeyboardEvent.Keys.RIGHT_ARROW))
        {
          beamIncrement.setValue(new SbVec3f(0.0f, 0.0f, 1.0f), -0.1f);
          stringIncrement.setValue(new SbVec3f(0.0f, 0.0f, 1.0f), 0.1f);
        }
        else
        {
          beamIncrement.setValue(new SbVec3f(0.0f, 0.0f, 1.0f), 0.1f);
          stringIncrement.setValue(new SbVec3f(0.0f, 0.0f, 1.0f), -0.1f);
        }

        // Use getPart to find the transform for each of the
        // rotating parts and modify their rotations.

        SoTransform xf = (SoTransform) beam1.getPart("transform");
        startRot = xf.rotation.getValue();
        xf.rotation.setValue(startRot.times(beamIncrement));

        xf = (SoTransform) string1.getPart("transform");
        startRot = xf.rotation.getValue();
        xf.rotation.setValue(startRot.times(stringIncrement));

        xf = (SoTransform) string2.getPart("transform");
        startRot = xf.rotation.getValue();
        xf.rotation.setValue(startRot.times(stringIncrement));

        eventCB.setHandled();
      }
    }
  }
}