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