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