CHAPTER 12

Using Interpolator

12.1 The Interpolator class

12.2 The Alpha class

12.3 Example of Interpolator usage

12.4 Using a cubic-spline interpolator

12.5 Summary 219

The Interpolator class defines the functionality for an important and powerful set of behaviors that deserve extra mention. The behaviors derived from Interpolator allow an object’s current state to be interpolated between a set of defined states as a function of time.

This chapter introduces the Interpolator classes and the Alpha class, which is used to control the speed of interpolation.

12.1 The Interpolator class

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Behavior
                          |
                          +--javax.media.j3d.Interpolator

Java 3D includes a rich set of behaviors for interpolating an object between states. These behaviors are important for many animation and visualization applications and are covered in detail in this section. Interpolator behaviors can be used to interpolate an object’s:

All the Interpolation classes require an Alpha object. The Alpha class is used to convert the current time to a value between 0 and 1—this value is then used by the Interpolator to perform its specific action. The Alpha class is described in section 12.2.

12.2 The Alpha class

java.lang.Object
  |
  +--javax.media.j3d.Alpha

A good understanding of the Alpha class is key to using all the Interpolator behaviors. The Alpha class defines a function that converts the current time (in milliseconds) to a value between 0 and 1 (alpha value). The alpha value can then be used by the Interpolator to produce the desired interpolation between specified end states.

For example, a PositionInterpolator might be used to move an object between position (0,0,0) and (0,0,5) in 10 seconds. To achieve the desired result, an Alpha object must be created that returns 0 at 0 seconds and 1 at 10 seconds. The PositionInterpolator can then use the Alpha object to create the desired z coordinate (table 12.1).

Table 12.1 Coordinate calculation using Alpha
Time (seconds) Alpha Z Coordinate
0 0 0 * 5 = 0
5 0.5 0.5 * 5 = 2.5
10 1 1 * 5 = 5

The Alpha class uses a parameterized function to convert time values to alpha values between 0 and 1.

Figure 12.1 illustrates the basic shape of the functions that can be created using the Alpha class. Nine parameters can be used to create a customized Alpha function are listed there.

Figure 12.1

Figure 12.1 The phases of the Alpha class: triggerTime (1), phase delay (2), increasing alpha (3), increasing alpha ramp (4), at one (5), decreasing alpha (6), decreasing alpha ramp (7), at zero (8), and loopCount (9)

The increasing alpha phase (3) is composed of three potential sections, as is the decreasing alpha phase (6). The values of increasing alpha ramp (4) and decreasing alpha ramp (7) define symmetrical acceleration and deceleration of the Alpha value at the beginning and end of the phase. These quadratic sections help to smooth the transition from 0 to 1 (increasing alpha) or 1 to 0 (decreasing alpha). Note that the Alpha value varies linearly between the quadratic sections. The loopCount parameter (9) allows Alpha functions to be repeated (a fixed number of times or infinitely) by joining Alpha functions end to end.

Run the AlphaTest example and interactively modify the nine parameters to get a good feel for how to parameterize an Alpha object for your application (figure 12.2).

Figure 12.2

Figure 12.2 The AlphaTest example allows an Alpha object to be parameterized interactively and plots the resulting Alpha function

NOTE     
 
 
 
 
While testing the AlphaTest example, I ran into an interesting bug in the Alpha class. If either the increasing alpha ramp or decreasing alpha ramp parameter is set to zero, the other will also be set to zero. You can work around this bug by setting the parameter to 1 millisecond instead of 0 milliseconds.

12.2.1 Using a custom Alpha class

You can, of course, derive your own class from Alpha to implement your own Alpha function. The FileAlpha class from the CustomAlphaTest examples loads times and alpha values from a text file and linearly interpolates between them.

From the file Values.xls from the CustomAlphaTest example

0     0
1000  0.1
3000  0.4
4000  0.2
6000  0.8
10000 0.5
12000 0.1
14000 1.0
16000 0.1

The text file defines an ordered series of time and alpha value pairs (figure 12.3). Times are specified in milliseconds.

The FileAlpha class overrides the following Alpha methods.

public void setStartTime(long l)
public long getStartTime()
public void setLoopCount( int i )
public int getLoopCount()
public boolean finished()
public float value( long time )

Please refer to the FileAlpha class for details.

The CustomAlphaTest is also interesting in that it interpolates the position of a ColorCube using the FileAlpha and plots the value and time of the FileAlpha on the graph shown in figure 12.3.

Figure 12.3

Figure 12.3 The FileAlpha class loads times and Alpha values from a file and linearly interpolates between them to provide a highly flexible Alpha function

12.2.2 Summary

An Alpha class is very simple—it converts a time value in milliseconds to a value between 0 and 1. The built-in Alpha class defines all the parameters for an Alpha function to implement a common class of onset, peak, offset activation functions.

A custom Alpha function, such as FileAlpha, is a very powerful mechanism to feed application-specific data into one of the Interpolator-derived classes. An application might define an Alpha class that samples data straight from a communications port, or reads precomputed data from a file. The output from the custom Alpha class can then be used to parameterize a wide variety of Interpolators.

Alpha classes are used in conjunction with Interpolators, but they do not do anything visible themselves. Their prime purpose in Java 3D is to provide input to the Interpolator classes. Interpolators are the subjects of section 12.3, so read on.

12.3 Example of Interpolator usage

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Behavior
                          |
                          +--javax.media.j3d.Interpolator

The Java 3D Interpolators were explored using the InterpolatorTest and the SplineInterpolatorTest examples. The InterpolatorTest example demonstrates using the following Interpolators:

SwitchValueInterpolator

The SwitchValueInterpolator is used to cycle through the children of a Switch Node based on the output from an Alpha object. The SwitchValueInterpolator maps the output from the Alpha into a current visible child Node using the following algorithm:

float f = alpha.value();
int i = firstSwitchIndex +
  (int)(f * (float)(childCount - 1) + 0.5F);
target.setWhichChild(i);

Note that the Switch Node passed to the SwitchInterpolator must have the Switch.ALLOW_SWITCH_WRITE capability.

//create the Switch Node
Switch switchNode = new Switch();

//set the WRITE capability
switchNode.setCapability( Switch.ALLOW_SWITCH_WRITE );

//add children to switchNode here…

//create the Alpha for the Interpolator
Alpha alpha = new Alpha(  -1,
       Alpha.INCREASING_ENABLE | Alpha.DECREASING_ENABLE,
       500,
       100,
       5000,
       2000,
       1000,
       5000,
       2000,
       500 );
       
//create the SwitchInterpolator and pass the Alpha
//and a Switch Node
Interpolator switchInterpolator =
 new SwitchValueInterpolator( alpha, switchNode );
 
//add the Interpolator to a parent BranchGroup
//and set Scheduling Bounds
BranchGroup bg = new BranchGroup();
bg.addChild( interpolator );
interpolator.setSchedulingBounds( getApplicationBounds() );

ColorInterpolator

The ColorInterpolator can be used to linearly interpolate the Material diffuse color of an Appearance between two extremes. Note that the Material must have the ALLOW_COMPONENT_WRITE capability bit set.

//create an Appearance
Appearance app = new Appearance();

//create a Material and assign an initial color
Color3f objColor = new Color3f(1.0f, 0.7f, 0.8f);
Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
Material mat =
 new Material(objColor, black, objColor, black, 80.0f);
 
//ensure the Interpolator has WRITE access to the Material
mat.setCapability( Material.ALLOW_COMPONENT_WRITE );

//assign the Material to the Appearance
app.setMaterial( mat );

//create the Interpolator–by default interpolate
//between black and white
Interpolator interpolator =
 new ColorInterpolator( alpha, app.getMaterial() );

PositionInterpolator

The PositionInterpolator can be used to linearly interpolate between two x, y, z positions. The PositionInterpolator modifies a TransformGroup, which will in turn affect the position of all its child Nodes. The TransformGroup must have the ALLOW_TRANSFORM_WRITE capability set.

//create the TransformGroup
TransformGroup tg = new TransformGroup();
tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

//create the Interpolator—by default interpolate
//between x = 0 and x = 1
Interpolator interpolator = new PositionInterpolator ( alpha, tg );

RotationInterpolator

The RotationInterpolator can be used to linearly interpolate between two sets of axis angle rotations. The RotationInterpolator modifies a TransformGroup, which will in turn affect the position of all its child Nodes. The TransformGroup must have the ALLOW_TRANSFORM_WRITE capability set.

//create the Interpolator—by default interpolate
//between Y angle = 0 and Y angle = 2p
Interpolator interpolator = new RotationInterpolator ( alpha, tg );

ScaleInterpolator

The ScaleInterpolator can be used to linearly interpolate between two scale values for an axis. The ScaleInterpolator modifies a TransformGroup, which will in turn affect the position of all its child Nodes. The TransformGroup must have the ALLOW_TRANSFORM_WRITE capability set.

//create the Interpolator—by default interpolate
//between a uniform scale of 0 and 1
Interpolator interpolator = new ScaleInterpolator ( alpha, tg );

TransparencyInterpolator

The TransparencyInterpolator can be used to linearly interpolate between two transparency values. The TransparencyInterpolator modifies an Appearance’s TransparencyAttributes. The TransparencyAttributes must have the ALLOW_VALUE_WRITE capability set.

//create the TransparencyAttributes
TransparencyAttributes transparency = new TransparencyAttributes();

//set the required capability bit
transparency.setCapability(
  TransparencyAttributes.ALLOW_VALUE_WRITE );
  
//set the transparency mode
transparency.setTransparencyMode( TransparencyAttributes.NICEST );

//assign the transparency to an Appearance
app.setTransparencyAttributes( transparency );

//create the interpolator and interpolate
//between 0 (opaque) and 0.8.
Interpolator interpolator = new TransparencyInterpolator( alpha, app.getTransparencyAttributes(), 0, 0.8f );

RotPosScalePathInterpolator

The RotPosScalePathInterpolator is the most flexible of all the PathInterpolators. As its name suggests, it allows the rotation, position, and scale of a TransformGroup to be modified.

Rotations, positions, and scales are specified at a series of Alpha values (or knots). The rotation, position, and scale defines a pose that, along with the time information, allows the Interpolator to linearly interpolate between poses based on the Alpha value and the defined knots.

As the name knots implies, a useful way to visualize the interpolator is as a string stretched taut between a number of points. Each point is called a knot, and as well as having an Alpha value (time) when the interpolator is to reach the knot it also possesses position, scale, and rotation information (a pose). The distance between knots defines the speed at which the interpolation between values must occur.

Knots are specified using float values between 0 and 1, where 0 is the knot used at Alpha value 0 and 1 is the knot used at Alpha time 1. The array of knot values defines a mapping from Alpha value to pose information. The knot values must increase from 0 to 1 in the knot array.

//define the knots array that map from Alpha to pose index
float[] knots = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f,
                 0.6f, 0.8f, 0.9f, 1.0f};
                 
//create array with 9 poses: containing rotation, position
//and scale values
Quat4f[] quats = new Quat4f[9];
Point3f[] positions = new Point3f[9];
float[] scales = {0.2f, 0.5f, 0.8f, 2.3f, 5.4f,
                  0.6f, 0.4f, 0.2f, 0.1f};
                  
//define the rotation values for each of the 9 poses
quats[0] = new Quat4f(0.3f, 1.0f, 1.0f, 0.0f);
quats[1] = new Quat4f(1.0f, 0.0f, 0.0f, 0.3f);
quats[2] = new Quat4f(0.2f, 1.0f, 0.0f, 0.0f);
quats[3] = new Quat4f(0.0f, 0.2f, 1.0f, 0.0f);
quats[4] = new Quat4f(1.0f, 0.0f, 0.4f, 0.0f);
quats[5] = new Quat4f(0.0f, 1.0f, 1.0f, 0.2f);
quats[6] = new Quat4f(0.3f, 0.3f, 0.0f, 0.0f);
quats[7] = new Quat4f(1.0f, 0.0f, 1.0f, 1.0f);
quats[8] = quats[0];

//define the positions for each of the 9 poses
positions[0]= new Point3f(0.0f,  0.0f, -1.0f);
positions[1]= new Point3f(1.0f, -2.0f, -2.0f);
positions[2]= new Point3f(-2.0f,  2.0f, -3.0f);
positions[3]= new Point3f(1.0f,  1.0f, -4.0f);
positions[4]= new Point3f(-4.0f, -2.0f, -5.0f);
positions[5]= new Point3f(2.0f,  0.3f, -6.0f);
positions[6]= new Point3f(-4.0f,  0.5f, -7.0f);
positions[7]= new Point3f(0.0f, -1.5f, -4.0f);
positions[8]= positions[0];

//create the interpolator and pass Alpha, TransformGroup,
//knots, and pose information
RotPosScalePathInterpolator rotPosScalePathInterplator = new RotPosScalePathInterpolator(
 alpha,
 tg,
 new Transform3D(),
 knots,
 quats,
 positions,
 scales );

As you can see, the rotation angles are specified using the Quat4f class. The Quat4f class specifies a rotation as a quaternion. The following is a description of quaternions, taken from the excellent “Matrix and Quaternion FAQ.” The FAQ can be found online and it currently maintained by Andreas Junghanns at http://www.cs.ualberta.ca/~andreas/math/matrfaq_latest.html.

“Quaternions extend the concept of rotation in three dimensions to rotation in four dimensions. This avoids the problem of “gimbal-lock” and allows for the implementation of smooth and continuous rotation. In effect, they may be considered to add an additional rotation angle to spherical coordinates: longitude, latitude, and rotation angles. A Quaternion is defined using four floating point values |x y z w|. These are calculated from the combination of the three coordinates of the rotation axis and the rotation angle.”

Unfortunately most people do not think readily in quaternions, so the following two conversion functions are useful to help create quaternions from axis or Euler angles. The algorithms for the functions were taken from the “Matrix and Quaternion FAQ.” I encourage you to check the FAQ for updates, optimizations and corrections to this code.

From InterpolatorTest.java

//Quat4f createQuaternionFromAxisAndAngle
//( Vector3d axis, double angle )
{
 double sin_a = Math.sin( angle / 2 );
 double cos_a = Math.cos( angle / 2 );
 
 //use a vector so we can call normalize
 Vector4f q = new Vector4f();
 
 q.x = (float) (axis.x * sin_a);
 q.y = (float) (axis.y * sin_a);
 q.z = (float) (axis.z * sin_a);
 q.w = (float) cos_a;
 
 //It is best to normalize the quaternion
 //so that only rotation information is used
 q.normalize();
 
 //convert to a Quat4f and return
 return new Quat4f( q );
}

//Quat4f createQuaternionFromEuler( double angleX,
//double angleY, double angleZ )
{
 //simply call createQuaternionFromAxisAndAngle for each axis
 //and multiply the results
 Quat4f qx = createQuaternionFromAxisAndAngle(
    new Vector3d(1,0,0), angleX );
 Quat4f qy = createQuaternionFromAxisAndAngle(
    new Vector3d(0,1,0), angleY );
 Quat4f qz = createQuaternionFromAxisAndAngle(
    new Vector3d(0,0,1), angleZ );
    
 //qx = qx * qy
 qx.mul( qy );
 
 //qx = qx * qz
 qx.mul( qz );
 
 return qx;
}

12.3.1 Design of the InterpolatorTest example

The InterpolatorTest example creates a Switch Node and attaches a SwitchInterpolator. A custom Alpha class (RandomAlpha) is used to generate Alpha values for the SwitchInterpolator. The RandomAlpha generates a random Alpha value (between 0 and 1) every 10 seconds. This causes the SwitchInterpolator to randomly switch between child Nodes every 10 seconds.

The Switch Node has six child Nodes:

  1. Group containing a ColorInterpolator: Operates on an Appearance.
  2. Group containing a PositionInterpolator: Operates on a TransformGroup.
  3. Group containing a RotationInterpolator: Operates on a TransformGroup.
  4. Group containing a ScaleInterpolator: Operates on a TransformGroup.
  5. Group containing a TransparencyInterpolator: Operates on an Appearance.
  6. Group containing a RotPosScaleInterpolator: Operates on a TransformGroup.

Each Group also contains a Link Node to a common SharedGroup. The SharedGroup contains a TransformGroup with a child Shape3D. The parent TransformGroup is passed to each Interpolator that requires a TransformGroup, while the Shape3D’s Appearance is passed to each Interpolator that requires an Appearance. A Text2D is also created with the name of the Interpolator as text so that the current active Interpolator can be seen. See figure 12.4.

Figure 12.4

Figure 12.4 The basic scenegraph design for the InterpolatorTest example

The InterpolatorTest example exploits a useful feature of Behaviors: when the Switch changes its active child Node, the Behaviors of the inactive child Nodes are no longer scheduled. In this way, the SwitchInterpolator that randomly selects child Nodes (using the RandomAlpha class) also activates a single Behavior—inactive Behaviors are no longer processed even though they are still enabled.

The RandomAlpha class is very simple:

//This class defines an Alpha class that returns
//a random value every N milliseconds.
public class RandomAlpha extends Alpha
{
 protected long      m_LastQueryTime = 0;
 protected float      m_Alpha = 0;
 protected final int    m_kUpdateInterval = 10000;
 
 public RandomAlpha()
 {
 }
 
//core method override returns the Alpha value for a given time
public float value( long time )
 {
  if( System.currentTimeMillis()
       - m_LastQueryTime > m_kUpdateInterval )
  {
   m_LastQueryTime = System.currentTimeMillis();
   m_Alpha = (float) Math.random();
  }
  
  return m_Alpha;
 }
}

12.4 Using a cubic-spline interpolator

Java 3D includes a class to perform cubic-spline interpolation of position, rotation, and scale between key frames. The class uses the Kochanek-Bartels (K-B) algorithm to allow smooth interpolation between key frames (knots), which specify pose and interpolation information. Unlike linear interpolation, which suffers from potential discontinuities at control points (knots), K-B interpolation, while guaranteeing that the path passes through all the control points, varies the path smoothly. Instead of a piece of taut string strung between control points, the analogy is closer to a springy rod that passes through each point.

The K-B algorithm is fairly complex, and a discussion of the mathematics behind the algorithm is beyond the scope of this practical book. There are a number of very good online resources that describe the algorithm in detail. In addition, because the Interpolator class that implements the algorithm is in the Java 3D utils package, all the source code is available for inspection.

The good news is that the algorithm is extremely flexible and allows very fine control over the interpolated path. Each key frame, along with all the pose information (position, rotation, scale), also defines the following three parameters:

As with most terms in this book, a picture is worth a thousand words, so see figures 12.5, 12.6, and 12.7, which illustrate the K-B parameters.

Figure 12.5

Figure 12.5 The effect of varying the tension parameter at a key frame on a 2D K-B spline curve



Figure 12.6

Figure 12.6 The effect of varying the continuity parameter at a key frame on a 2D K-B spline curve



Figure 12.7

Figure 12.7 The effect of varying the bias parameter at a key frame on a 2D K-B spline curve



Tension parameter

The tension parameter controls the length of the curve segments either side of a key frame. The higher the tension, the shorter the segments.

Continuity

The continuity parameter enables you to purposefully introduce discontinuities into the path. The path can either mimic a sharp corner at the key frame (–1) or overshoot the key frame and slingshot back (1).

Bias

The bias parameter enables you to apportion tension at a key frame to each curve segment either side of the key frame. In this way the path is skewed about the key frame, with the segment on one side of the key frame slacker than the segment on the other side.

12.4.1 The SplineInterpolatorTest example

SplineInterpolatorTest is one of the most ambitious examples of the book. When I thought of animating an object using a spline curve, I thought of tracking a camera along a spline curve and rendering the results. My initial idea was to model an architectural or city fly-over, and the example grew from there.

SplineInterpolatorTest creates a virtual model of the city of Boston. The example loads a predefined spline curve from a file and takes the viewer on a virtual flight around the city (figures 12.8, 12.9, and 12.10).

Figure 12.8

Figure 12.8 A frame from the SplineInterpolatorTest example. Take a virtual fly-over of Boston city center



Figure 12.9

Figure 12.9 When viewed from afar, the SplineInterpolatorTest dynamically switches to a low-resolution image of the whole of the city. One of the orbiting helicopters is visible in the frame



Figure 12.10

Figure 12.10 Looking up at one of the helicopters orbiting the city



The virtual city is composed of the following elements:

The rendered results are very pleasing, and the smoothness of the spline based animation gives a nice impression of movement and acceleration through the generated world. The satellite images were downloaded from the Microsoft Terraserver satellite image database. Terraserver is very useful in that different resolution images (i.e., from 32 meters per pixel down to 1 meter per pixel) can be easily downloaded. It allows a LOD behavior to be used, so that a high-resolution image appears when the viewer is close to the city and a lower resolution image appears when the viewer is far above the city. Both texture images are 256 × 256 pixels in size, and hence should be supported by hardware renderers.

To support the functionality required for the example, a base class was created for defining the behaviors, geometry, and appearance for the objects in the world. For an application of this complexity, the approach taken in most example code, which is to throw everything in the Applet class, was clearly not going to work. What was required was an OO design that empowered the objects within the world with the abilities they required. These abilities include:

These chores are handled by the ComplexObject class, which allows the main code of the application to deal with the objects themselves, and not with TransformGroups, sounds, appearances, and the like. This keeps the reusable functionality low in the class hierarchy and puts the application-specific details in super classes and the main Applet class itself.

The code for the example is obviously too long to be shown in full, so some interesting highlights will be discussed. The source code for ComplexObject is in the org.selman.java3d.book package.

12.4.2 Creating the LOD behavior

//create a Switch group that contains two versions of the world:
//The first is a high resolution version, and the second is a
//lower resolution version.
public Group createLodLand( Group g )
{
 Switch switchNode = new Switch();
 switchNode.setCapability( Switch.ALLOW_SWITCH_WRITE );
 
 Group hiResGroup = createLand( switchNode );
 createEnvirons( switchNode );
 
//Create a DistanceLOD that will select the child
//of the Switch node based on distance. Here we are
//selecting child 0 (high res) if we are closer
//than 180 units to 0,0,0, and child 1 (low res) otherwise.
 float[] distanceArray = {180};
 
 DistanceLOD distanceLod = new DistanceLOD( distanceArray );
 distanceLod.setSchedulingBounds( getApplicationBounds() );
 distanceLod.addSwitch( switchNode );
 
 g.addChild( distanceLod );
 g.addChild( switchNode );
 
 return hiResGroup;
}

12.4.3 Reading spline key frames from disk

The following code is used to read in a series of key frames from a disk file. Defining key frames by hand takes a lot of trial and error, so it is very desirable to be able to quickly modify the key frame file and rerun the application rather than having to recompile.

A utility method was written to read the data for a series of key frames from a disk file. The format of the file is as follows:

  1. Alpha time
  2. Position x,y,z
  3. Rotation x,y,z
  4. Scale x,y,z
  5. Tension (–1 to 1)
  6. Continuity (–1 to 1)
  7. Bias (–1 to 1)
  8. Linear Interpolation (0 or 1)

For example, the spline curve that the viewer is interpolated along is defined as follows, from rotate_viewer_spline.xls:

0.0
5 6 5
-0.4 0 0
1 1 1
0 1 0
0

0.3
2 4 10
1.0  0.2 0
1 1 1
0 0 0
0

0.5
-2 4 8
-0.3 0.6 0.2
1 1 1
-1 1 -1
0

0.7
-2 5 10
-0.4 -0.6 0.5
1 1 1
-1 1 -1
0

0.8
-1 4 5
-0.6 -0.9 -0.2
1 1 1
1 1 1
0

0.9
0 10 15
-1.2 0 0
1 1 1
1 0 1
0

1.0
0 52 0
-1.5 0 0
1 1 1
0 1 1
0

The utility method to read and create the key frame array is simply the following:

From Utils.java in the org.selman.java3d.book package

static public TCBKeyFrame[] readKeyFrames( URL urlKeyframes )
{
 StringBuffer szBufferData = readFile( urlKeyframes );
 
 if( szBufferData == null )
  return null;
  
 Vector keyFramesVector = new Vector();
 
 //create a tokenizer to tokenize the input file at whitespace
 java.util.StringTokenizer tokenizer =
   new java.util.StringTokenizer( szBufferData.toString() );
   
 /*
  * Each keyframe is defined as follows:
  * - knot (0 >= k <= 1)
  * - position (x,y,z)
  * - rotation (rx,ry,rz)
  * - scale (x,y,z)
  * - tension (-1 >= t <= 1)
  * - continuity (-1 >= c <= 1)
  * - bias (-1 >= b <= 1)
  * - linear (int - 0 or 1)
  */
 while( true )
 {
  try
  {
   float knot = Float.parseFloat( tokenizer.nextToken() );
   
   float posX = Float.parseFloat( tokenizer.nextToken() );
   float posY = Float.parseFloat( tokenizer.nextToken() );
   float posZ = Float.parseFloat( tokenizer.nextToken() );
   
   float rotX = Float.parseFloat( tokenizer.nextToken() );
   float rotY = Float.parseFloat( tokenizer.nextToken() );
   float rotZ = Float.parseFloat( tokenizer.nextToken() );
   
   float scaleX = Float.parseFloat( tokenizer.nextToken() );
   float scaleY = Float.parseFloat( tokenizer.nextToken() );
   float scaleZ = Float.parseFloat( tokenizer.nextToken() );
   
   float tension = Float.parseFloat( tokenizer.nextToken() );
   float continuity = Float.parseFloat( tokenizer.nextToken() );
   float bias = Float.parseFloat( tokenizer.nextToken() );
   
   int linear = Integer.parseInt( tokenizer.nextToken() );
   
   //create the actual keyframe from the data just read
   TCBKeyFrame keyframe = new TCBKeyFrame(
    knot,
    linear,
    new Point3f( posX, posY, posZ ),
    createQuaternionFromEuler( rotX, rotY, rotZ ),
    new Point3f( scaleX, scaleY, scaleZ ),
    tension,
    continuity,
    bias );
    
   keyFramesVector.add( keyframe );
  }
  catch( Exception e )
  {
   break;
  }
 }
 
 //create the return structure and populate
 TCBKeyFrame[] keysReturn =
   new TCBKeyFrame[ keyFramesVector.size() ];
   
 for( int n = 0; n < keysReturn.length; n++ )
  keysReturn[n] = (TCBKeyFrame) keyFramesVector.get( n );
  
 //return the array
 return keysReturn;
}

12.4.4 Creating the texture-mapped sky backdrop

To create the background for the example we create a Sphere, assign a texture image to the Sphere and then assign the Sphere to a Background node. Note that because the viewer of the scene is within the Sphere the normal vectors assigned to the vertices of the Sphere must be created with the Primitive.GENERATE_NORMALS_INWARD option.

From SplineInterpolatorTest.java

//we want a texture-mapped background of a sky
protected Background createBackground()
{
 //add the sky backdrop
 Background back = new Background();
 back.setApplicationBounds( getApplicationBounds() );
 
 BranchGroup bgGeometry = new BranchGroup();
 
 //create an appearance and assign the texture image
 Appearance app = new Appearance();
 Texture tex = new TextureLoader( "sky.gif", this).getTexture();
 app.setTexture( tex );
 
 Sphere sphere =
  new Sphere( 1.0f, Primitive.GENERATE_TEXTURE_COORDS |  
                    Primitive.GENERATE_NORMALS_INWARD,
              app );
              
 bgGeometry.addChild( sphere );
 back.setGeometry( bgGeometry );
 
 return back;
}

12.4.5 Controlling the extent of the audio for the helicopters

The distance over which the PointSounds created for the three helicopters can be heard is controlled by three factors: (1) the ViewPlatform’s activation radius, (2) the scheduling bounds of the Sound, and (3) distance/gain parameters of the PointSound itself.

A PointSound is potentially audible when the ViewPlatform’s activation radius intersects the scheduling bounds for the Sound. The actual volume of the mixed sound is controlled by the distance/gain parameters for the PointSound.

From Helicopter.java

//Return the scheduling bounds for the Helicopter’s sound
protected Bounds getSoundSchedulingBounds( boolean bCollide )
{
 return new BoundingSphere( new Point3d(0,0,0), 20 );
}

The getSoundDistanceGain method returns an array of Point2f objects that define how the volume of the sound attenuates with distance. In the following example the sound is at 20 percent of its maximum intensity at 2 units distance and at 5 percent of maximum intensity at 20 units distance.

protected Point2f[] getSoundDistanceGain( boolean bCollide )
{
 Point2f[] gainArray = new Point2f[2];
 
 gainArray[0] = new Point2f( 2, 0.2f );
 gainArray[1] = new Point2f( 20, 0.05f );
 
 return gainArray;
}

12.5 Summary

This discussion of Interpolators and the SplineInterpolatorTest example should have alerted you to some of the power of the interpolator behaviors built into Java 3D. The support for such high-level features as interpolators in Java 3D is a real time saver. Just think about having to implement this functionality by hand.

[previous]  |  [main]  |  [next]