Interpolation and timers

The magical timer

Real Audio

So far, we have learned about animation under user control, and about routing events from a sensor to an exposed field of a node. It would be interesting to have animation occur without explicit user interaction. Of course, VRML allows this. Here is an example:

fader.wrl
#VRML V2.0 utf8
#size.wrl
#Describes how the timer is used to modify an object

Inline {
  url ["http://www.cs.iupui.edu/~aharris/mm/vrml2/axis.wrl"]
} # end inline

Shape {
  appearance Appearance {
    material DEF BALL Material {
      transparency 0
      diffuseColor 0 0 1
    } # end material
  } # end appearance

  geometry Sphere {
    radius 1
  } # end sphere
} # end shape

DEF TIMER TimeSensor {
  loop TRUE
} # end timer

ROUTE TIMER.fraction_changed TO BALL.transparency
We have added a new object here called a TimeSensor. Here's the official specs for the TimeSensor object:
TimeSensor {
  exposedField SFTime   cycleInterval 1
  exposedField SFBool   enabled       TRUE
  exposedField SFBool   loop          FALSE
  exposedField SFTime   startTime     0
  exposedField SFTime   stopTime      0
  eventOut     SFTime   cycleTime
  eventOut     SFFloat  fraction_changed
  eventOut     SFBool   isActive
  eventOut     SFTime   time
}
It has a number of interesting fields, and produces some eventOut values, like most sensors. For now, we will just look at one field and one eventOut. The loop field determines whether the timer repeats or not. The fraction_changed eventOut spits out a continuous value between zero and one. Each time through the loop, the value will be a series of floats evenly distributed from zero to one.

The transparency field of a shape takes the same kinds of values. If a shape has a transparency of zero, the shape is completely opaque. If the transparency is 1, the shape is completely transparent. Values in between indicate varying levels of visibility.

Our program routes the value of the fraction_changed event of the timer to the transparency node of the sphere's appearance.

This particular model goes through the cycle once per second. If we wanted, we could change the value of the cycleInterval field to some other value, and it would take that number of seconds to complete a cycle. So, if the cycleInterval is set to 4, the fading action would take four times as long to occur. The generated values of fraction_changed would still be in the zero to one range.

fader2.wrl
#VRML V2.0 utf8
#fader2.wrl
#adds an interpolator to the fader for a more uniform shift

Inline {
  url ["http://www.cs.iupui.edu/~aharris/mm/vrml2/axis.wrl"]
} # end inline

Shape {
  appearance Appearance {
    material DEF BALL Material {
      transparency 0
      diffuseColor 0 0 1
    } # end material
  } # end appearance

  geometry Sphere {
    radius 1
  } # end sphere
} # end shape

DEF TIMER TimeSensor {
  cycleInterval 4
  loop TRUE
} # end timer

DEF CONTROLLER ScalarInterpolator {
  key [
    0
    .5
    1
  ] # end key

  keyValue [
    0
    1
    0
  ] # end keyValue
} # end controller
ROUTE TIMER.fraction_changed TO CONTROLLER.set_fraction
ROUTE CONTROLLER.value_changed TO BALL.transparency


This program does not on the surface to be much different, but it does behave differently. Note that the sphere seems to fade in and out much more smoothly than in the last example. During one cycle of the timer, the visibility will go from zero to one back to zero evenly.

We need a value that will go 0 to 1 to 0, but the clock just goes 0 to 1. We will use a special tool to generate the values we want. CONTROLLER is an example of an interpolator node. Interpolators are special nodes that are meant to grab certain input values (usually 0 to 1 values such as the ones we are dealing with now). They allow us to define some kind of output range we want to map to. They convert the percentage input into an appropriate output automatically. Huh? CONTROLLER is a scalarInterpolator. It is designed to take a scalar and convert it into another scalar. It contains two main fields, key and keyValue. Key is an MFFloat. In our case, it has values of 0, .5, and 1. The key field is used to explain where along the input there will be special instructions. The keyValue field contains the same number of values as the keyfield, and explains what the output should be at each point. A chart might clarify the situation:
key keyValue Explanation
0 0 At the beginning of the cycle, set value to 0
.5 1 At the middle of the cycle, set value to 1
1 0 At the end of the cycle, set value back to zero
Note that the interpolator is interested only in the percentage of the cycle completed. The actual time taken does not matter. This means that we can change the cycleInterval of the TimeSensor at will, and the interpolator will still work correctly.

This works reasonably well, because the TimeSensor sends out scalar values, and the transparency field accepts a scalar. We can use the ScalarInterpolator anytime we want to convert a timer (or other 0 to 1 value) to some other kind of scalar. Often, though, we want to do other things based on time. We might want to change the color, size, orientation, or position of an object over time. None of these are scalar values. What we will do is introduce special interpolators designed to generate different kinds of values to be routed to different nodes. As always, let's look at an example:

Motion:

Real Audio

motion.wrl
#VRML V2.0 utf8
#motion.wrl
#Describes how the timer is used to move an object

Inline {
  url ["http://www.cs.iupui.edu/~aharris/mm/vrml2/axis.wrl"]
} # end inline

DEF BALL Transform{
  children [
    Shape {
      appearance Appearance {
        material Material {
          diffuseColor 0 0 1
        } # end material
      } # end appearance

      geometry Sphere {
        radius .25
      } # end sphere
    } # end shape
  ] # end children
} # end transform

DEF PATH PositionInterpolator {
  key [
    0
    .25
    .5
    .75
    1
  ] # end key

  keyValue [
    -3  0  0,
     0  3  0,
     3  0  0,
     0 -3  0
    -3  0  0,
  ] # end keyValue
} # end path

DEF TIMER TimeSensor {
  loop TRUE
  cycleInterval 5
} # end timer

ROUTE TIMER.fraction_changed TO PATH.set_fraction
ROUTE PATH.value_changed TO BALL.translation

As you can see, the ball moves around in a specified path. The ball covers many points in its circuit, but we only specified 5 key points. We used an interpolator to do two main things for us. First, it accepted the scalar 0-1 value from the TimeSensor and converted it into a series of Vec3fs, and secondly, it generated all the intermediate positions we needed automatically. In other words, we told the ball where to be only at a few points in the timer circuit, and the interpolator automatically determined all the other points we needed. The key field is still an MFfloat, but now it has five points in it. The keyValue field is an MFVec3F, as it outputs a series of Vec3F values. At each of the specified points in the key array, the ball will be at the location determined by the keyValue array. At all intermediate points, the interpolator will determine the appropriate point on a straight line between the key points.

sidebar - linear interpolation

This is known as 'linear interpolation', because intermediate points are determined by generating the line formula from key values, and plotting points on the line. All interpolation need not be linear, and in fact true straight lines rarely occur in nature. Later when we are doing script nodes, we will investigate some other types of interpolation. We could, for example, make a special interpolator that would bounce a ball realistically taking into effect gravity and elasticity.


Note that in our example, the ball travelled in a 4-point path, but we specified 5 points. The starting position was also the ending position, which ensures a smooth continuous action. This is not absolutely necessary, but avoids having things 'jump' around the screen. Note also that we always have a value for the 0 position and the 1 position in the key field. All others are optional, but it makes no sense not to have some starting and ending location. It is also worth noting that these examples so far have all used constant width gaps. (0 to .25 is the same size as .5 to .75) This is not necessary. If you want your object to seemingly speed up, you essentially have to have it translate either a larger distance or a shorter time (or both).

Changing the scale of an object

Here is an interesting use of an interpolator:

size.wrl
#VRML V2.0 utf8
#size.wrl
#Describes how the timer is used to resize an object

Inline {
  url ["http://www.cs.iupui.edu/~aharris/mm/vrml2/axis.wrl"]
} # end inline

DEF BALL Transform {
  #note that now BALL is the name of a transform object
  #which has the sphere as a child
  children[
    Shape {
      appearance Appearance {
        material Material {
          transparency 0
          diffuseColor 0 0 1
        } # end material
      } # end appearance
    
      geometry Sphere {
        radius 1
      } # end sphere
    } # end shape
  ] # end children
} # end transform

DEF TIMER TimeSensor {
  cycleInterval 5
  loop TRUE
} # end timer

DEF SIZER PositionInterpolator{
  key [
    0
    .5
    1
  ] # end key

  keyValue [
    0 0 0
    5 5 5
    0 0 0
  ] # end keyValue
} # end sizer


ROUTE TIMER.fraction_changed TO SIZER.set_fraction
ROUTE SIZER.value_changed TO BALL.scale
You might expect there to be some sort of scaleInterpolator used here, but no such node exists. It is not needed, because PositionInterpolator transmits a Vec3F, which is exactly what the scale field expects. The lesson here is that certain kinds of interpolators spit out certain kinds of values, and you will want to choose an interpolator based on the kind of value your 'changing' field needs.

Interpolating colors

Real Audio

A special interpolator is used to handle color changes. It is called (surprise!) the ColorInterpolator. Once you understand the other interpolators, use of the colorInterpolator is reasonably straightforward.

color.wrl
#VRML V2.0 utf8
#color changer
#demonstrates how color interpolation works

Shape {
  appearance Appearance {
    material DEF theColor Material {
      diffuseColor 0 0 1
    } # end material
  } # end appearance

  geometry Sphere {
    radius 2
  } # end geometry
} # end shape
 
DEF timer TimeSensor {
  loop TRUE
  cycleInterval 5
}

DEF colorChanger ColorInterpolator {
  key[
    0
    .33
   .66
   1
  ] # end key

  keyValue [
    1 0 0
    0 1 0
    0 0 1
    1 0 0
  ] # end keyValue
} # end colorChanger

ROUTE timer.fraction_changed TO colorChanger.set_fraction
ROUTE colorChanger.value_changed TO theColor.set_diffuseColor
The only surprise here is that we DEFd the Material node in order to route the new color somewhere. Note that the results will not be intuitive. The mathematical values between 1 0 0 (red) and 0 0 1 (blue) will be somewhat arbitrary to our perceptual senses. Still, it is interesting to be able to fade between various color spaces. VRML does some math tricks to make he color transitions reasonably sensible, but there is no guarantee that 'halfway' colors will make sense. It still is a technique worthy of use in some situations.

Rotating things

The last major interpolator we need consider is the OrientationInterpolator. As you have no doubt guessed, it generates values based on 0-1 keys from the timer, and translates them to orientations listed in a KeyValues MF. Here's the obligitory example:

rotate.wrl
#VRML V2.0 utf8
#rotate.wrl
#demonstrates orientation Interpolation

DEF theCone Transform {
  children [
    Shape {
      appearance Appearance {
        material Material {
          diffuseColor 0 1 0
        } # end material
      } # end appearance

      geometry Cone {
        bottomRadius 1
        height 2
      } # end cone
    } # end shape
  ] # end children
} # end theCone

DEF timer TimeSensor {
  cycleInterval 3
  enabled TRUE
  loop TRUE
} # end timer

DEF twirler OrientationInterpolator {
  key [
    0
    .25
    .5
    .75
    1
  ] # end key

  keyValue [
    1 0 0 0
    1 0 0 1.57
    1 0 0 3.14
    1 0 0 4.71
    1 0 0 6.28
  ]
} #end twirler

ROUTE timer.fraction_changed TO twirler.set_fraction
ROUTE twirler.value_changed TO theCone.rotation
This model rotates a shape under timer control. Note that we tend to keep the orientation along the same axis, but this is not necessary. One potential trap of the orientationInterpolator is that there is more than one path to any particular orientation. If you go, for example from 1 0 0 0 to 1 0 0 3.1415 in one jump, you are going exactly halfway around the circle, and there is no predicting whether you will go clockwise or counterclockwise. Likewise, values larger than 2*pi will NOT result in full rotations, but will be evaluated as smaller angles. Your key values should be at at the largest pi/2 radians apart, or you may have unpredictable results.

Bovine Rotation

Real Audio

You have finally made it to the big leagues of virtual reality. You have earned the right to view a serious and weighty application of virtual reality - virtual cow tipping!

cowTip.wrl
#VRML V2.0 utf8
#the cow tipper

DEF theCow Transform {
  children [
    Transform {
      scale .2 .2 .2
      rotation 0 1 0 1.5
      children [
        Inline {
          url ["cow.wrl"]
        } # end inline
      ] # end children
    } # end Transform
    DEF ts TouchSensor{}
  ] # end theCow children
} # end theCow

Sound {
  spatialize FALSE
  maxFront 15
  source DEF moo AudioClip {
    url["cow.wav"]
    loop FALSE
    startTime 0
  } # end moo
} # end source


DEF timer TimeSensor {
  cycleInterval 2
  loop FALSE
  startTime 0
} # end timer



DEF tipper OrientationInterpolator{
  key[
    0
    .25
    .5
    .75
   1
  ]

  keyValue[
    1 0 0 0,
    1 0 0 -.75
    1 0 0 -1.5
    1 0 0 -.75
    1 0 0 0,
  ]
} # end tipper

Viewpoint {
  position 0 3 10
} # end viewpoint

Background {
  skyColor [
    0 0 0,
    .4 .4 1
  ]
  skyAngle 1.6

  groundColor [
    1 1 1,
    .4 .8 .4,
    .2 .2 .2 
  ]
  groundAngle [
    1.2,
    1.57
  ] # end groundAngle
} # end background


ROUTE ts.touchTime TO timer.startTime
ROUTE ts.touchTime TO moo.startTime

ROUTE timer.fraction_changed TO tipper.set_fraction
ROUTE tipper.value_changed TO theCow.rotation

This example - silly as it is - demonstrates a number of key elements of animating VRML. In all our previous examples, we simply left the timer in an infinite loop. In this case, we want the cow to tip only when the user has an irrepressible sophmoric urge, and clicks on the cow. Further, we want the cow to tip down, and back up, and we want a plaintive 'moo' sound to occur as the cow is descending into the pasture of doom. This will require some clever use of interpolation and routing.

The initial setup of the world is not terribly surprising. We have inlined the cow from a terrific site called VRML lion. We didn't have to worry about the creation of the cow at all. It is a massive indexedFaceSet in VRML 1.0 format. Fortunately, all these details are hidden from us by the inline node. What we are more concerned about is the cow is placed inside a pair of Transforms. The first one is DEFined, and the inner one handles resizing and positioning the cow in a good initial position for the world.

Inside the cow's transform is a touchSensor. This will tell us when the cow has been clicked. However, we do not simply want a boolean value this time, because we want to use the click to set off a whole chain of events. We will use the touchTime event out of the timer to do some magic. You'll see it work in a few minutes, but basically it sends out a signal telling when the sensor was touched.

We have also created a timeSensor called timer. The timer is set up with the looping field set to false, and a cycleInterval of 2 seconds. We don't want to loop indefinitely as we have for our other examples. Instead, we want the animation to occur once after the cow has been clicked. Note that we set the startTime field to zero (for obscure reasons, zero is midnight, December 31, 1970). Clearly we are well past that date. We will route the touchTime of the touchSensor to the startTime of the timer, and this will 'turn on' the timer. Since the loop is set to false, the timer will stop once the animation is over, awaiting the next input from the touchSensor.

We created an OrientationInterpolator called tipper which takes the zero to one values that come from timer.fraction_changed, and converts them to a series of orientations. In one loop of the timer, we want the cow to rotate 1/4 of a circle around the X axis, and back up. The key and keyValue fields of the orientationInterpolator are designed to do just that.

It isn't really necessary, but in order to create the mood, (haw, MOOd, !!!) we added a sound file and a background. We plan on covering these entities later in more detail, but you can see the basic layout of them. For now, the interesting part is that the audioClip has a startTime.

All the interesting wiring happens in the ROUTE statements.
ROUTE ts.touchTime TO timer.startTime
ROUTE ts.touchTime TO moo.startTime
sends the value of the touchSensor startTime to the timer and the audioClip. This tells both of those entities they can get started. NOTE: It is a happy coincidence that the 'moo' sound takes about the same length of time as the animation. That is not guaranteed, as they are seperate events that just happen to share the same trigger.
ROUTE timer.fraction_changed TO tipper.set_fraction
This line copies the fraction_changed of the timer to the orientationInterpolator, setting up the interpolator to start pumping out rotation values.
ROUTE tipper.value_changed TO theCow.rotation
This last ROUTE takes the orientations being created by the orientationInterpolator and sends them to the rotation field of the theCow transform.

Thoughts about animation

VRML animation using timers and routes is an incredibly powerful phenomenon. This allows us to generate values of just about any type we can use in VRML, and map them to any exposedField of any node. We have seen just a few examples of the possibilities. The exciting part of this is we have not needed any formal scripting language at all!! The things we can do simply with this technology is quite impressive, but has limits. In our next couple of sessions, we will learn how to generate our own scripts to do even more powerful things.

No actual cattle were harmed in the production of this tutorial
© Andy Harris
Indiana University / Purdue University, Indianapolis
email: aharris@.cs.iupui.edu
homepage: http://www.cs.iupui.edu/~aharris rotation 1 0 0 1.57