15.9. Animation Engines

The following engines can be used to animate objects in the scene graph. Each of these engines has a timeIn field, which is connected automatically to the realTime global field when the engine is constructed. This field can, however, be connected to any other time source.

Elapsed-Time Engine

The elapsed-time engine is a basic controllable time source. You can start, stop, reset, pause, and control the speed of this engine. If you pause it (by setting the pause field to TRUE), it stops updating its timeOut field, but it keeps counting internally. When you turn off the pause, it jumps to its current position without losing time.

Example 15.2, “ Using an Elapsed-Time Engine uses the output from an elapsed time engine to control the translation of a figure. The resulting effect is that the figure slides across the scene. Figure 15.9, “ Scene Graph for Elapsed-Time Engine Example shows the scene graph for this example. The timeOut output of the elapsed time engine (myCounter) is connected to an SoComposeVec3f SoComposeVec3f SoComposeVec3f engine (slideDistance). This second engine inserts the timeOut value into the x slot of a vector. Once the value is in vector format, it can be connected to the translation field of the slideTranslation node.

Note that the timeOut value is an SoSFTime SoSFTime SoSFTime , but the SoComposeVec3f SoComposeVec3f SoComposeVec3f engine requires inputs of type SoSFFloat SoSFFloat SoSFFloat . Inventor performs this conversion automatically for you, converting the time to a number of seconds.

Scene Graph for Elapsed-Time Engine Example

Figure 15.9.  Scene Graph for Elapsed-Time Engine Example



Example 15.2.  Using an Elapsed-Time Engine

   // Set up transformations
   SoTranslation *slideTranslation = new SoTranslation;
   root->addChild(slideTranslation);
   SoTransform *initialTransform = new SoTransform;
   initialTransform->translation.setValue(-5., 0., 0.);
   initialTransform->scaleFactor.setValue(10., 10., 10.);
   initialTransform->rotation.setValue(SbVec3f(1,0,0), M_PI/2.);
   root->addChild(initialTransform);

   // Read the figure object from a file and add to the scene
   SoInput myInput;
   if (!myInput.openFile("jumpyMan.iv")) 
      return (1);
   SoSeparator *figureObject = SoDB::readAll(&myInput);
   if (figureObject == NULL) 
      return (1);
   root->addChild(figureObject);

   // Make the X translation value change over time.
   SoElapsedTime *myCounter = new SoElapsedTime;
   SoComposeVec3f *slideDistance = new SoComposeVec3f;
   slideDistance->x.connectFrom(&myCounter->timeOut);
   slideTranslation->translation.connectFrom(
            &slideDistance->vector);
    
// Set up transformations
SoTranslation slideTranslation = new SoTranslation();
root.AddChild(slideTranslation);
SoTransform initialTransform = new SoTransform();
initialTransform.translation.SetValue(-5.0f, 0.0f, 0.0f);
initialTransform.scaleFactor.SetValue(10.0f, 10.0f, 10.0f);
initialTransform.rotation.SetValue(new SbVec3f(1.0f, 0.0f, 0.0f),
  (float)(Math.PI / 2.0f));
root.AddChild(initialTransform);

// Read the figure object from a file and add to the scene
SoInput myInput = new SoInput();
myInput.OpenFile("../../../../../data/jumpyMan.iv");

SoSeparator figureObject = SoDB.ReadAll(myInput);
root.AddChild(figureObject);

// Make the X translation value change over time.
SoElapsedTime myCounter = new SoElapsedTime();
SoComposeVec3f slideDistance = new SoComposeVec3f();
slideDistance.x.ConnectFrom(myCounter.timeOut);
slideTranslation.translation.ConnectFrom(slideDistance.vector);
    
// Set up transformations
SoTranslation slideTranslation = new SoTranslation();
root.addChild(slideTranslation);
SoTransform initialTransform = new SoTransform();
initialTransform.translation.setValue(-5.0f, 0.0f, 0.0f);
initialTransform.scaleFactor.setValue(10.0f, 10.0f, 10.0f);
initialTransform.rotation.setValue(new SbVec3f(1.0f, 0.0f, 0.0f),
  (float)(Math.PI / 2.0f));
root.addChild(initialTransform);

// Read the figure object from a file and add to the scene
SoInput myInput = new SoInput();
myInput.openFile("../../../../data/models/jumpyMan.iv");

SoSeparator figureObject = SoDB.readAll(myInput);
root.addChild(figureObject);

// Make the X translation value change over time.
SoElapsedTime myCounter = new SoElapsedTime();
SoComposeVec3f slideDistance = new SoComposeVec3f();
slideDistance.x.connectFrom(myCounter.timeOut);
slideTranslation.translation.connectFrom(slideDistance.vector);
    


One-Shot Engine

The SoOneShot SoOneShot SoOneShot engine is started when its trigger input is touched (with either touch() or setValue()). It runs for the specified duration, updating its timeOut field until it reaches the duration time. The ramp output, a float value from 0.0 (when the trigger starts) to 1.0 (when the duration is reached), is provided as a convenience. For example, the ramp output of a one-shot engine could be connected to the alpha input of a rotation interpolation to make a door open.

This engine has two flags stored in an SoSFBitMask SoSFBitMask field. The Retriggerable flag specifies whether to start the cycle over if a trigger occurs in the middle of a cycle. If this flag is not set (the default), the trigger is ignored and the cycle is finished. If this flag is set, the cycle restarts when a trigger occurs.

The Hold_Final flag specifies what happens at the end of the cycle. If this flag is not set (the default), all outputs return to 0 when the cycle finishes. If this flag is set, the isActive output returns to 0, but ramp and timeOut stay at their final values.

Time-Counter Engine

The SoTimeCounter SoTimeCounter SoTimeCounter engine counts from a minimum count (min) to a maximum count (max). The value for step indicates how the timer counts (the default is in increments of 1). The frequency input specifies the number of min-to-max cycles per second.

Unlike the one-shot and elapsed-time engines, the time-counter engine does not output a time; it outputs the current count. Each time the time counter starts a cycle, it triggers its syncOut output. This output can be used to synchronize one of the triggered engines with some other event.

Example 15.3, “ Using Time-Counter Engines uses the output from two time-counter engines to control the horizontal and vertical motion of a figure. The resulting effect is that the figure jumps across the screen.

This example creates three engines, as shown in Figure 15.10, “ Scene Graph for the Time-Counter Example. The output of the jumpWidthCounter (a time counter engine) is connected to the x input of the jump engine (an SoComposeVec3f SoComposeVec3f SoComposeVec3f engine). The output of the jumpHeightCounter (another time counter engine) is connected to the y input of the jump engine. The jump engine composes a vector using the x and y inputs, and then feeds this vector into the translation field of the jumpTranslation node. Figure 15.11, “ Controlling an Object's Movement Using Time-Counter Engines shows scenes from this example.

Scene Graph for the Time-Counter Example

Figure 15.10.  Scene Graph for the Time-Counter Example



Example 15.3.  Using Time-Counter Engines

// Set up transformations
SoTranslation *jumpTranslation = new SoTranslation;
root->addChild(jumpTranslation);
SoTransform *initialTransform = new SoTransform;
initialTransform->translation.setValue(-20., 0., 0.);
initialTransform->scaleFactor.setValue(40., 40., 40.);
initialTransform->rotation.setValue(SbVec3f(1,0,0), M_PI/2.);
root->addChild(initialTransform);

// Read the man object from a file and add to the scene
SoInput myInput;
if (!myInput.openFile("jumpyMan.iv")) 
   return (1);
SoSeparator *manObject = SoDB::readAll(&myInput);
if (manObject == NULL) 
   return (1);
root->addChild(manObject);
      
// Set up transformations
SoTranslation jumpTranslation = new SoTranslation();
root.AddChild(jumpTranslation);
SoTransform initialTransform = new SoTransform();
initialTransform.translation.SetValue(-20.0f, 0.0f, 0.0f);
initialTransform.scaleFactor.SetValue(40.0f, 40.0f, 40.0f);
initialTransform.rotation.SetValue(new SbVec3f(1.0f, 0.0f, 0.0f),(float)(Math.PI / 2.0f));
root.AddChild(initialTransform);

// Read the man object from a file and add to the scene
SoInput myInput = new SoInput();
myInput.OpenFile("../../../../../data/jumpyMan.iv");

SoSeparator manObject = SoDB.ReadAll(myInput);
root.AddChild(manObject);
      
// Set up transformations
SoTranslation jumpTranslation = new SoTranslation();
root.addChild(jumpTranslation);
SoTransform initialTransform = new SoTransform();
initialTransform.translation.setValue(-20.0f, 0.0f, 0.0f);
initialTransform.scaleFactor.setValue(40.0f, 40.0f, 40.0f);
initialTransform.rotation.setValue(new SbVec3f(1.0f, 0.0f, 0.0f),(float)(Math.PI / 2.0f));
root.addChild(initialTransform);

// Read the man object from a file and add to the scene
SoInput myInput = new SoInput();
myInput.openFile("../../../../data/models/jumpyMan.iv");

SoSeparator manObject = SoDB.readAll(myInput);
root.addChild(manObject);
      


Controlling an Object's Movement Using Time-Counter Engines

Figure 15.11.  Controlling an Object's Movement Using Time-Counter Engines



// Create two counters, and connect to X and Y translations.
// The Y counter is small and high frequency.
// The X counter is large and low frequency.
// This results in small jumps across the screen,
// left to right, again and again and again.
SoTimeCounter *jumpHeightCounter = new SoTimeCounter;
SoTimeCounter *jumpWidthCounter = new SoTimeCounter;
SoComposeVec3f *jump = new SoComposeVec3f;
   
jumpHeightCounter->max = 4;
jumpHeightCounter->frequency = 1.5;
jumpWidthCounter->max = 40;
jumpWidthCounter->frequency = 0.15;
   
jump->x.connectFrom(&jumpWidthCounter->output);
jump->y.connectFrom(&jumpHeightCounter->output);
jumpTranslation->translation.connectFrom(&jump->vector);
    
// Create two counters, and connect to X and Y translations.
// The Y counter is small and high frequency.
// The X counter is large and low frequency.
// This results in small jumps across the screen, 
// left to right, again and again and again and ....
SoTimeCounter jumpHeightCounter = new SoTimeCounter();
SoTimeCounter jumpWidthCounter = new SoTimeCounter();
SoComposeVec3f jump = new SoComposeVec3f();

jumpHeightCounter.max.Value = 4;
jumpHeightCounter.frequency.Value = 1.5f;
jumpWidthCounter.max.Value = 40;
jumpWidthCounter.frequency.Value = 0.15f;

jump.x.ConnectFrom(jumpWidthCounter.output);
jump.y.ConnectFrom(jumpHeightCounter.output);
jumpTranslation.translation.ConnectFrom(jump.vector);
    
// Create two counters, and connect to X and Y translations.
// The Y counter is small and high frequency.
// The X counter is large and low frequency.
// This results in small jumps across the screen, 
// left to right, again and again and again and ....
SoTimeCounter jumpHeightCounter = new SoTimeCounter();
SoTimeCounter jumpWidthCounter = new SoTimeCounter();
SoComposeVec3f jump = new SoComposeVec3f();

jumpHeightCounter.max.setValue((short)4);
jumpHeightCounter.frequency.setValue(1.5f);
jumpWidthCounter.max.setValue((short)40);
jumpWidthCounter.frequency.setValue(0.15f);

jump.x.connectFrom(jumpWidthCounter.output);
jump.y.connectFrom(jumpHeightCounter.output);
jumpTranslation.translation.connectFrom(jump.vector);