1 /**************************************************************************\
2 * Copyright (c) Kongsberg Oil & Gas Technologies AS
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of the copyright holder nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif // HAVE_CONFIG_H
36
37 #ifdef HAVE_VRML97
38
39 /*!
40 \class SoVRMLTimeSensor SoVRMLTimeSensor.h Inventor/VRMLnodes/SoVRMLTimeSensor.h
41 \brief The SoVRMLTimeSensor class is a multi-purpose time event generator.
42
43 \ingroup VRMLnodes
44
45 \WEB3DCOPYRIGHT
46
47 \verbatim
48 TimeSensor {
49 exposedField SFTime cycleInterval 1 # (0,inf)
50 exposedField SFBool enabled TRUE
51 exposedField SFBool loop FALSE
52 exposedField SFTime startTime 0 # (-inf,inf)
53 exposedField SFTime stopTime 0 # (-inf,inf)
54 eventOut SFTime cycleTime
55 eventOut SFFloat fraction_changed # [0, 1]
56 eventOut SFBool isActive
57 eventOut SFTime time
58 }
59 \endverbatim
60
61 TimeSensor nodes generate events as time passes. TimeSensor nodes
62 can be used for many purposes including:
63
64 - driving continuous simulations and animations;
65 - controlling periodic activities (e.g., one per minute);
66 - initiating single occurrence events such as an alarm clock.
67
68 The TimeSensor node contains two discrete eventOuts: \e isActive and
69 \e cycleTime. The \e isActive eventOut sends TRUE when the TimeSensor
70 node begins running, and FALSE when it stops running. The \e cycleTime
71 eventOut sends a time event at \e startTime and at the beginning of
72 each new cycle (useful for synchronization with other time-based
73 objects). The remaining eventOuts generate continuous events. The
74 \e fraction_changed eventOut, an SFFloat in the closed interval [0,1],
75 sends the completed fraction of the current cycle. The \e time eventOut
76 sends the absolute time for a given simulation tick.
77
78 If the enabled exposedField is TRUE, the TimeSensor node is enabled
79 and may be running. If a set_enabled FALSE event is received while
80 the TimeSensor node is running, the sensor performs the following
81 actions:
82
83 - evaluates and sends all relevant outputs;
84 - sends a FALSE value for isActive;
85 - disables itself.
86
87 Events on the exposedFields of the TimeSensor node (e.g., \e
88 set_startTime) are processed and their corresponding eventOuts
89 (e.g., startTime_changed) are sent regardless of the state of the
90 enabled field. The remaining discussion assumes enabled is TRUE.
91
92 The e\ loop, \e startTime, and \e stopTime exposedFields and the
93 isActive eventOut and their effects on the TimeSensor node are discussed
94 in detail in 4.6.9, Time-dependent nodes
95 (<http://www.web3d.org/documents/specifications/14772/V2.0/part1/concepts.html#4.6.9>).
96 The "cycle" of a TimeSensor node lasts for cycleInterval
97 seconds. The value of cycleInterval shall be greater than zero.
98
99 A cycleTime eventOut can be used for synchronization purposes such
100 as sound with animation. The value of a cycleTime eventOut will be
101 equal to the time at the beginning of the current cycle. A cycleTime
102 eventOut is generated at the beginning of every cycle, including the
103 cycle starting at startTime. The first cycleTime eventOut for a
104 TimeSensor node can be used as an alarm (single pulse at a specified
105 time).
106
107 When a TimeSensor node becomes active, it generates an isActive =
108 TRUE event and begins generating time, fraction_changed, and
109 cycleTime events which may be routed to other nodes to drive
110 animation or simulated behaviours. The behaviour at read time is
111 described below. The time event sends the absolute time for a given
112 tick of the TimeSensor node (time fields and events represent the
113 number of seconds since midnight GMT January 1, 1970).
114
115 fraction_changed events output a floating point value in the closed
116 interval [0, 1]. At startTime the value of fraction_changed is
117 0. After startTime, the value of fraction_changed in any cycle will
118 progress through the range (0.0, 1.0]. At startTime + N
119 cycleInterval, for N = 1, 2, ..., that is, at the end of every
120 cycle, the value of fraction_changed is 1.
121
122 Let \e now represent the time at the current simulation tick. Then
123 the time and fraction_changed eventOuts can then be computed as:
124
125 \verbatim
126 time = now
127 temp = (now - startTime) / cycleInterval
128 f = fractionalPart(temp)
129 if (f == 0.0 && now > startTime) fraction_changed = 1.0
130 else fraction_changed = f
131 \endverbatim
132
133 where fractionalPart(x) is a function that returns the fractional
134 part, (that is, the digits to the right of the decimal point), of a
135 nonnegative floating point number.
136
137 A TimeSensor node can be set up to be active at read time by
138 specifying loop TRUE (not the default) and stopTime less than or
139 equal to startTime (satisfied by the default values). The time
140 events output absolute times for each tick of the TimeSensor node
141 simulation. The time events shall start at the first simulation
142 tick greater than or equal to startTime. time events end at
143 stopTime, or at startTime + N cycleInterval for some positive
144 integer value of N, or loop forever depending on the values of the
145 other fields. An active TimeSensor node shall stop at the first
146 simulation tick when now >= stopTime > startTime.
147
148 No guarantees are made with respect to how often a TimeSensor node
149 generates time events, but a TimeSensor node shall generate events
150 at least at every simulation tick. TimeSensor nodes are guaranteed
151 to generate final time and fraction_changed events. If loop is FALSE
152 at the end of the Nth cycleInterval and was TRUE at startTime + M
153 cycleInterval for all 0 < M < N, the final time event will be
154 generated with a value of (startTime + N cycleInterval) or
155 stopTime (if stopTime > startTime), whichever value is less. If loop
156 is TRUE at the completion of every cycle, the final event is
157 generated as evaluated at stopTime (if stopTime > startTime) or
158 never.
159
160 An active TimeSensor node ignores set_cycleInterval and
161 set_startTime events. An active TimeSensor node also ignores
162 set_stopTime events for set_stopTime less than or equal to
163 startTime. For example, if a set_startTime event is received while
164 a TimeSensor node is active, that set_startTime event is ignored
165 (the startTime field is not changed, and a startTime_changed
166 eventOut is not generated). If an active TimeSensor node receives a
167 set_stopTime event that is less than the current time, and greater
168 than startTime, it behaves as if the stopTime requested is the
169 current time and sends the final events based on the current time
170 (note that stopTime is set as specified in the eventIn).
171
172 A TimeSensor read from a VRML file shall generate isActive TRUE,
173 time and fraction_changed events if the sensor is enabled and all
174 conditions for a TimeSensor to be active are met.
175
176 */
177
178 /*!
179 \var SoSFTime SoVRMLTimeSensor::cycleInterval
180 The cycle interval. Default value is 1. Must be > 0.
181 */
182
183 /*!
184 \var SoSFBool SoVRMLTimeSensor::enabled
185 Used to enable/disable timer. Default value is TRUE.
186 */
187
188 /*!
189 \var SoSFBool SoVRMLTimeSensor::loop
190 TRUE if timer should loop. Default value is FALSE.
191 */
192
193 /*!
194 \var SoSFTime SoVRMLTimeSensor::startTime
195 The timer start time. Default value is 0.0.
196 */
197
198 /*!
199 \var SoSFTime SoVRMLTimeSensor::stopTime
200 The timer stop time. Default value is 0.0.
201 */
202
203 /*!
204 \var SoEngineOutput SoVRMLTimeSensor::cycleTime
205 An eventOut that is sent when a new cycle is started.
206 */
207
208 /*!
209 \var SoEngineOutput SoVRMLTimeSensor::fraction_changed
210 An eventOut that is sent for each tick, containing a number between 0 and 1.
211 */
212
213 /*!
214 \var SoEngineOutput SoVRMLTimeSensor::isActive
215 An eventOut that is sent when the timer is enabled/disabled.
216 */
217
218 /*!
219 \var SoEngineOutput SoVRMLTimeSensor::time
220 An eventOut that is sent for each tick, containing the current time.
221 */
222
223 #include <Inventor/VRMLnodes/SoVRMLTimeSensor.h>
224
225 #include <Inventor/VRMLnodes/SoVRMLMacros.h>
226 #include <Inventor/SoDB.h>
227
228 #include "engines/SoSubNodeEngineP.h"
229
230 #ifndef DOXYGEN_SKIP_THIS
231
232 class SoVRMLTimeSensorP {
233 public:
234 double starttime;
235 double stoptime;
236 double currtime;
237 double cycletime;
238 double cyclestart;
239 float fraction;
240 SbBool loop;
241 SbBool running;
242 };
243
244 #endif // DOXYGEN_SKIP_THIS
245
246 SO_NODEENGINE_SOURCE(SoVRMLTimeSensor);
247
248 // Doc in parent
249 void
initClass(void)250 SoVRMLTimeSensor::initClass(void) // static
251 {
252 SO_NODEENGINE_INTERNAL_INIT_CLASS(SoVRMLTimeSensor);
253 }
254
255 #define PRIVATE(obj) ((obj)->pimpl)
256
257 /*!
258 Constructor.
259 */
SoVRMLTimeSensor(void)260 SoVRMLTimeSensor::SoVRMLTimeSensor(void)
261 {
262 PRIVATE(this) = new SoVRMLTimeSensorP;
263
264 SO_NODEENGINE_INTERNAL_CONSTRUCTOR(SoVRMLTimeSensor);
265
266 SO_VRMLNODE_ADD_EXPOSED_FIELD(cycleInterval, (1.0f));
267 SO_VRMLNODE_ADD_EXPOSED_FIELD(enabled, (TRUE));
268 SO_VRMLNODE_ADD_EXPOSED_FIELD(loop, (FALSE));
269 SO_VRMLNODE_ADD_EXPOSED_FIELD(startTime, (0.0f));
270 SO_VRMLNODE_ADD_EXPOSED_FIELD(stopTime, (0.0f));
271 SO_VRMLNODE_ADD_EVENT_IN(timeIn); // private
272
273 SO_NODEENGINE_ADD_OUTPUT(cycleTime, SoSFTime);
274 SO_NODEENGINE_ADD_OUTPUT(fraction_changed, SoSFFloat);
275 SO_NODEENGINE_ADD_OUTPUT(isActive, SoSFBool);
276 SO_NODEENGINE_ADD_OUTPUT(time, SoSFTime);
277
278 this->isActive.enable(FALSE);
279 this->cycleTime.enable(FALSE);
280
281 PRIVATE(this)->fraction = 0.0;
282 PRIVATE(this)->cyclestart = 0.0;
283 PRIVATE(this)->cycletime = 1.0;
284 PRIVATE(this)->running = FALSE;
285 PRIVATE(this)->loop = FALSE;
286 PRIVATE(this)->starttime = 0.0;
287 PRIVATE(this)->stoptime = 0.0;
288
289 this->timeIn.enableNotify(FALSE);
290 SoField * realtime = SoDB::getGlobalField("realTime");
291 this->timeIn.connectFrom(realtime);
292
293 // we always connect and just disable notification when timer
294 // is not active, since it is currently not possible to disconnect
295 // from a field in the inputChanged() method. inputChanged() is
296 // triggered by notify(), and if a field is disconnected while the
297 // master field is notifying, bad things will happen in
298 // SoAuditorList.
299
300 // FIXME: Maybe we should consider making a version of SoAuditorList
301 // that handles disconnects in the notification loop? I think
302 // it might be difficult though. pederb, 2001-11-06
303 }
304
305 /*!
306 Destructor.
307 */
~SoVRMLTimeSensor()308 SoVRMLTimeSensor::~SoVRMLTimeSensor()
309 {
310 delete PRIVATE(this);
311 PRIVATE(this) = 0;
312 }
313
314 // Doc in parent
315 void
notify(SoNotList * list)316 SoVRMLTimeSensor::notify(SoNotList * list)
317 {
318 inherited::notify(list);
319 }
320
321 // Documented in superclass. Overridden to not write connection to
322 // realTime global field.
323 void
write(SoWriteAction * action)324 SoVRMLTimeSensor::write(SoWriteAction * action)
325 {
326 // Note: the code in this method matches that of SoElapsedTime and
327 // SoOneShot and SoTimeSensor, so if any bugs are found and
328 // corrected, remember to pass on the updates.
329
330 // Disconnect from realTime field.
331 SoField * connectfield = NULL;
332 SbBool connectfromrealTime =
333 this->timeIn.getConnectedField(connectfield) &&
334 connectfield == SoDB::getGlobalField("realTime");
335 SbBool defaultflag = this->timeIn.isDefault();
336 if (connectfromrealTime) {
337 this->timeIn.disconnect();
338 this->timeIn.setDefault(TRUE);
339 }
340
341 inherited::write(action);
342
343 // Re-connect to realTime field.
344 if (connectfromrealTime) {
345 // Don't send notification when reconnecting to preserve the state
346 // of the scenegraph between write passes.
347 this->timeIn.connectFrom(connectfield, TRUE);
348 this->timeIn.setDefault(defaultflag);
349 }
350 }
351
352
353 // Doc in parent
354 void
evaluate(void)355 SoVRMLTimeSensor::evaluate(void)
356 {
357 SO_ENGINE_OUTPUT(time, SoSFTime, setValue(PRIVATE(this)->currtime));
358 SO_ENGINE_OUTPUT(isActive, SoSFBool, setValue(PRIVATE(this)->running));
359 SO_ENGINE_OUTPUT(cycleTime, SoSFTime, setValue(PRIVATE(this)->cyclestart));
360 SO_ENGINE_OUTPUT(fraction_changed, SoSFFloat, setValue(PRIVATE(this)->fraction));
361 }
362
363 // Doc in parent
364 void
inputChanged(SoField * which)365 SoVRMLTimeSensor::inputChanged(SoField * which)
366 {
367 // Default is to not do any notification when we return from this
368 // function to SoEngine::notify(). This is an optimization for this
369 // engine to avoid transmission of notification to all slave fields
370 // each time the timeIn field is updated.
371 this->fraction_changed.enable(FALSE);
372 this->isActive.enable(FALSE);
373 this->cycleTime.enable(FALSE);
374
375 if (which == &this->enabled) {
376 SbBool on = this->enabled.getValue();
377
378 if (!on) this->timeIn.enableNotify(FALSE);
379
380 if (PRIVATE(this)->running && !on) {
381 PRIVATE(this)->running = FALSE;
382 this->fraction_changed.enable(TRUE);
383 this->isActive.enable(TRUE);
384 }
385 else if (!PRIVATE(this)->running && on) {
386 which = &this->startTime; // warning, hack
387 }
388 }
389
390 if (which == &this->loop) {
391 PRIVATE(this)->loop = this->loop.getValue();
392 if (PRIVATE(this)->loop == TRUE && !this->timeIn.isNotifyEnabled())
393 which = &this->startTime; // warning hack
394 }
395
396 if (which == &this->startTime) {
397 double currtime = this->timeIn.getValue().getValue();
398 PRIVATE(this)->starttime = currtime;
399 if (!PRIVATE(this)->running) {
400 PRIVATE(this)->starttime = this->startTime.getValue().getValue();
401 if (currtime >= PRIVATE(this)->starttime) {
402 SbBool old = this->timeIn.enableNotify(TRUE);
403 assert(old == FALSE);
404 which = &this->timeIn; // warning, hack
405 } else {
406 // enable to wait for timeIn to be >= starttime
407 this->timeIn.enableNotify(TRUE);
408 }
409 }
410 }
411
412 if (which == &this->timeIn) {
413 double currtime = this->timeIn.getValue().getValue();
414 if (!PRIVATE(this)->running) {
415 if (currtime >= PRIVATE(this)->starttime) {
416 this->isActive.enable(TRUE);
417 this->cycleTime.enable(TRUE);
418 PRIVATE(this)->cyclestart = PRIVATE(this)->starttime;
419 PRIVATE(this)->running = TRUE;
420 }
421 else return; // wait for startTime
422 }
423 PRIVATE(this)->currtime = currtime;
424 this->time.enable(TRUE);
425 this->fraction_changed.enable(TRUE);
426
427 SbBool stopit = FALSE;
428 if (currtime >= PRIVATE(this)->stoptime && PRIVATE(this)->stoptime > PRIVATE(this)->starttime) stopit = TRUE;
429
430 double difftime = currtime - PRIVATE(this)->cyclestart;
431
432 if (difftime > PRIVATE(this)->cycletime) {
433 this->cycleTime.enable(TRUE);
434 double num = difftime / PRIVATE(this)->cycletime;
435 PRIVATE(this)->cyclestart += PRIVATE(this)->cycletime * floor(num);
436 difftime = currtime - PRIVATE(this)->cyclestart;
437
438 if (PRIVATE(this)->loop == FALSE) stopit = TRUE;
439 }
440 PRIVATE(this)->fraction = (float) (difftime / PRIVATE(this)->cycletime);
441
442 if (stopit) {
443 PRIVATE(this)->running = FALSE;
444 this->isActive.enable(TRUE);
445 this->fraction_changed.enable(FALSE);
446 this->timeIn.enableNotify(FALSE);
447 }
448 }
449 else if (which == &this->stopTime) {
450 PRIVATE(this)->stoptime = this->stopTime.getValue().getValue();
451 }
452 else if (which == &this->cycleInterval) {
453 PRIVATE(this)->cycletime = this->cycleInterval.getValue().getValue();
454 }
455 }
456
457 // Doc in parent
458 void
handleEvent(SoHandleEventAction * action)459 SoVRMLTimeSensor::handleEvent(SoHandleEventAction * action)
460 {
461 inherited::handleEvent(action);
462 }
463
464 #undef PRIVATE
465
466 #endif // HAVE_VRML97
467