15.10. Track Follower Engine

Figure 15.12. Track follower engine class


The SoTrackFollower( C++ | Java | .NET ) engine interpolates between positions to generate animations in Open Inventor.

The main input fields are a collection of points (points), which define a track to be followed, and an interpolation value (alpha) which must vary between 0 and 1 in order to determine the outputs. The two outputs are a position (position) and an orientation (orientation). Orientation has the same meaning as it does for SoCamera( C++ | Java | .NET ). Initially “forward” is the -Z direction, “up” is the +Y direction, and the orientation is rotation with respect to this starting point.

To animate an object (i.e., a sub-scene graph), the object must be controlled by an SoTransform( C++ | Java | .NET )node which has its translation and rotation fields connected respectively to the position and orientation fields of the SoTrackFollower( C++ | Java | .NET )engine.

To animate a camera, the position and orientation fields of the SoTrackFollower( C++ | Java | .NET ) engine are connected to the position and orientation fields of the camera.

In order to make an animation, the alpha field value must be connected to a time-changing field, for example, the result of an SoTimeCounter( C++ | Java | .NET )engine tuned by an SoCalculator( C++ | Java | .NET )engine.

This code fragment shows the basic objects and connections that must be defined:


C++
SoSeparator *root = new SoSeparator;
SoTransform *animationTransform = new SoTransform;
root->addChild (animationTransform);
root->addChild (objectToAnimate);

SoTrackFollower *trackFollowerEngine = new SoTrackFollower;
trackFollowerEngine->points.setValue (0, NUM_POINTS, points);
animationTransform ->translation.connectFrom (&trackFollowerEngine->position);
animationTransform ->rotation.connectFrom (&trackFollowerEngine->orientation);

SoTimeCounter *counter = new SoTimeCounter();
counter->on = true;
counter->min = 0;
counter->max = 10000;
counter->frequency = .005f;

SoCalculator *calc = new SoCalculator();
calc->expression.set1Value(0,"oa = a/10000.");
calc->a.connectFrom(&counter->output);
trackFollowerEngine->alpha.connectFrom(&calc->oa);
  

.NET
SoSeparator root = new SoSeparator();
SoTransform animationTransform = new SoTransform();
root.AddChild (animationTransform);
root.AddChild (objectToAnimate);

SoTrackFollower trackFollowerEngine = new SoTrackFollower();
trackFollowerEngine.points.SetValue (0, NUM_POINTS, points);
animationTransform.translation.ConnectFrom (trackFollowerEngine.position);
animationTransform.rotation.ConnectFrom (trackFollowerEngine.orientation);

SoTimeCounter counter = new SoTimeCounter();
counter.on.Value = true;
counter.min.Value = 0;
counter.max.Value = 10000;
counter.frequency.Value = .005f;

SoCalculator calc = new SoCalculator();
calc.expression[0] = "oa = a/10000.";
calc.a.ConnectFrom(counter.output);
trackFollowerEngine.alpha.ConnectFrom(calc.oa);

Java
SoSeparator root = new SoSeparator();
SoTransform animationTransform = new SoTransform();
root.addChild (animationTransform);
root.addChild (objectToAnimate);

SoTrackFollower trackFollowerEngine = new SoTrackFollower();
trackFollowerEngine.points.setValue(points);
animationTransform.translation.connectFrom (trackFollowerEngine.position);
animationTransform.rotation.connectFrom (trackFollowerEngine.orientation);

SoTimeCounter counter = new SoTimeCounter();
counter.on.setValue(true);
counter.min.setValue((short)0);
counter.max.setValue((short)10000);
counter.frequency.setValue(.005f);

SoCalculator calc = new SoCalculator();
calc.expression.set1Value(0,"oa = a/10000.");
calc.a.connectFrom(counter.output);
trackFollowerEngine.alpha.connectFrom(calc.oa);

A time timeStamp representing the time at which a control point must be reached can be set for each point; the “scale” of the time is independent of the animation time. However, it is proportional to it. For example, if point 1 has a time stamp value of 10, point 2 has a time stamp value of 30, and point 3 has a time stamp value of 40, it means that the interpolation will take twice as long to go from point 1 to point 2 (20 time units) as it will to go from point 2 to point 3 (10 time units).

The actual time to complete an animation, and therefore the overall speed, is determined by the time it takes for the alpha value to go from 0 to 1.

If no time stamp is specified for a point (SO_UNDEFINED_TIME_STAMP), then the engine will compute a time stamp which will be proportional to the distance between the previous and the next control point, i.e., speed is constant.

If no time stamps are specified, they are all computed to be proportional to the distance between the points, and speed is constant over the entire track.

The loop flag tells the engine that the first point of the track must also be the last one. In this particular case, the timeStamp can contains one more value than the points field, and the last time stamp is assumed to apply to the first/last control point in a loop.

A turning radius can be associated with each point of the track. When this radius is 0, the orientation of the object will change immediately. Instead, if a radius is specified for a point, the position and orientation will follow the arc of the circle tangent to the two parts of the track at this point which has the specified radius. This could be used, for example, to smoothly animate a car through a turn at an intersection.

For each point of the track, a roll angle (rollAngle) can be specified. This will bank the object at the specified angle during the turn. The speed (rollSpeed) at which this angle is reached can be specified also: with a speed at 1, the object is rolled as soon as the turn starts. With the speed at 0, the object is rolled smoothly from flat and the roll angle is reached in the middle of the turn. Intermediate values will adjust the point where the roll angle is reached.

If the keep roll angle flag (keepRollAngle) is set for a point, the roll speed is assumed to be 1 in the second part of the turn for the current control point and in the first part of the turn for the next control point. This allows the roll angle to be maintained between two adjacent turns. This could be used, for example, to have a more realistic animation for an airplane.

A rotation can be added to the computed rotation: it is named “head rotation” because it acts like the rotation of the head of a passenger in a vehicle following a track. The head rotation can be specified at each point, and the actual head rotation is computed by interpolating the values between the points.

If the useHeadRotationOnly flag is set, only this interpolation is written in the output field. This allows you to create a track that moves a camera around an object, and to orient the camera toward the object at each point.

Here’s a simple example of using the SoTrackFollower( C++ | Java | .NET ) engine to animate a cube going around a sphere. First we show the definition of the SoTrackFollower( C++ | Java | .NET ) engine.

TrackFollower
{
points [ -4 0 -4,
  -4 0 4,
  4 0 4,
  4 0 -4]
  radius [ 2 ]
  rollAngle [ 0.25]
  rollSpeed [ 0.5 ]
  loop TRUE
}

Now to make a complete file, we connect the Track Follower engine to a transform node, and two other engines (SoCalculator( C++ | Java | .NET )and SoElapsedTime( C++ | Java | .NET ) ) are connected to the Track Follower engine to generate the time steps.

#Inventor V2.1 ascii

  Separator
    {

    Sphere
      {
      }
    Transform
      {
      translation 0 0 0 =
        DEF track TrackFollower
        {
        points [ -4 0 -4,
          -4 0 4,
          4 0 4,
          4 0 -4]
          radius [ 2 ]
          rollAngle [ 0.25]
          rollSpeed [ 0.5 ]
          alpha 0 =
          Calculator
          {
          a 0 =
            ElapsedTime
            {
            speed 0.1
            } . timeOut
            expression "oa = fmod (a , 1)"
          } . oa
          loop TRUE
        } . position
        rotation 0 0 0 0 = USE track . orientation
      }
    Cube
      {
      }
    }