/*
 *
 *  Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved. 
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  Further, this software is distributed without any warranty that it is
 *  free of the rightful claim of any third person regarding infringement
 *  or the like.  Any license provided herein, whether implied or
 *  otherwise, applies only to this software file.  Patent licenses, if
 *  any, provided herein do not apply to combinations of this program with
 *  other software, or any other product whatsoever.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 *  Mountain View, CA  94043, or:
 * 
 *  http://www.sgi.com 
 * 
 *  For further information regarding this notice, see: 
 * 
 *  http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */

/*----------------------------------------------------------------
 *  This is an example from the Inventor Mentor,
 *  chapter 14, example 3.
 *
 *  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 <cstdlib>
#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>

#include <Inventor/actions/SoWriteAction.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();
   }
}

int
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...
   SoPerspectiveCamera *myCamera = SO_GET_PART(myScene,
      "cameraList[0].camera", SoPerspectiveCamera);
   myCamera->viewAll(myScene, myRenderArea->getSize());

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

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