The Annotated VRML 97 Reference

1 Intro     Concepts     3 Nodes     4 Fields/Events    Conformance
A Grammar     B Java     C JavaScript     D Examples     E Related Info    References
Quick Java         Quick JavaScript         Quick Nodes   
 

  About the Book
  
Help
  Copyright © 1997-99
  Purchase the book from Amazon.com

Chapter 3:
Node Reference


Intro
Anchor
Appearance
AudioClip
Background
Billboard
Box
Collision
Color
ColorInterpolator
Cone
Coordinate
CoordinateInterpolator
Cylinder
CylinderSensor
DirectionalLight
ElevationGrid
Extrusion
Fog
FontStyle
Group
ImageTexture
IndexedFaceSet
IndexedLineSet
Inline
LOD
Material
MovieTexture
NavigationInfo
Normal
NormalInterpolator
OrientationInterpolator
PixelTexture
PlaneSensor
PointLight
PointSet
PositionInterpolator
ProximitySensor
ScalarInterpolator
Script
Shape
Sound
Sphere
SphereSensor
SpotLight
Switch
Text
TextureCoordinate
TextureTransform
TimeSensor
TouchSensor
Transform
Viewpoint
VisibilitySensor
WorldInfo

+3.50 TimeSensor

TimeSensor { 
  exposedField SFTime   cycleInterval 1       # (0,INF)
  exposedField SFBool   enabled       TRUE
  exposedField SFBool   loop          FALSE
  exposedField SFTime   startTime     0       # (-INF,INF)
  exposedField SFTime   stopTime      0       # (-INF,INF)
  eventOut     SFTime   cycleTime
  eventOut     SFFloat  fraction_changed
  eventOut     SFBool   isActive
  eventOut     SFTime   time
}

TimeSensor nodes generate events as time passes. TimeSensor nodes can be used for many purposes including:

  1. driving continuous simulations and animations
  2. controlling periodic activities (e.g., one per minute)
  3. initiating single occurrence events such as an alarm clock

The TimeSensor node contains two discrete eventOuts: isActive and cycleTime. The isActive eventOut sends TRUE when the TimeSensor node begins running, and FALSE when it stops running. The cycleTime eventOut sends a time event at startTime and at the beginning of each new cycle (useful for synchronization with other time-based objects). The remaining eventOuts generate continuous events. The fraction_changed eventOut, an SFFloat in the closed interval [0,1], sends the completed fraction of the current cycle. The time eventOut sends the absolute time for a given simulation tick.

TECHNICAL NOTE: More time was spent refining the design of the TimeSensor node than any other node in the VRML 2.0 specification. That's not unreasonable; TimeSensors are important. With the exception of Sounds and MovieTextures, all animation in VRML worlds is driven by TimeSensors, and TimeSensors implement VRML's model of time.

It might have been simpler to define two types of TimeSensors: one that generated a (conceptually) continuous stream of events and one that generated a series of discrete events. Much of the work of defining the behavior of the TimeSensor was specifying exactly when discrete (isActive, cycleTime) and continuous (fraction_changed, time) eventOuts are generated, relative to the events that come in and relative to each other. TimeSensor generates both discrete and continuous events because synchronizing discrete events (such as starting an audio clip) with continuous events (such as animating the position of an object) is very important. Even if two separate nodes had been defined it would still be necessary to define precisely how they interact, which would be as difficult as defining the behavior of the combined TimeSensor. Daniel Woods rewrote and improved the original TimeSensor node and time-dependent nodes sections in the VRML specification.


TECHNICAL NOTE: If the enabled exposedField is TRUE, the TimeSensor node is enabled and may be running. If a set_enabled FALSE event is received while the TimeSensor node is running, the sensor performs the following actions:

  1. evaluates and sends all relevant outputs
  2. sends a FALSE value for isActive
  3. disables itself.

Events on the exposedFields of the TimeSensor node (e.g., set_startTime) are processed and their corresponding eventOuts (e.g., startTime_changed) are sent regardless of the state of the enabled field. The remaining discussion assumes enabled is TRUE.

The loop, startTime, and stopTime exposedFields and the isActive eventOut and their effects on the TimeSensor node are discussed in detail in "2.6.9 Time dependent nodes". The "cycle" of a TimeSensor node lasts for cycleInterval seconds. The value of cycleInterval must be > 0. A value <= 0 produces undefined results.

A cycleTime eventOut can be used for synchronization purposes such as sound with animation. The value of a cycleTime eventOut will be equal to the time at the beginning of the current cycle. A cycleTime eventOut is generated at the beginning of every cycle, including the cycle starting at startTime. The first cycleTime eventOut for a TimeSensor node can be used as an alarm (single pulse at a specified time).


TIP: The easiest way to set up a TimeSensor as an "alarm clock" that produces an event at a specific time in the future is to specify that time as the startTime, specify loop FALSE, and ROUTE from the TimeSensor's cycleTime eventOut. Theoretically, it doesn't matter what value you give for cycleInterval, since you're only using the cycleTime event generated at startTime. However, it is a good idea to use an arbitrarily small value as the cycleInterval (0.001 s should work well), because some browsers may generate fraction_changed and time events during the cycleInterval regardless of whether or not they are being used.

The easiest way to have one TimeSensor start when another has stopped is to write a little Script that sends the second TimeSensor a startTime event when it receives an isActive FALSE event from the first TimeSensor, like this:

     DEF TS1 TimeSensor { }
     DEF TS2 TimeSensor { }
     DEF S Script {
       eventIn SFBool isActive
       eventOut SFTime startTime_changed
       url "javascript:
         function isActive(value, timestamp) {
           if (value == false)
             startTime_changed = timestamp;
         }"
     }
     ROUTE TS1.isActive TO S.isActive
     ROUTE S.startTime_changed TO TS2.set_startTime

However, it is better to set the second TimeSensor's startTime as early as possible, so the browser knows in advance when it will start and thus it has a better chance of downloading any textures, sounds, or Inline geometry that might be needed once the second animation starts. This is also fairly easy, because the first TimeSensor will end at time startTime + cycleInterval:

     DEF TS1 TimeSensor { }
     DEF TS2 TimeSensor { }
     DEF S Script {
       eventIn SFTime startTime_changed
       field SFTime start 0
       eventIn SFTime cycleInterval_changed
       field SFTime interval 0
       eventOut SFTime set_startTime
       url "javascript:
         function startTime_changed(value)
           { start = value; }
         function cycleInterval_changed(value)
           { interval = value; }
         function eventsProcessed()
           { set_startTime = start+interval; }"
     }
     ROUTE TS1.startTime_changed
       TO S.startTime_changed
     ROUTE TS1.cycleInterval_changed
       TO S.cycleInterval_changed
     ROUTE S.set_startTime TO TS2.set_startTime 

When a TimeSensor node becomes active, it generates an isActive = TRUE event and begins generating time, fraction_changed, and cycleTime events which may be routed to other nodes to drive animation or simulated behaviours. The behaviour at read time is described below. The time event sends the absolute time for a given tick of the TimeSensor node (time fields and events represent the number of seconds since midnight GMT January 1, 1970).

fraction_changed events output a floating point value in the closed interval [0, 1]. At startTime the value of fraction_changed is 0. After startTime, the value of fraction_changed in any cycle will progress through the range (0.0, 1.0]. At startTime + N × cycleInterval, for N = 1, 2, ..., that is, at the end of every cycle, the value of fraction_changed is 1.

Let now represent the time at the current simulation tick. Then the time and fraction_changed eventOuts can then be computed as:

    time = now
    temp = (now - startTime) / cycleInterval
    f    = fractionalPart(temp)
    if (f == 0.0 && now > startTime) fraction_changed = 1.0
    else fraction_changed = f

where fractionalPart(x) is a function that returns the fractional part, that is, the digits to the right of the decimal point, of a nonnegative floating point number.

A TimeSensor node can be set up to be active at read time by specifying loop TRUE (not the default) and stopTime <= startTime (satisfied by the default values). The time events output absolute times for each tick of the TimeSensor node simulation. The time events must start at the first simulation tick greater than or equal to startTime. time events end at stopTime, or at startTime × cycleInterval for some positive integer value of N, or loop forever depending on the values of the other fields. An active TimeSensor node shall stop at the first simulation tick when now >= stopTime > startTime.

Figure 3-61: TimeSensor Node

No guarantees are made with respect to how often a TimeSensor node generates time events, but a TimeSensor node shall generate events at least at every simulation tick. TimeSensor nodes are guaranteed to generate final time and fraction_changed events. If loop is FALSE at the end of the Nth cycleInterval and was TRUE at startTime + M × cycleInterval for all 0 < M < N, then the final time event will be generated with a value of (startTime + N × cycleInterval) or stopTime (if stopTime startTime), whichever value is less. If loop is TRUE at the completion of every cycle, the final event is generated as evaluated at stopTime (if stopTime startTime) or never.

An active TimeSensor node ignores set_cycleInterval and set_startTime events. An active TimeSensor node also ignores set_stopTime events for set_stopTime <= startTime. For example, if a set_startTime event is received while a TimeSensor node is active, that set_startTime event is ignored (the startTime field is not changed, and a startTime_changed eventOut is not generated). If an active TimeSensor node receives a set_stopTime event that is less than the current time, and greater than startTime, it behaves as if the stopTime requested is the current time and sends the final events based on the current time (note that stopTime is set as specified in the eventIn).

TIP: Ignoring set_ events while a TimeSensor is running makes creating simple animations much easier, because for most simple animations you want the animation played to completion before it can be restarted. If you do need to stop and restart a TimeSensor while it is running, send it both a stopTime and a startTime event. The stopTime event will stop the sensor and the startTime event will restart it immediately. For example, this fragment will result in the TimeSensor immediately restarting when the TouchSensor is activated:
     DEF TOUCHS TouchSensor { ... }
     DEF TIMES TimeSensor { ... }
     ROUTE TOUCHS.touchTime TO TIMES.set_stopTime
     ROUTE TOUCHS.touchTime TO TIMES.set_startTime 
                  


TIP: There are two cases of the TimeSensor that are most common. The first case uses a TimeSensor to drive a single cycle of an animation or behavior. Typically, another node that has a SFTime eventOut (e.g., Script, TouchSensor, or ProximitySensor) routes to the TimeSensor's startTime eventIn (setting it to now or now + delay), which in turn routes its fraction_changed eventOut to another node's set_fraction eventIn. The second common case of a TimeSensor is a continuously looping animation or behavior. In this case, the TimeSensor's loop field is TRUE, stopTime is 0, startTime is 0, and cycleTime is the length of the intended sequence. This has the effect of starting the sequence in 1970 and looping forever. Be aware that looping TimeSensors can slow down rendering performance if too many are active simultaneously, and should be used only when necessary. It is recommended that you restrict the effect of looping TimeSensors by coupling them with a ProximitySensor, VisibilitySensor, Script, or LOD that disables the TimeSensor when out of range or not relevant.

EXAMPLE (click to run): The following example illustrates the TimeSensor (see Figure 3-62). The first TimeSensor defines a continuously running animation that is enabled and disabled by a ProximitySensor. The second TimeSensor is triggered by a TouchSensor and fires one cycle of an animation each time it is triggered:

#VRML V2.0 utf8
Group { children [
  DEF PS ProximitySensor { size 30 30 30 }
  DEF TS1 TimeSensor {
    enabled FALSE 
    loop TRUE
  }
  DEF T1 Transform {
    translation 0 0 -.5
    rotation .707 -.707 0 1.57
    children Shape {
      geometry Box {}
      appearance DEF A Appearance {
        material Material { diffuseColor 1 1 1 }
      }
    }
  }
  DEF OI OrientationInterpolator {
    key [ 0, 0.33, 0.66, 1.0 ]
    keyValue [ .707 .707 0 0,    .707 .707 0 2.09,
               .707 .707 0 4.18, .707 .707 0 6.28 ]
  }
  DEF T2 Transform {
    translation -4 0 0
    children [
      Shape {
        geometry Sphere { radius 0.5 }
        appearance USE A
      }
      DEF TOS TouchSensor {}
      DEF TS2 TimeSensor { cycleInterval 0.75 }
      DEF PI PositionInterpolator {
        key [ 0, .2, .5, .8, 1 ]
        keyValue [ -4 0 0, 0 4 0, 4 0 0, 0 -4 0, -4 0 0 ]
      }
    ]
  }
  Viewpoint { position 0 0 50 description "Animation off"}
  Viewpoint { position 0 0 10 description "Animation on"}
] }
ROUTE PS.isActive TO TS1.enabled
ROUTE TS1.fraction_changed TO OI.set_fraction
ROUTE OI.value_changed TO T1.rotation
ROUTE TOS.touchTime TO TS2.startTime
ROUTE TS2.fraction_changed TO PI.set_fraction
ROUTE PI.value_changed TO T2.translation

TimeSensor node example

Figure 3-62: TimeSensor Node Example