Interpolator Examples

VRML Concepts present in the Example

This example uses the following Interpolators

A TimeSensor as well as a TouchSensor are also present in this example. ROUTES are used for animation purposes. 

Step 1: Building the Static Scene

The scene includes a red platform, build using a Box. The platform is placed such that the top surface is in the XZ plane. The code to build the platform is:

Transform { 
     translation 0 -0.2 0 
     children Shape { 
                                  appearance Appearance { material Material { diffuseColor 1 0 0 }} 
                                  geometry Box {size 22 0.4 2} 

A textured sphere is also present in the scene. The sphere is placed on the top at one end of the platform. The code for the sphere is:

Transform { 
     translation -10 1 0 
          Shape { appearance Appearance { material Material { } 
                                                                  texture ImageTexture { url "cone.jpg"}} 
                           geometry Sphere {} 

This scene is displayed differently on different viewers. Some viewers show the whole scene and you can see the whole platform and the ball on top of it. Other viewers only show a part of the platform and the ball isn't visible. These later viewers are the ones which are according to the official specification. The specification defines an initial position for the viewer by default. The former viewers do not use this default viewer's position, instead they compute a position where the whole scene is visible. 

In here it is assumed that you're using a compliant viewer and therefore using the default viewer's position you're not able to see the whole scene. This is because you're too close to the scene. The default position for the viewer is 0 0 10, looking in the negative Z direction. The direction is good, so we only have to move ourselves a little further from the origin to view the whole scene. Try 0 0 20 and you'll be able to see the whole scene. In order to change the position a Viewpoint node is added to the VRML file. The code for this node is:

Viewpoint { position 0 0 20 } 

Step 2: Animation Part I

The objective of this section is to introduce the PositionInterpolator, and TimeSensor nodes. 

The animation for this section moves the sphere from one side of the platform, its original position, to the other side. 

In the general case animations require a timer to control the pace. In VRML timers are implemented with TimeSensors. Suppose we want the ball to take eight seconds to move from one extreme of the platform to the opposite extreme and then to start all over again. The following timer could be used:

DEF timer TimeSensor { 
      cycleInterval 8 
      loop TRUE 

Note that the node is given a name using DEF, this is because this name will be needed afterwards. The field loop is set to TRUE to repeat the animation. 

The TimeSensor node can be placed anywhere in the file. 

Because we want to change the ball's position we also need a PositionInterpolator. The interpolator will move the ball from its original position, -10 1 0, to the opposite extreme of the platform, 10 1 0. The node is defined as:

DEF pi PositionInterpolator { 
               key [ 0 1 ] 
               keyValue [-10 1 0, 10 1 0] 

Note that the node is given a name using DEF, this is because this name will be needed afterwards. 

The PositionInterpolator  node can be placed anywhere in the file. 

It is time to look at the events the above nodes generate to see how the animation is performed. 

The main idea is to have the PositionInterpolator outputting events with 3D values which will be used to change the translation of the Transform node in which the ball is defined. However the PositionInterpolator can't output events without receiving events. This is where the TimeSensor comes into play. The TimeSensor node generates events as time goes by, we can send these events to the PositionInterpolator which in turn will output events to be feed into the Transform node to change its translation field. 

In order for a node to receive events it must be given a name using DEF, so we'll have to add this to the Transform node where the ball is. The new version is:

DEF tr Transform { 
     translation -10 1 0 
          Shape { appearance Appearance { material Material { } 
                                                                  texture ImageTexture { url "cone.jpg"}} 
                           geometry Sphere {} 

A TimeSensor node outputs an event called fraction_changed, this event has a value between 0 and 1, telling the fractional amount of time from the cycle interval elapsed. We can feed this value into the interpolator's, sending the event set_fraction to it. This is achieved using ROUTES:

ROUTE timer.fraction_changed TO pi.set_fraction 

The event set_fraction inputs a value which is used has a key for the interpolator. The interpolator then computed the associated keyValue, and outputs the computed value with the event value_changed. This value is then fed into the Transform using the event set_translation. This is achieved by:

ROUTE pi.value_changed TO ball_tr.set_translation 

Step 3: Animation Part II - Adding More Realism

The objective of this section is to introduce the OrientationInterpolator

The movement of the ball is not very realistic is it? A rolling ball would have been better. That's exactly what we're going to do in here, roll the ball as it moves. 

To roll the ball we need to change its orientation as it moves, so we'll add a OrientationInterpolator to our world. The source code for this interpolator is:

DEF oi OrientationInterpolator { 
        key [0 0.157 0.314 0.471 0.628 0.785 0.942 1] 
        keyValue [ 0 0 1 0, 0 0 1 -3.14, 0 0 1 -6.28, 0 0 1  -9.42, 0 0 1 -12.56, 
                          0 0 1 -15.7, 0 0 1 -18.84, 0 0 1 -20.0 ] 

Where did we get all those strange looking values for both key and keyValue

First look at the keyValue list, it is a list of rotations. All rotations are done in the Z axis, this is because this is the axis of rotation for a rolling ball moving along the X axis, in the XZ plane. 

Now focus on the rotation values, i.e. the angles and the keys. The first key specifies a fraction of 0.157. We know that the ball will move 20 VRML units from the start of the animation till the end. So in 0.157 of the total time the ball will be translated 3.14 VRML units. Now look at the first angle, -3.14. It is the same absolute value, the minus sign comes in because we want to rotate the ball in a clockwise direction and that requires negative values. 

The reasoning behind this is that if you roll a ball until the top of the ball reaches the floor, the distance covered is 3.14 * the radius of the ball, or PI times the radius. In our case the radius is 1 so the distance is 3.14. Measuring an angle in radians actually provides the fractional perimeter for the angle. 

If you apply the same computations for the remaining keyValues you'll see that this ratio is maintained. 

So why not just specify two rotations in the keyValues, namely, 0 0 1 0 and 0 0 1 -20 ? Because VRML normalizes rotations, i.e. it computes the module 2 * PI of the rotation, therefore -20 = -1.16 - (6 * PI). In VRML -20 is equal to -1.16 which is a little bit under -90 degrees.  


When interpolating rotations VRML takes the shortest path, this is why we must specify rotations which differ at most 3.14 radians between two consecutive keyValues.

As for the positionInterpolator we need to receive events from the TimeSensor and send them to the Transform, but this time to the rotation field. The routes to achieve this are:

ROUTE timer.fraction_changed TO oi.set_fraction 
ROUTE oi.value_changed TO ball_tr.set_rotation 

Step 4: Animation Part IV - Making the ball move forward and backwards

This section introduces the ScalarInterpolator node. 

Up till now we have a ball moving from left to right, end the ball reaches the right extreme of the platform over and over again. In this section we will make the ball move backwards as well. 

The animation built so far moves the ball from left to right. Now imagine what would happen if time went backwards: we would see the ball moving from right to left. 

How do we reverse time? All interpolators in this example receive an event set_fraction. This event has a value which goes from 0.0 to 1.0. What we want is an event which goes from 0.0 to 1.0 and then back again to 0.0. The ball starts at the left (0.0), moves to the right (1.0) and then moves back to the left (0.0 again). 

So basically what we want is something which converts the sequence 0.0 -> 1.0 into the sequence 0.0->1.0->0.0. This can be achieved by a ScalarInterpolator as follows:

DEF si ScalarInterpolator { 
      key [ 0 0.5 1] 
      keyValue [0 1 0] 

The TimeSensor should be routed to the ScalarInterpolator to complete the sequence conversion. This is achieved by:

ROUTE timer.fraction_changed TO si.set_fraction 

The value_changed event from the ScalarInterpolator will move from 0.0 to 1.0 and back to 0.0. All there is left to do now is to change all the routes to the remaining interpolators, they no longer receive events from the TimeSensor, instead they will receive events from the ScalarInterpolator.

ROUTE si.value_changed TO pi.set_fraction 
ROUTE si.value_changed TO oi.set_fraction 

Note: we haven't changed the cycleInterval field of the TimeSensor, so the ball will move twice as fast because the the ball is running the double of the initial distance, i.e. is moving from the left to the right and back again. IF you want to keep the previous pace then you should set the cycleInterval to twice the original value. 

Step 5: Starting the Movement with a Mouse Click

This section introduces the TouchSensor node. 

In the previous sections the ball was moving constantly, now were going to start the movement with a mouse click and have the movement repeated only once. 

First, one must set the loop field of the TimeSensor to FALSE. The resulting TimeSensor is as follows: 

DEF timer TimeSensor { 
      cycleInterval 8 
      loop FALSE 

Because the loop field is false, and we're using the default startTime and stopTime fields for the TimeSensor the ball won't move. 

Next we need to add a TouchSensor to the same group as the Sphere. The Transform node will be:

DEF ball_tr Transform { 
      translation -10 1 0 
      children [ 
               Shape { appearance Appearance { material Material { } 
                                                                     texture ImageTexture { url "cone.jpg"}} 
                            geometry Sphere {} 

               DEF ball_sensor TouchSensor {} 

The TouchSensor will generate the event touchTime when the user clicks the mouse over a shape that within the same group as the sensor. This event shall be used to set the startTime of the TimeSensor therefore starting the animation. Because the loop field of the TimeSensor is FALSE the animation shall perform only once. 

The required routing is:

ROUTE ball_sensor.touchTime TO timer.set_startTime 

That's all folks !!!