The basis for animation is to dynamically change the values of a given object's fields. Lets imagine that you want to rotate an object and you that have a geometry node embedded in a Transform node like in the following example:
DEF Tecfa Transform { rotation 0 1 0 0 children [ Inline { url "inter-persons-2.wrl" } ] }In order to rotate the object, you want to able to change the value of the rotation field.
All exposed fields of a VRML node can be changed via incoming events. E.g. if you look at the definition of the Transform node, you can see that you can for example scale or rotate or translate and object, and even add or remove nodes from its children.
Transform { eventIn MFNode addChildren eventIn MFNode removeChildren exposedField SFVec3f center 0 0 0 exposedField MFNode children [] exposedField SFRotation rotation 0 0 1 0 exposedField SFVec3f scale 1 1 1 exposedField SFRotation scaleOrientation 0 0 1 0 exposedField SFVec3f translation 0 0 0 field SFVec3f bboxCenter 0 0 0 field SFVec3f bboxSize -1 -1 -1 }In addition to fields, most VRML nodes have so-called =>Events slots which are distinct from the ``fields'' you already encountered. There are two kinds of events:
In addition to specific ``eventIN'' and ``eventOUT'' definitions, all the exposedFields implicitly define events too:
In order to connect a node generating a message (event) with an other node receiving a message you must use a ``ROUTE'' statement. A node that produces events of a given type can be routed to a node that receives events of the same type with the following syntax:
ROUTE NodeName.eventOutName TO NodeName.eventInNameRemember this: you can only connect fields of nodes that are exactly of the same type, else you will have to write a script in between as you will learn later.
Here are two examples of such a route statement:
ROUTE LIGHT_CONTROL.isActive TO LIGHT.set_on ROUTE Random.feeling_changed TO Hasard.set_consequenceSince you can ONLY route messages that are exactly of the same data type, it might be a good idea now to start mastering all of VRML's data types (see section 8.4 on page
Usually Events are triggered by sensors. A variety of =>Sensor Nodes allow you to detect what and when something happens. We will discuss a few of them in the next sections. Now let's look at a simple TouchSensor example. The =>Touch Sensor node is defined as follows:
TouchSensor exposedField SFBool enabled TRUE eventOut SFVec3f hitNormal_changed eventOut SFVec3f hitPoint_changed eventOut SFVec2f hitTexCoord_changed eventOut SFBool isActive eventOut SFBool isOver eventOut SFTime touchTimeThe TouchSensor generates events as the pointing device ``passes over'' any geometry nodes that are descendants of the TouchSensor's parent group. In the code below we will make use of the ``isActive'' field which will change it's value from FALSE to TRUE whenever the user points to it (e.g. moves the mouse ``over'' it and holds down a button). As soon as the state of this field changes, the contents of the field (TRUE or FALSE) is ROUTEed to a PointLight node.
Before reading further on, you might want to have a look at the example.
VRML: | ../examples/anim/anim-touch-1.wrl |
Source: | ../examples/anim/anim-touch-1.text |
Click on the ``switch'' and the blue sphere will be lit while you hold down the mouse. Holding down the mouse over the sensor means that the LIGHT_CONTROL.isActive is TRUE, releasing the mouse makes it FALSE again.
Here are the important parts of the code:
# A TouchSensor #(in the same group as a geometry object so that it can be seen) # Transform { translation 0.5 1 5 children [ DEF LIGHT_CONTROL TouchSensor { } Inline { url ["light-switch.wrl"] } ] } # The light, off when the file is loaded # DEF LIGHT PointLight { on FALSE location -3 2 2 } # The object to be lit in here: # ...... ROUTE LIGHT_CONTROL.isActive TO LIGHT.set_on
As you can see in the following definition of the =>PointLight node, it has an exposedField (i.e. accessible field) called ``on''. All the ROUTE statement above does, is to define a connection between the sensor's isActive event to the PointLight's set_on event (i.e. to the value of the on field of PointLight. As in other GUIs it's the browser that will handle the routing of user actions (events) to sensors, i.e. they will become active. Below we give the complete definition of PointLight and you can see that there is indeed a exposedField ``on'' and therefore implictely a set_on event defined. [Yes I know, a section on lights is missing in this manual.]
PointLight { exposedField SFFloat ambientIntensity 0 exposedField SFVec3f attenuation 1 0 0 exposedField SFColor color 1 1 1 exposedField SFFloat intensity 1 exposedField SFVec3f location 0 0 0 exposedField SFBool on TRUE exposedField SFFloat radius 100 }
Of course you can also make light switches that will turn on some light
permanently but (if I am not wrong) you
have to do this with some scripting (see section
5.3.1 on page
that does exactly this).
But before we look into scripting, let's learn a few
more things about events.
[Warning, this section is difficult reading. Have to rewrite this at some point. Study the examples while you digest the text]
In this section you will meet first an other sensor, the TimeSensor which can be used for example to produce ``ticks'' that will get an animation going for a certain time and with a certain speed. Then we introduce the concept of an interpolator, i.e. a node that can generate numbers to producde an animation path.
The =>TimeSensor is a kind of =>Time Dependant Node. TimeSensors generate events as time passes. TimeSensors can be used to drive continuous simulations and animations, periodic activities (e.g., one per minute), and/or single occurrence events such as an alarm clock. Each Time Dependant Node contains the exposedFields: startTime, stopTime and loop and the eventOut isActive. Time dependant nodes are inactive until startTime is reached. Once active (isActive = TRUE), they will excute for 0 or more cycles until stopTime is reached or loop becomes FALSE. Note that they will ignore stopTIME if stopTime < startTime. To sum it up:
Here is the full definition of TimeSensor
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 }Note that VRML time is not human readable. =>SFTime are the number of seconds since Jan 1 1970. In the example that follows we will simply use it to get an animation going as soon as the user loads a world. I.e. the node below will just run eternally as soon as you load the file.
DEF TIME TimeSensor { cycleInterval 20 startTime 0 stopTime -1 loop TRUE }When a TimeSensor becomes active it will generate an isActive = TRUE event and begin generating time, fraction_changed, and cycleTime events, which may be routed to other nodes to drive animation or simulated behaviors. The two most important output events are:
We route the fraction_changed event to a so called OrientationInterpolator whose time-sensor triggered output is then sent to the object we are rotating. In order to guarantee a smooth animation, it will compute vaious orientation points as you will learn below.
Let's look at the =>OrientationInterpolators which is one kind of VRML's =>Interpolators. In short, they allow you do simple animations (e.g.rotate objects, move objects around, change colors of things). An interpolator will generate values based on ``clues'' defined by a set of keys (received from the set_faction eventIn) and a set of corresponding keyValues. In other words, it will automatically compute intermediate positions for each time_fraction. You only have to specify a few to get it going like in the example below. Key Values are of a different type for each Interpolator. Here is the definition for the OrientationInterpolator:
OrientationInterpolator { eventIn SFFloat set_fraction exposedField MFFloat key [] exposedField MFRotation keyValue [] eventOut SFRotation value_changed }
You now can first look at the example before reading on.
VRML: | ../examples/anim/anim-rotate-1.wrl |
Source: | ../examples/anim/anim-rotate-1.text |
Below, you can see both the definition of the Orientation Interpolator and the ROUTE statements of the example world.
DEF Tecfa Transform { rotation 0 1 0 0 children [ ..... ] } DEF TIME TimeSensor { cycleInterval 20 ........ } DEF OI_Tecfa OrientationInterpolator { key [ 0 0.5 1 ] keyValue [ 0 1 0 0, 0 1 0 3.1416, 0 1 0 6.2832] } ROUTE TIME.fraction_changed TO OI_Tecfa.set_fraction ROUTE OI_Tecfa.value_changed TO Tecfa.rotation
The keyValue field is of type MFRotation, i.e. a comma separated set of orientation values that correspond to each element in the key field. In our example you can see that we rotate around the y axis (0 1 0) using orientations 0, 3.1416 and 6.2832. VRML now uses those positions to interpolate, i.e. to generate rotation values in between which are sent out with value_changed eventOUT each time the node receives a set_fraction eventIN.
The set_fraction event is of type SFFloat in the range of [0,1] and usually routed to from a TimeSensor's fraction_changed event. The fraction_changed event is an indicater in the range of [0,1] that tells how much of a complete cycle has elapsed so far. The duration of a cycle itself is defined in seconds by the cycleInterval field. E.g. in our example, the interval is 20 seconds.
DEF TIME TimeSensor { cycleInterval 20 ...... }Indeed, if you look again at this simple rotation example you can see that a full rotation lasts about 20 seconds. Rotation speed is constant, because we told the interpolator so with the key/keyValues.
To sum it up Interpolators:
The next example shows some more irregular rotation (randomly put together).
VRML: | ../examples/anim/anim-rotate-2.wrl |
Source: | ../examples/anim/anim-rotate-2.text |
Of course, eternal rotations can be annoying to the user and they certainly waste resources. Since (as far as I understand it) there is no way to start and stop animation at a given time after the scene has been loaded (time is in seconds starting some obscure date) you'd have to write a touchsensor that activates the rotation and do some Javascript for that.
Let's see how to ``configure'' a TimeSensor according to your needs:
loop TRUE stopTime < startTime
loop FALSE stopTime < startTime
loop TRUE or FALSE stopTime >= startTime
The next example inspired from David Frerichs' exellent Creating Integrated Web Content with VRML 2.0 Tutorial In this example you can see something sliding forth and back once you click on the little red switch.
VRML: | ../examples/anim/anim-shuttle-1.wrl |
Source: | ../examples/anim/anim-shuttle-1.text |
Let's have a look at things that are new: When we load the file, the TimeSensor (called TimeSource here) has the loop = FALSE, so there will be no eventOUTs looping:
DEF TimeSource TimeSensor { cycleInterval 10 loop FALSE }
Then we use a TouchSensor node (see section 5.2
on ) to activate the loop.
More precisely, the Starter.isActive event (Touchsensor) is
routed to TimeSource.set_loop (Timesensor). As soon
as you click on the switch the loop will be turned on (and
remain so). Here is the code of the switch and it's route:
DEF Switch Transform { translation 2 -2 5 children [ DEF Starter TouchSensor { } Inline { url ["light-switch2.wrl"] } ] } ROUTE Starter.isActive TO TimeSource.set_loop
Finally, in order to move the object forth and back we use a =>PositionInterpolator that works more or less the same way as the OrientationInterpolator encountered in the previous section. The keyValue fields are the kinds of values (SFVec3f) that one uses in translation fields. Here is the other half of the code:
DEF TECFA Transform { translation 0 0 -50 children [ DEF TimeSource TimeSensor { cycleInterval 10 loop FALSE } DEF Animation PositionInterpolator { key [ 0, .50, 1.0 ] keyValue [ 0 0 -40, 0 0 6, 0 0 -40] } Inline { url "inter-persons-2.wrl" } ] } ROUTE TimeSource.fraction_changed TO Animation.set_fraction ROUTE Animation.value_changed TO TECFA.translation