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
|
TimeSensor {
exposedField SFTime cycleInterval 1 # (0, )
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
}
TimeSensor nodes generate events as time passes. TimeSensor nodes
can be used for many purposes including:
- driving continuous simulations and animations
- controlling periodic activities (e.g., one per minute)
- 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:
- evaluates
and sends all relevant outputs
- sends
a FALSE value for isActive
- 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 + N × 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
|

Figure 3-62: TimeSensor Node Example
|