September 15, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Understanding the Alpha Time-Base Class in Java 3D

  • November 6, 2007
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1546


Preface

General

Fourth in a series of lessons

This is the fourth lesson in a series of lessons designed to start with Java 3D basics and work up to the general complexity of the program that I explained in the earlier lesson titled "Understanding Lighting in the Java 3D API" (see Resources).

The first lesson in this series was titled "Back to Basics in the Java 3D API" (see Resources). The previous lesson was titled "Simple Animation with the Java 3D API" This lesson is titled "Understanding the Alpha Time-Base Class in Java 3D" My current plan is for future lessons to deal with user and object interaction as well as advanced animation and textures.

What you will learn

Understanding the Alpha class is critical to understanding Java 3D animation. The constructors for the Alpha class can require up to ten parameters. Understanding the purpose of those parameters and the behavior imparted by those parameters is far from trivial. In this lesson, I will teach you about the detailed behavior of objects instantiated from the Alpha class, with particular emphasis on understanding the behavior imparted by each of the ten parameters. In many cases, I will provide practical suggestions as to an animation scenario in which a particular parameter value might be appropriate.

Compiling and running Java 3D programs

In order to compile and run programs using the Java 3D API, you will need to download and install the Java 3D API software. As of the date of this writing, version 1.5.0 is available for download.

In addition, you will need to download and install either Microsoft DirectX or OpenGL. All of the sample programs in this series of tutorials were developed and tested using Microsoft DirectX. They were not tested using OpenGL.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

  • Figure 1. Bouvier's recipe for animation in Java 3D.
  • Figure 2. Parameter names and default values for Alpha constructor.
  • Figure 3. Time functions produced by six different Alpha objects.

Listings

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find a consolidated index at www.DickBaldwin.com.

General background information

Dennis Bouvier (see Resources) gives us the recipe shown in Figure 1 for creating animation in Java 3D through the use an Interpolator object. This lesson will concentrate on developing an understanding of step 2 in Figure 1 dealing with objects of the Alpha class.

Figure 1. Bouvier's recipe for animation in Java 3D.

  1. Create a target TransformGroup
    • Set the ALLOW_TRANSFORM_WRITE capability
  2. Create an Alpha object
    • Specify the time parameters for the alpha
  3. Create the interpolator object
    • Have it reference the Alpha and TransformGroup objects
    • Customize the behavior parameters
  4. Specify a scheduling region
    • Set the scheduling region for the behavior
  5. Make the behavior a child of the TransformGroup

As you can see from Figure 1, being able to create the Alpha object is critical to the creation of the overall animation.

Constructors

The Alpha class has four overloaded constructors. The most complex of those four constructors requires ten parameters. The only differences among the constructors are that some of the constructors use default values for some of the ten required parameters. This results in three of the four constructors having fewer parameters. Regardless of which constructor you elect to use, you need to understand the behavior imparted by the values provided by all ten parameters even if some of those parameters take on default values.

The parameter names and default values

Although this is not likely to mean much to you at this point, Figure 2 shows the names of all ten parameters along with their default values. These names are based on the Sun documentation for the Alpha class.

Figure 2. Parameter names and default values for Alpha constructor.

loopCount :                     -1
mode :                           INCREASING_ENABLE
triggerTime :                    0
phaseDelayDuration :             0
increasingAlphaDuration :     1000
increasingAlphaRampDuration :    0
alphaAtOneDuration :             0
decreasingAlphaDuration :        0
decreasingAlphaRampDuration :    0
alphaAtZeroDuration :            0

By the end of this lesson, you should understand the purpose of each of the parameters listed in Figure 2.

Preview

In this lesson, I will present and explain a program named Java3D007, which instantiates six different Alpha objects using different combinations of parameter values, and then illustrates the behavior of each Alpha object using the plots shown in Figure 3.

Figure 3. Time functions produced by six different Alpha objects.

What is an Alpha object?

Before getting ahead of ourselves, we should probably back up and start at the beginning. According to Sun,

"The alpha NodeComponent object provides common methods for converting a time value into an alpha value (a value in the range 0 to 1). The Alpha object is effectively a function of time that generates alpha values in the range [0,1] when sampled: f(t) = [0,1]. A primary use of the Alpha object is to provide alpha values for Interpolator behaviors. The function f(t) and the characteristics of the Alpha object are determined by user-definable parameters"

Stated more simply, an Alpha object provides a time base that can be used by an Interpolator object in the process of producing the intermediate views of the universe that are needed during an animation of that universe.

Four overloaded constructors

There are four different overloaded constructors for the Alpha class, with the most complex constructor requiring the following ten parameters:

  1. loopCount - number of times to run this alpha; a value of -1 specifies that the alpha loops indefinitely.
  2. mode - indicates whether the increasing alpha parameters or the decreasing alpha parameters or both are active.
  3. triggerTime - time in milliseconds since the start time that this object first triggers
  4. phaseDelayDuration - number of milliseconds to wait after triggerTime before actually starting this alpha
  5. increasingAlphaDuration - period of time during which alpha goes from zero to one
  6. increasingAlphaRampDuration - period of time during which the alpha step size increases at the beginning of the increasingAlphaDuration and, correspondingly, decreases at the end of the increasingAlphaDuration. This value is clamped to half of increasingAlphaDuration. NOTE: a value of zero means that the alpha step size remains constant during the entire increasingAlphaDuration.
  7. alphaAtOneDuration - period of time that alpha stays at one
  8. decreasingAlphaDuration - period of time during which alpha goes from one to zero
  9. decreasingAlphaRampDuration - period of time during which the alpha step size increases at the beginning of the decreasingAlphaDuration and, correspondingly, decreases at the end of the decreasingAlphaDuration. This value is clamped to half of decreasingAlphaDuration. NOTE: a value of zero means that the alpha step size remains constant during the entire decreasingAlphaDuration.
  10. alphaAtZeroDuration - period of time that alpha stays at zero

Some names and descriptions don't quite do the job

Some of you may not need any more information than that given above to fully understand the behavior imparted by each of these ten parameters. For me, however, some of the names and descriptions given above didn't quite do the job. I found it necessary to do some experimentation with the parameter values to understand their behavior. The purpose of this lesson is to share the results of that experimentation with you, and also to provide a program that you can easily modify to do your own experimentation.

Discussion and sample code

A complete listing of the program named Java3D007 is provided in Listing 15. The purpose of this program is to investigate and illustrate the behavior of the Java 3D class named Alpha as a function of the values passed as parameters when an Alpha object is instantiated. The class named Alpha is a programmable time-base program that is used mainly to control animations, but could be used for any purpose that requires a programmable time base.

The alpha value ranges from 0.0 to 1.0

An Alpha object generates and provides an alpha value ranging from 0.0 to 1.0 over a specified elapsed cycle time.; The shape of the time function described by the alpha value is controlled by parameters that are passed to the constructor when the object is instantiated.

Number of cycles is an input parameter

The number of repetitive cycles of the alpha value that will be produced is an input parameter (loopCount in Figure 2). A value of -1 will cause the object to continue producing repetitive alpha values until it is purposely terminated.

A set of sample values

An Alpha object has many methods, one if which is named value. This method can be called to sample the alpha value at any point in time. By using a clock and sampling the alpha value at a set of equally spaced points in time, a set of sample values can be obtained from the Alpha object that describe the alpha time function.

Constructors

The Alpha class contains four overloaded constructors. The most complex constructor requires ten parameters. The only differences among the constructors are that some of the constructors use default values for some of the parameters. This results in three of the four constructors having fewer than ten required parameters.

Samples from six Alpha objects

This program constructs six different Alpha objects, each having different parameter values. All six constructors run for two cycles with a duration of five seconds per cycle. Thus, each Alpha object generates alpha values during a time span of ten seconds (10000 milliseconds).

A timer is used to collect 100 samples from each Alpha object spaced at uniform intervals of 100 milliseconds during the total elapsed time of ten seconds.

As a quality check, the system clock is also used to get and print the total elapsed time during which the Alpha objects are generating data.

Plots of the time functions

Each set of 100 samples is plotted as shown in Figure 3. The plot data is scaled so that a value of 1.0 produced by an Alpha object is plotted as a value of 100 in Figure 3.

The plotting software

The plotting software used in the program is a simplified version of the program named Graph01 that I published and explained earlier in the tutorial titled "Plotting Engineering and Scientific Data using Java" (see Resources). Because I explained that plotting software in the earlier lesson, I won't repeat that explanation here, but will simply refer you to the earlier lesson.

Program testing

This program was tested using Java SE 6, and Java 3D 1.5.0 running under Windows XP.

Will explain in fragments

As mentioned earlier, a complete listing of the program is presented in Listing 15 near the end of the lesson. As is my custom for long programs, I will break the program down and explain it in fragments.

The program begins in the fragment shown in Listing 7.

Listing 1. Beginning of the program named Java3D007.

class Java3D007{

  Alpha alpha1Obj;
  Alpha alpha2Obj;
  Alpha alpha3Obj;
  Alpha alpha4Obj;
  Alpha alpha5Obj;
  Alpha alpha6Obj;

  float[] alpha1Data = new float[100];
  float[] alpha2Data = new float[100];
  float[] alpha3Data = new float[100];
  float[] alpha4Data = new float[100];
  float[] alpha5Data = new float[100];
  float[] alpha6Data = new float[100];

Reference variables for the Alpha objects and data

As mentioned earlier, this program is designed to instantiate and run six Alpha objects with different combinations of parameters being passed to the constructors, and to plot the time series produced by those Alpha objects.

Listing 1 declares reference variables that will point to each of the Alpha objects. Listing 1 also creates six array objects, each having a length of 100 elements. The sampled alpha values will be stored in these array objects as they are being produced. After all six arrays have been populated, the data in those arrays will be plotted as shown in Figure 3.

Three additional instance variables

Listing 2 declares and initializes three additional instance variables that are used later in the program. The purpose of these variables will be explained later.

Listing 2. Three additional instance variables.

  //Initialize the sampleTime counter to the time that the
  // first sample will be taken.
  int sampleTime = 100;
  //Used to compute the actual elapsed time from the
  // system clock.
  long baseTime;
  //Timer used to trigger the collection of samples of the
  // alpha values.
  Timer samplingTimer;

The main method

The main method is shown in Listing 3. The sole purpose of this method is to instantiate an object of the class.

Listing 3. The main method.

  public static void main(String[] args){
    new Java3D007();
  }//end main

Beginning of the constructor

The code for the constructor begins in Listing 4. Note that the first thing that happens in the constructor is the instantiation of a Timer object, which creates and passes an anonymous inner class as the second parameter to the constructor the Timer class.

Listing 4. Beginning of the constructor.

  Java3D007(){//constructor 
    samplingTimer = new Timer(100,
      new ActionListener(){
        public void actionPerformed(ActionEvent e){

Not trivial code

This in not trivial code, particularly if you are unfamiliar with the use of anonymous inner classes in Java. The documentation for J2SE 5 shows threeTimer classes. This timer is constructed from the Timer class in the javax.swing package (see the import directives in Listing 15).

I'm not going to explain the underlying technology behind the use of anonymous inner classes in Java. I have previously published many tutorial lessons that explain those concepts. Furthermore, I used and discussed a timer very similar to this one in an earlier lesson titled "Using the Full-Screen Exclusive Mode API in Java" (see Resources). I will simply refer you back to those earlier lessons for a detailed understanding of this code.

Briefly, however, this code will create a javax.swing.Timer object which, once started, will fire an Action event once each 100 milliseconds. The code in the overridden actionPerformed method that begins in Listing 4 will get and save the current alpha value from each of the six Alpha objects each time the timer fires an event.

Time to quit sampling

The overridden actionPerformed method begins with the if-clause of an if-else construct in Listing 5.

Listing 5. Time to quit sampling.

          if(sampleTime >= 10000){
            System.out.println("Elapsed time = " + 
                       (new Date().getTime() - baseTime));

            samplingTimer.stop();
            
            new Plotter();

Behavior of the if-else construct

An alternative approach
In retrospect, it may have been better to use the finished method belonging to one or more of the Alpha objects to determine when to quit taking samples.

The first clause in the if-else construct tests to determine if 10000 milliseconds have passed since the six Alpha objects were started, based on the value of the sampleTime variable that was declared and initialized in Listing 2, and is updated by 100 milliseconds each time a sample is taken. If true, three things happen:

  1. Get and display the total elapsed time from the system clock
  2. Cause the timer to stop firing events
  3. Instantiate a new Plotter object to plot the alpha data values that have been saved in the six arrays that were declared in Listing 1.

This code is straightforward

None of the code required to accomplish these three things is complicated, so no further explanation should be required.

The Plotter object is instantiated from an inner class named Plotter. As I explained earlier in this lesson, the code in that class is a simplified version of code that I explained in an earlier lesson (see Resources), so I won't repeat that explanation here.

The else clause

The else clause in the if-else structure in the overridden actionPerformed method is shown in its entirety in Listing 6.

Listing 6. The else clause.

          }else{
            alpha1Data[sampleTime/100] = 
                                        alpha1Obj.value();
            alpha2Data[sampleTime/100] = 
                                        alpha2Obj.value();
            alpha3Data[sampleTime/100] = 
                                        alpha3Obj.value();
            alpha4Data[sampleTime/100] = 
                                        alpha4Obj.value();
            alpha5Data[sampleTime/100] = 
                                        alpha5Obj.value();
            alpha6Data[sampleTime/100] = 
                                        alpha6Obj.value();
          }//end else

Getting six sample values

This code calls the method named value on each of the six Alpha objects to get and save a sample from each Alpha object. Here is part of what Sun has to say about the value method:

"This method returns a value between 0.0 and 1.0 inclusive, based on the current time and the time-to-alpha parameters established for this alpha. If this alpha object is paused, the value will be based on the pause time rather than the current time. This method will return the starting alpha value if the alpha has not yet started (that is, if the current time is less than startTime + triggerTime + phaseDelayDuration). This method will return the ending alpha value if the alpha has finished (that is, if the loop count has expired)."

I don't know how to elaborate on that other than to say that calling the value method on an Alpha object once at the end of each 100 millisecond interval will produce a set of sampled values that describe the shape of the time function produced by the Alpha object. That is probably what an Interpolator object does when it uses an Alpha object to provide the necessary time-base information for generating the intermediate states of a universe that are required to produce an animated universe.

End of anonymous inner class definition

Listing 7 shows the end of the overridden actionPerformed method as well as the end of the definition of the anonymous inner class.

Listing 7. End of anonymous inner class definition.

          sampleTime += 100;
        
        }//end actionPerformed
      }//end new ActionListener
    );//end new javax.swing.Timer

The overridden actionPerformed method contains one additional statement in Listing 7, which updates the sampleTime variable used in the conditional clause in Listing 5. This prepares the program for collecting samples the next time the timer fires an action event.

Instantiate the first Alpha object

This is where things get interesting. The code in Listing 8 begins the process of instantiating six new Alpha objects using different constructors and parameter lists. The Alpha objects are used to populate the arrays of alpha data under control of the timer discussed above.

Listing 8. Instantiate the first Alpha object.

    alpha1Obj = new Alpha(2,    //cycles
                          5000);//cycle duration up

Only two parameters required

The code in Listing 8 instantiates a new Alpha object using a constructor that requires only two parameters and uses default values for the other eight required parameters. The time function produced by this Alpha object is shown as the very top plot in Figure 3.

Two cycles at 5000 milliseconds each

Listing 8 specifies that when this Alpha object is started, it should produce two cycles of its time function with each cycle requiring a duration of 5000 milliseconds.

The two parameters for which values are passed to this constructor are named loopCount and increasingAlphaDuration in Figure 2. (I used somewhat shorter names in the comments in Listing 8.) The default values for the remaining eight parameters shown in Figure 2 were used in constructing this object. (I recommend that you click on the two links above and read the descriptions. This should help you to connect behavior with parameter names.)

When would this Alpha object be appropriate?

Many different types of animations can be produced by using Interpolator subclasses in conjunction with Alpha objects in Java 3D. For purposes of this discussion, let's consider the top time function in Figure 3 to represent the movement of a visual object in a Java 3D universe. For example, that visual object might be the big hand on a clock, a dragster on a drag strip, or a moon orbiting a planet.

The big hand on a clock

The time function at the top of Figure 3 might be ideal for animating the big hand on a clock. To begin with, the hands on clocks have very little mass and from a practical viewpoint appear to start and stop abruptly. Furthermore, when they are moving, they move at a constant angular velocity.

This Alpha object could be used to control the angular position of the big hand on the clock, with each cycle of the saw tooth in Figure 3 representing one complete rotation of the clock's big hand. By passing a value of -1 to the constructor in Listing 8, the Alpha object would continue producing the same saw tooth waveform indefinitely.

The position of a moon

While not quite as ideal, this Alpha object could also be used to control the position of a moon in a circular orbit around a planet. In fact this is the form of Alpha object that was used to control the rotation of the white sphere around the yellow sphere in the previous lesson titled "Simple Animation with the Java 3D API" (see Resources). For the case of moons and planets, it is often assumed that the moon is already orbiting the planet when the animation begins and will continue to do so indefinitely. Thus, none of the startup transients involved in getting the moon into orbit initially would typically come into play.

Do dragsters start and stop abruptly? They don't start abruptly. Unfortunately, they may stop rather abruptly when they run into an immovable obstacle.

What about a dragster?

This would not be an appropriate Alpha object for controlling the animation of a dragster. Cars don't start moving abruptly and move with constant velocity until they suddenly stop abruptly. Rather, when the driver presses the accelerator pedal, the dragster starts moving, slowly at first and gaining velocity as time elapses. That is not the sort of time function represented by the top plot in Figure 3. When used to animate motion, the top plot in Figure 3 would represent something that starts and stops abruptly and moves at a constant velocity while it is moving.

A delayed saw tooth time function

The Alpha object constructed in Listing 9 produces the delayed saw tooth time function shown in the second plot down from the top in Figure 3. (This constructor requires more parameters, but I passed the default values of zero to all but three of them.)

Listing 9. A delayed saw tooth time function.

    alpha2Obj = new Alpha(2,   //cycles
                          1000,//trigger time
                          0,   //phase delay
                          5000,//cycle duration up
                          0,   //ramp duration for up
                          0);  //duration at one

The triggerTime parameter

Note that for this object, the time function in Figure 3 remains at a value of zero for the first 1000 milliseconds after startup, at which time it starts to behave just like the object that produced the top plot in Figure 3. This resulted from specifying a value of 1000 for the second parameter in Listing 9.

This parameter is named triggerTime in the earlier description of the parameters. If you go back and read that description, the reason for this behavior will probably make sense to you.

Frankly, I have scratched my head and haven't come up with any practical examples for the use of a time-delayed time function of this sort. However, I'm sure that there are some or Sun wouldn't have included this capability in Java 3D.

Even more delay

The Alpha object instantiated in Listing 10 produced the third plot down from the top in Figure 3. This was accomplished by passing a value of 500 as the third parameter in the constructor.

Listing 10. Even more delay.

    alpha3Obj = new Alpha(2,   //cycles
                          1000,//trigger time
                          500, //phase delay
                          5000,//cycle duration up
                          0,   //ramp duration for up
                          0);  //duration at one

The phaseDelayDuration parameter

This parameter is named phaseDelayDuration, and it provides a mechanism for inserting an additional delay into the start of the time function following the triggerTime delay.

This behavior is evident in the third plot in Figure 3, which shows that the function remained at a value of zero for 500 milliseconds in addition to the 1000 millisecond delay produced by the triggerTime delay.

Once again, since I was unable to come up with a practical example for the use of a single delay, I was also unable to come up with a practical example for two sequential delays.

Going from 1.0 to 0.0

An Alpha object not only has the ability to produce a time function that increases from 0.0 to 1.0 in a specified manner, it also has the ability to produce a time function that decreases from 1.0 to 0.0 in a specified manner. As you will see later, it also has the ability to produce a time function that is the combination of the two.

The Alpha object that resulted from calling the constructor shown in Listing 11 produced the time function shown in the fourth plot from the top in Figure 3.

Listing 11. Going from 1.0 to 0.0

    alpha4Obj = new Alpha(2,   //cycles
                          Alpha.DECREASING_ENABLE,//mode
                          0,   //trigger time
                          0,   //phase delay
                          0,   //cycle duration up
                          0,   //ramp duration for up
                          0,   //duration at one
                          5000,//cycle duration down
                          0,   //ramp duration for down
                          0);  //duration at zero

Using a value of Alpha.DECREASING_ENABLE for mode

In this case, it was necessary to use the most complex constructor requiring the specification of all ten parameters. Note however, that with one exception, I provided parameter values that matched the default values used by the constructor in Listing 8. Thus, as a practical matter, the only difference between Listing 11 and Listing 8 was the specification of the value Alpha.DECREASING_ENABLE for the parameter named mode in Listing 11.

Two allowable values

There are only two allowable values for this parameter:

  • Alpha.INCREASING_ENABLE
  • Alpha.DECREASING_ENABLE

The first of the two values in the above list is the default value as shown in Figure 2.

Behavior imparted by the two values for the mode parameter

The Alpha.INCREASING_ENABLE value results in an Alpha object whose time function increases from 0.0 to 1.0.

The Alpha.DECREASING_ENABLE value results on an Alpha object whose time function decreases from 1.0 to 0.0.

Many variations are possible
Note that it is possible to use either or a combination of these two values for mode with many different combinations of the other nine required parameters.

As you will see later, it is also possible to perform a bitwise OR on the two values producing an Alpha object whose time function first increases from 0.0 to 1.0 and then decreases from 1.0 back down to 0.0.

Compare the two plots

If you compare the top plot in Figure 3, which was produced using the default Alpha.INCREASING_ENABLE value for the mode parameter with the fourth plot, which was produced using an explicit Alpha.DECREASING_ENABLE value for the mode parameter, you will see the results of an increasing or a decreasing time function.

A possible usage scenario

It might be useful to combine one cycle of the time function in the fourth plot with one cycle of the time function in the top plot to cause a visual object to move from point A to point B at a constant velocity and then return from point B to point A at the same, or perhaps a different constant velocity. However, as you will see later, there is an easier way to accomplish that behavior by performing a bitwise OR on the two allowable values.

Another aspect of the mode parameter

Another aspect of the mode parameter is that by passing a value of Alpha.INCREASING_ENABLE, you will enable the parameters in the following list and disable the parameters in the list titled Decreasing Alpha parameters.

Passing a parameter value of Alpha.DECREASING_ENABLE will enable the parameters in the following list and disable the parameters in the above list titled Increasing Alpha parameters.

A mode for moving objects with significant mass

Now let's go back to the dragster scenario. The code in Listing 12 produced the fifth plot down from the top in Figure 3. A single cycle of this time function might be appropriate for animating the motion of a dragster.

Listing 12. A mode for moving objects with significant mass.

    alpha5Obj = new Alpha(2,   //cycles
                          0,   //trigger time
                          0,   //phase delay
                          5000,//cycle duration up
                          2000,//ramp duration for up
                          0);  //duration at one

The parameter named increasingAlphaRampDuration

The significant thing in Listing 12 is the specification of 2000 milliseconds for the parameter named increasingAlphaRampDuration. If we apply this time function to our idealized dragster, the driver presses the accelerator pedal to the floor at the extreme left end of the fifth plot in Figure 3. The dragster starts moving, slowly at first with the velocity increasing over time. This curve describes constant positive acceleration and increasing velocity for the first 2000 milliseconds (out to the second tick mark on the horizontal axis).

The dragster won't go any faster

At that point, the dragster has achieved its maximum velocity and moves at a constant velocity for the next 1000 milliseconds (out to the third tick mark). (For those readers with an engineering or scientific bent, the velocity is indicated by the slope of the time function. The slope of the function continues to increase out to the second tick mark and is constant between the second and third tick marks.)

Time to slow down and stop

At the third tick mark, the dragster crosses the finish line and the driver applies the brakes, deploys the parachute, or both. From that point to the fifth tick mark, the dragster is slowing down with a constant negative acceleration. (The slope of the function is decreasing with time.) Finally, the velocity of the dragster goes to zero at the fifth tick mark (5000 milliseconds or the time for one cycle) where the slope of the function is flat with a slope of zero.

The dragster has covered quite a lot of ground

At this point, the dragster has moved a considerable distance down the drag strip so the value of the function, which began at 0, is now at 100. (The sample values from the Alpha object were all multiplied by 100 for plotting in Figure 3. Therefore, a plotted value of 100 equates to an alpha value of 1.0.)

What if you change the value of the mode parameter?

The constructor that was used in Listing 12 used a default value of Alpha.INCREASING_ENABLE for the mode parameter. If we were to rewrite this code using the constructor that allows us to specify the mode value, and were to specify a value of Alpha.DECREASING_ENABLE, the shape of the curve would be flipped upside down going from its maximum value at zero time to a value of zero at 5000 milliseconds.

Combining the mode values with a bitwise OR

That brings us to the last, and most complicated of the six Alpha objects as shown in Listing 13.

Listing 13. Combining the mode values with a bitwise OR.

    alpha6Obj = new Alpha(2,   //cycles
                          Alpha.INCREASING_ENABLE //mode
                                | Alpha.DECREASING_ENABLE,
                          0,   //trigger time
                          0,   //phase delay
                          2500,//cycle duration up
                          1000,//ramp duration for up
                          0,   //duration at one
                          2500,//cycle duration down
                          1000,//ramp duration for down
                          0);  //duration at zero

First an increase and then a decrease

In this case, the value passed to the mode parameter was the bitwise OR of the two allowable mode values. Basically this causes the function to increase from 0.0 to 1.0 according to the values provided for the three Increasing Alpha parameters listed above and then to decrease from 1.0 to 0.0 according to the three Decreasing Alpha parameters listed above.

Thus, a complete cycle includes an increasing component followed by a decreasing component. In this case, I wanted the total cycle time to remain at 5000 milliseconds, so I set the duration for each of the increasing and decreasing portions to 2500 milliseconds. I also provided the ramp duration values shown in Listing 13 for both the increasing and decreasing portions.

The code in Listing 13 resulted in an Alpha object that produced the time function shown in the bottom plot in Figure 3. (Note that Figure 3 shows two complete cycles for every plot including the bottom one.)

A pendulum

The function shown in the bottom plot in Figure 3 might be appropriate for animating a pendulum. Assume that you manually pull the pendulum to an extreme position on the left side and hold it there. That position would be represented by the zero value of the function at the extreme left of the plot.

Now assume that you turn the pendulum loose and allow it to swing. One complete cycle of the pendulum from an extreme position on the left side to the extreme position on the right side and back to the extreme position on the left side could be represented by one cycle of the function in the bottom plot of Figure 3. The extreme position on the right side would be represented by the maximum value of the function.

A more accurate physical model
If I remember my physics correctly, to more closely model the physics of a pendulum, I probably should have set the two ramp values to 1250 milliseconds instead of 1000 milliseconds.

The maximum positive and negative velocities would occur on the two sides of the cycle where the magnitude of the slope of the function is at its maximum. Physically, this would represent the velocity of the pendulum as it goes through the bottom of its swing in each direction.

The end of the constructor

That is the end of the discussion of the instantiation of the six Alpha objects.

The first statement in Listing 14 gets and saves the current time from the system clock to be used later to compute the total elapsed time for the Alpha objects to generate two complete cycles of data.

Listing 14. The end of the constructor.

    baseTime = new Date().getTime();
    samplingTimer.start();

  }//end constructor

The last statement in Listing 14 starts the timer running and gets the acquisition of the alpha values from the six Alpha objects underway.

Listing 14 signals the end of the constructor and almost signals the end of the discussion. However, there are a few more things that I need to mention before the discussion ends.

Two more parameters

There are two parameters in the list of ten parameters that are not illustrated by this program:

  • alphaAtOneDuration
  • alphaAtZeroDuration

The behavior of these two parameters is straightforward. A non-zero value for either parameter causes the alpha value to remain at either the value of 1.0 or the value of 0.0 for the specified amount of time at the end of a cycle before it begins the next cycle.

Numerous methods

An Alpha object provides numerous methods. The list of methods includes setter and getter methods for all ten of the parameters in the list of ten parameters. This makes it possible to set and get the values for those parameters for an existing Alpha object.

There are also methods for causing an Alpha object to start running at a specified time according to the system clock and for causing an Alpha object to pause.

A wide variety of time functions is available

Hopefully by now you have recognized that by combining different values for the ten constructor parameters for the Alpha class, you can generate quite a variety of time functions to control your animations in Java 3D.

Run the program

I encourage you to copy the code from Listing 15 into your text editor, compile it, and execute it.  Experiment with it, making changes, and observing the results of your changes.

Remember, you will need to download and install the Java 3D API plus either Microsoft DirectX or OpenGL to compile and execute these programs. See Downloads for links to the web sites from which this material can be downloaded.

Summary

Understanding the Alpha class is critical to understanding Java 3D animation. The constructors for the Alpha class can require up to ten parameters. Understanding the purpose of those parameters and the behavior imparted by those parameters is far from trivial. In this lesson, I taught you about the detailed behavior of objects instantiated from the Alpha class, with particular emphasis on understanding the behavior imparted by each of the ten parameters. In many cases, I provided practical suggestions as to an animation scenario in which a particular parameter value might be appropriate.

What's next?

The topics for future lessons include interactive Java 3D programs, advanced animation, and surfaces. I plan to deal with interaction in the next lesson.

Download

Resources

Complete program listing

A complete listing of the program discussed in this lesson is presented in Listing 15 below.

Listing 15. Source code for the program named Java3D007.

/* File Java3D007.java
Copyright 2007, R.G.Baldwin

The purpose of this program is to investigate and
illustrate the behavior of the Java 3D class named Alpha
as a function of the values passed as parameters when
an Alpha object is instantiated.

The class named Alpha is a programmable time-base program
that is used mainly to control animations, but could be
used for any purpose that requires a programmable time
base.

The object generates and provides an alpha value ranging
from 0.0 to 1.0 over a specified elapsed cycle time.  The
shape of the time function described by the alpha
value is controlled by parameters that are passed to the
constructor when the object is instantiated.  The number
of repetitive cycles of the alpha value that will be
produced is an input parameter.  A value of -1 will cause
the object to continue producing repetitive alpha values
until it is purposely terminated.

The object has a method named value(), which can be used
to sample the alpha value at any point in time.
By using a clock and sampling the alpha value at a set of
equally spaced points in time, a set of sample values can
be obtained that describe the alpha time function.

The Alpha class contains four overloaded constructors. The
most complex constructor requires ten parameters.  The
only differences among the constructors is that some of
the constructors default some of the ten required
parameters, resulting in constructors having different
numbers of parameters.

This program constructs six different Alpha objects, each
having different parameter values.  All six constructors
run for two cycles with a duration of five seconds per
cycle.  Thus, each Alpha object generates alpha values
during a time span of ten seconds (10,000 milliseconds)

A timer is used to collect 100 samples from each Alpha
object spaced at uniform intervals of 100 milliseconds
during the total elapsed time of ten seconds.

As a quality check, the system clock is used to get and
print the total elapsed time during which the Alpha
objects are generating data.

Each set of 100 samples is plotted in a format that
displays the six plots in a vertical stack, one plot above
the other.  The plot data is scaled so that a value of 1.0
produced by an Alpha object is plotted as a value of 100 
on the plot.

The plotting software is a simplified version of the
program named Graph01 that was published earlier in a
tutorial lesson number 1468 titled  "Plotting Engineering
and Scientific Data using Java."


Tested using Java SE 6, and Java 3D 1.5.0 running under
Windows XP.
*********************************************************/
import javax.media.j3d.Alpha;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Frame;
import java.util.Date;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;

class Java3D007{

  Alpha alpha1Obj;
  Alpha alpha2Obj;
  Alpha alpha3Obj;
  Alpha alpha4Obj;
  Alpha alpha5Obj;
  Alpha alpha6Obj;

  float[] alpha1Data = new float[100];
  float[] alpha2Data = new float[100];
  float[] alpha3Data = new float[100];
  float[] alpha4Data = new float[100];
  float[] alpha5Data = new float[100];
  float[] alpha6Data = new float[100];

  //Initialize the sampleTime counter to the time that the
  // first sample will be taken.
  int sampleTime = 100;
  //Used to compute the actual elapsed time from the
  // system clock.
  long baseTime;
  //Timer used to trigger the collection of samples of the
  // alpha values.
  Timer samplingTimer;

  public static void main(String[] args){
    new Java3D007();
  }//end main
  //----------------------------------------------------//

  Java3D007(){//constructor

    //Create a timer that will fire once each 100
    // milliseconds.  Get and save the current alpha value
    // from each Alpha object each time the timer fires, 
    samplingTimer = new Timer(100,
      new ActionListener(){
        public void actionPerformed(ActionEvent e){



          if(sampleTime >= 10000){
            //Time to quit taking samples.  Get and
            // display the actual elapsed time from the
            // system clock.
            System.out.println("Elapsed time = " + 
                       (new Date().getTime() - baseTime));
            //Cause the timer to quit firing events.
            samplingTimer.stop();

            //Instantiate a Plotter object, which will
            // cause the data in the arrays to be plotted.
            new Plotter();
          }else{
            //Get and save a sample from each Alpha
            // object.
            alpha1Data[sampleTime/100] = 
                                        alpha1Obj.value();
            alpha2Data[sampleTime/100] = 
                                        alpha2Obj.value();
            alpha3Data[sampleTime/100] = 
                                        alpha3Obj.value();
            alpha4Data[sampleTime/100] = 
                                        alpha4Obj.value();
            alpha5Data[sampleTime/100] = 
                                        alpha5Obj.value();
            alpha6Data[sampleTime/100] = 
                                        alpha6Obj.value();
          }//end else
          
          //Update the sample time to prepare for 
          // collecting the next sample when the timer
          // fires again.
          sampleTime += 100;

        }//end actionPerformed
      }//end new ActionListener
    );//end new javax.swing.Timer


    //Instantiate new Alpha objects using different
    // constructors and parameter lists. Use them to
    // populate arrays of alpha data.
    alpha1Obj = new Alpha(2,    //cycles
                          5000);//cycle duration up

    alpha2Obj = new Alpha(2,   //cycles
                          1000,//trigger time
                          0,   //phase delay
                          5000,//cycle duration up
                          0,   //ramp duration for up
                          0);  //duration at one

    alpha3Obj = new Alpha(2,   //cycles
                          1000,//trigger time
                          500, //phase delay
                          5000,//cycle duration up
                          0,   //ramp duration for up
                          0);  //duration at one

    alpha4Obj = new Alpha(2,   //cycles
                          Alpha.DECREASING_ENABLE,//mode
                          0,   //trigger time
                          0,   //phase delay
                          0,   //cycle duration up
                          0,   //ramp duration for up
                          0,   //duration at one
                          5000,//cycle duration down
                          0,   //ramp duration for down
                          0);  //duration at zero
 
    alpha5Obj = new Alpha(2,   //cycles
                          0,   //trigger time
                          0,   //phase delay
                          5000,//cycle duration up
                          2000,//ramp duration for up
                          0);  //duration at one

    alpha6Obj = new Alpha(2,   //cycles
                          Alpha.INCREASING_ENABLE //mode
                                | Alpha.DECREASING_ENABLE,
                          0,   //trigger time
                          0,   //phase delay
                          2500,//cycle duration up
                          1000,//ramp duration for up
                          0,   //duration at one
                          2500,//cycle duration down
                          1000,//ramp duration for down
                          0);  //duration at zero


    //Get the start time from the system clock to be used
    // later to compute the actual elapsed time as a
    // quality check.
    baseTime = new Date().getTime();
    samplingTimer.start();

  }//end constructor
  //===================================================//


  //This is an inner plotting class
  class Plotter extends JFrame{

    //Define plotting parameters.
    double xMin = 0.0;
    double xMax = 100.0;
    double yMin = -10.0;
    double yMax = 100.0;

    //Tic mark intervals
    double xTicInt = 10.0;
    double yTicInt = 10.0;

    //Tic mark lengths.
    double xTicLen = (yMax-yMin)/20;
    double yTicLen = (xMax-xMin)/20;

    //Calculation interval along x-axis
    double xCalcInc = 1.0;

    //Misc instance variables
    int frmWidth = 408;
    int frmHeight = 700;
    int width;
    int height;
    int numberPlots = 6;

    //Plots are drawn on the canvases
    // in this array.
    Canvas[] canvases;

    //Constructor
    Plotter(){
      //Create array to hold correct
      // number of Canvas objects.
      canvases = new Canvas[numberPlots];

      //Create a panel to contain the
      // Canvas objects.  They will be
      // displayed in a one-column grid.
      JPanel canvasPanel = new JPanel();
      canvasPanel.setLayout(//?rows,1 col
                    new GridLayout(0,1));

      //Create a custom Canvas object for
      // each function to be plotted and
      // add them to the one-column grid.
      // Make background colors alternate
      // between white and gray.
      for(int cnt = 0;cnt < numberPlots;cnt++){
        switch(cnt){
          case 0 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.WHITE);
            break;
          case 1 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.LIGHT_GRAY);
            break;
          case 2 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.WHITE);
            break;
          case 3 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.LIGHT_GRAY);
            break;
          case 4 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.WHITE);
            break;
          case 5 :
            canvases[cnt] = new MyCanvas(cnt);
            canvases[cnt].setBackground(Color.LIGHT_GRAY);
            break;
        }//end switch
        //Add the object to the grid.
        canvasPanel.add(canvases[cnt]);
      }//end for loop

      //Add the sub-assemblies to the
      // frame.  Set its location, size,
      // and title, and make it visible.
      getContentPane().
               add(canvasPanel,"Center");

      setBounds(0,0,frmWidth,frmHeight);
      setTitle("Copyright 2007, " +
                   "Richard G. Baldwin");
      setVisible(true);

      //Set to exit on X-button click
      setDefaultCloseOperation(
                          EXIT_ON_CLOSE);

      //Get and save the size of the
      // plotting surface
      width = canvases[0].getWidth();
      height = canvases[0].getHeight();

      //Guarantee a repaint on startup.
      for(int cnt = 0;
               cnt < numberPlots; cnt++){
        canvases[cnt].repaint();
      }//end for loop

    }//end constructor
    //---------------------------------//


    //This is an inner class, which is used
    // to override the paint method on the
    // plotting surface.
    class MyCanvas extends Canvas{
      int cnt;//object number
      //Factors to convert from double
      // values to integer pixel locations.
      double xScale;
      double yScale;

      MyCanvas(int cnt){//save obj number
        this.cnt = cnt;
      }//end constructor

      //Override the paint method
      public void paint(Graphics g){
        //Calculate the scale factors
        xScale = width/(xMax-xMin);
        yScale = height/(yMax-yMin);

        //Set the origin based on the
        // minimum values in x and y
        g.translate((int)((0-xMin)*xScale),
                   (int)((0-yMin)*yScale));
        drawAxes(g);//Draw the axes
        g.setColor(Color.BLACK);

        //Get initial data values
        double xVal = xMin;
        int oldX = getTheX(xVal);
        int oldY = 0;
        //Use the Canvas obj number to
        // determine which method to
        // invoke to get the value for y.

        //Now loop and plot the points
        while(xVal < xMax){
          int yVal = 0;
          //Get next data value.  Use the
          // Canvas obj number to
          // determine which array to query
          // to get the value for y

          switch(cnt){
            case 0 :
              yVal =
                getTheY((int)(100*alpha1Data[(int)xVal]));
              break;
            case 1 :
              yVal =
                getTheY((int)(100*alpha2Data[(int)xVal]));
              break;
            case 2 :
              yVal =
                getTheY((int)(100*alpha3Data[(int)xVal]));
              break;
            case 3 :
              yVal =
                getTheY((int)(100*alpha4Data[(int)xVal]));
              break;
            case 4 :
              yVal =
                getTheY((int)(100*alpha5Data[(int)xVal]));
              break;
            case 5 :
              yVal =
                getTheY((int)(100*alpha6Data[(int)xVal]));
          }//end switch1

          //Convert the x-value to an int
          // and draw the next line segment
          int x = getTheX(xVal);
          g.drawLine(oldX,oldY,x,yVal);

          //Increment along the x-axis
          xVal += xCalcInc;

          //Save end point to use as start
          // point for next line segment.
          oldX = x;
          oldY = yVal;
        }//end while loop

      }//end overridden paint method
      //---------------------------------//

      //Method to draw axes with tic marks
      // and labels in the color RED
      void drawAxes(Graphics g){
        g.setColor(Color.RED);

        //Lable left x-axis and bottom
        // y-axis.  These are the easy
        // ones.  Separate the labels from
        // the ends of the tic marks by
        // two pixels.
        g.drawString("" + (int)xMin,
                     getTheX(xMin),
                     getTheY(xTicLen/2)-2);
        g.drawString("" + (int)yMin,
                      getTheX(yTicLen/2)+2,
                            getTheY(yMin));

        //Label the right x-axis and the
        // top y-axis.  These are the hard
        // ones because the position must
        // be adjusted by the font size and
        // the number of characters.
        //Get the width of the string for
        // right end of x-axis and the
        // height of the string for top of
        // y-axis
        //Create a string that is an
        // integer representation of the
        // label for the right end of the
        // x-axis.  Then get a character
        // array that represents the
        // string.
        int xMaxInt = (int)xMax;
        String xMaxStr = "" + xMaxInt;
        char[] array = xMaxStr.
                             toCharArray();

        //Get a FontMetrics object that can
        // be used to get the size of the
        // string in pixels.
        FontMetrics fontMetrics =
                        g.getFontMetrics();
        //Get a bounding rectangle for the
        // string
        Rectangle2D r2d =
               fontMetrics.getStringBounds(
                   array,0,array.length,g);
        //Get the width and the height of
        // the bounding rectangle.  The
        // width is the width of the label
        // at the right end of the
        // x-axis.  The height applies to
        // all the labels, but is needed
        // specifically for the label at
        // the top end of the y-axis.
        int labWidth =
                     (int)(r2d.getWidth());
        int labHeight =
                    (int)(r2d.getHeight());

        //Label the positive x-axis and the
        // positive y-axis using the width
        // and height from above to
        // position the labels.  These
        // labels apply to the very ends of
        // the axes at the edge of the
        // plotting surface.
        g.drawString("" + (int)xMax,
                    getTheX(xMax)-labWidth,
                     getTheY(xTicLen/2)-2);
        g.drawString("" + (int)yMax,
                  getTheX(yTicLen/2)+2,
                  getTheY(yMax)+labHeight);

        //Draw the axes
        g.drawLine(getTheX(xMin),
                             getTheY(0.0),
                             getTheX(xMax),
                             getTheY(0.0));

        g.drawLine(getTheX(0.0),
                            getTheY(yMin),
                            getTheX(0.0),
                            getTheY(yMax));

        //Draw the tic marks on axes
        xTics(g);
        yTics(g);
      }//end drawAxes

      //---------------------------------//

      //Method to draw tic marks on x-axis
      void xTics(Graphics g){
        double xDoub = 0;
        int x = 0;

        //Get the ends of the tic marks.
        int topEnd = getTheY(xTicLen/2);
        int bottomEnd =
                       getTheY(-xTicLen/2);

        //If the vertical size of the
        // plotting area is small, the
        // calculated tic size may be too
        // small.  In that case, set it to
        // 10 pixels.
        if(topEnd < 5){
          topEnd = 5;
          bottomEnd = -5;
        }//end if

        //Loop and draw a series of short
        // lines to serve as tic marks.
        // Begin with the positive x-axis
        // moving to the right from zero.
        while(xDoub < xMax){
          x = getTheX(xDoub);
          g.drawLine(x,topEnd,x,bottomEnd);
          xDoub += xTicInt;
        }//end while

        //Now do the negative x-axis moving
        // to the left from zero
        xDoub = 0;
        while(xDoub > xMin){
          x = getTheX(xDoub);
          g.drawLine(x,topEnd,x,bottomEnd);
          xDoub -= xTicInt;
        }//end while

      }//end xTics
      //---------------------------------//

      //Method to draw tic marks on y-axis
      void yTics(Graphics g){
        double yDoub = 0;
        int y = 0;
        int rightEnd = getTheX(yTicLen/2);
        int leftEnd = getTheX(-yTicLen/2);

        //Loop and draw a series of short
        // lines to serve as tic marks.
        // Begin with the positive y-axis
        // moving up from zero.
        while(yDoub < yMax){
          y = getTheY(yDoub);
          g.drawLine(rightEnd,y,leftEnd,y);
          yDoub += yTicInt;
        }//end while

        //Now do the negative y-axis moving
        // down from zero.
        yDoub = 0;
        while(yDoub > yMin){
          y = getTheY(yDoub);
          g.drawLine(rightEnd,y,leftEnd,y);
          yDoub -= yTicInt;
        }//end while

      }//end yTics

      //---------------------------------//

      //This method translates and scales
      // a double y value to plot properly
      // in the integer coordinate system.
      // In addition to scaling, it causes
      // the positive direction of the
      // y-axis to be from bottom to top.
      int getTheY(double y){
        double yDoub = (yMax+yMin)-y;
        int yInt = (int)(yDoub*yScale);
        return yInt;
      }//end getTheY
      //---------------------------------//

      //This method scales a double x value
      // to plot properly in the integer
      // coordinate system.
      int getTheX(double x){
        return (int)(x*xScale);
      }//end getTheX
      //---------------------------------//

    }//end inner class MyCanvas
    //===================================//

  }//end inner class Plotter
  //====================================================//


}//end class Java3D007


Copyright

Copyright 2007, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel