/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoTimeCounter SoTimeCounter.h Inventor/engines/SoTimeCounter.h \brief The SoTimeCounter class is an integer counter engine. \ingroup engines The SoTimeCounter engine is by default connected to the realTime global field, and does stepwise updates on the SoTimeCounter::output field according to the field settings. */ /*! \var SoSFTime SoTimeCounter::timeIn Running time. Connected to the \e realTime field by default. */ /*! \var SoSFShort SoTimeCounter::min Minimum counter value. Default value is 0. */ /*! \var SoSFShort SoTimeCounter::max Maximum counter value. Default value is 1. */ /*! \var SoSFShort SoTimeCounter::step Counter step size. Default value is 1. */ /*! \var SoSFBool SoTimeCounter::on Set to \c FALSE to pause the counter. Default value is \c TRUE. */ /*! \var SoSFFloat SoTimeCounter::frequency Number of complete cycles from the min value to the max value per second. Default value is 1.0. */ /*! \var SoMFFloat SoTimeCounter::duty Used to weight step times. Supply one weight value per step. Default array is a single value of 1.0. */ /*! \var SoSFShort SoTimeCounter::reset Manually set the counter to some value. If SoTimeCounter::reset is set below the SoTimeCounter::min value or above the SoTimeCounter::max value it will be clamped to the closest boundary value. */ /*! \var SoSFTrigger SoTimeCounter::syncIn Restart counter at the minimum value. */ /*! \var SoEngineOutput SoTimeCounter::output (SoSFShort) The counter value. */ /*! \var SoEngineOutput SoTimeCounter::syncOut (SoSFTrigger) Triggers every cycle start. */ // ************************************************************************* #include #include #include #include #include #include // ************************************************************************* SO_ENGINE_SOURCE(SoTimeCounter); // ************************************************************************* /*! Default constructor. */ SoTimeCounter::SoTimeCounter(void) { SO_ENGINE_INTERNAL_CONSTRUCTOR(SoTimeCounter); SO_ENGINE_ADD_INPUT(timeIn, (SbTime::zero())); SO_ENGINE_ADD_INPUT(min, (0)); SO_ENGINE_ADD_INPUT(max, (1)); SO_ENGINE_ADD_INPUT(step, (1)); SO_ENGINE_ADD_INPUT(on, (TRUE)); SO_ENGINE_ADD_INPUT(frequency, (1.0f)); SO_ENGINE_ADD_INPUT(duty, (1.0f)); SO_ENGINE_ADD_INPUT(reset, (0)); SO_ENGINE_ADD_INPUT(syncIn, ()); SO_ENGINE_ADD_OUTPUT(output, SoSFShort); SO_ENGINE_ADD_OUTPUT(syncOut, SoSFTrigger); this->syncOut.enable(FALSE); SoField * realtime = SoDB::getGlobalField("realTime"); this->starttime = ((SoSFTime *)realtime)->getValue().getValue(); this->firstoutputenable = TRUE; this->outputvalue = 0; this->cyclelen = 1.0; this->numsteps = 2; this->stepnum = 0; this->ispaused = FALSE; this->timeIn.connectFrom(realtime); } // Documented in superclass. void SoTimeCounter::initClass(void) { SO_ENGINE_INTERNAL_INIT_CLASS(SoTimeCounter); } /*! Destructor. */ SoTimeCounter::~SoTimeCounter() { } // ************************************************************************* // Documented in superclass. Overridden to not write connection to // realTime global field. void SoTimeCounter::writeInstance(SoOutput * out) { // Note: the code in this method matches that of SoElapsedTime and // SoOneShot, so if any bugs are found and corrected, remember to // pass on the updates. // Disconnect from realTime field. SoField * connectfield = NULL; SbBool connectfromrealTime = this->timeIn.getConnectedField(connectfield) && connectfield == SoDB::getGlobalField("realTime"); SbBool defaultflag = this->timeIn.isDefault(); if (connectfromrealTime) { this->timeIn.disconnect(); this->timeIn.setDefault(TRUE); } inherited::writeInstance(out); // Re-connect to realTime field. if (connectfromrealTime) { // Don't send notification when reconnecting to preserve the state // of the scenegraph between write passes. this->timeIn.connectFrom(connectfield, TRUE); this->timeIn.setDefault(defaultflag); } } // doc in parent void SoTimeCounter::evaluate(void) { if (!this->ispaused) { // FIXME: the code calculating the output value is a // mess. 20000919 mortene. double currtime = this->timeIn.getValue().getValue(); double difftime = currtime - this->starttime; if (difftime > this->cyclelen) { double num = difftime / this->cyclelen; this->starttime += this->cyclelen * floor(num); difftime = currtime - this->starttime; } short value = this->findOutputValue(difftime); if (value == this->outputvalue + this->step.getValue()) { // common case this->stepnum++; } else { // either reset, wrap-around or a delay somewhere short offset = value - this->min.getValue(); this->stepnum = offset / this->step.getValue(); } this->outputvalue = value; } // Force update on slave fields (SO_ENGINE_OUTPUT checks // isEnabled()-value, and we want the setValue() to happen anyway). this->output.enable(TRUE); SO_ENGINE_OUTPUT(output, SoSFShort, setValue(this->outputvalue)); // The isEnabled() flag will be set back to FALSE again upon the // next invocation of SoTimeCounter::inputChanged(). } // doc in parent void SoTimeCounter::inputChanged(SoField * which) { // Default is to not do any notification when we return from this // function to SoEngine::notify(). This is an optimization for this // engine to avoid transmission of notification to all slave fields // each time the timeIn field is updated. this->output.enable(FALSE); this->syncOut.enable(FALSE); // Enable outputs on first call. if (this->firstoutputenable) { this->firstoutputenable = FALSE; this->output.enable(TRUE); this->syncOut.enable(TRUE); } // First handle the case where the timeIn field has been changed, as // we need to optimize that to avoid too many notifications on the // slave fields (we only want to be notified when the output counter // value actually changes). if (which == &this->timeIn) { if (this->ispaused) return; double currtime = this->timeIn.getValue().getValue(); double difftime = currtime - this->starttime; if (difftime > this->cyclelen) { // Trigger syncOut once at start of cycle. this->syncOut.enable(TRUE); double num = difftime / this->cyclelen; this->starttime += this->cyclelen * floor(num); difftime = currtime - this->starttime; } // Optimize for the common case. This prevents a notification on // each and every trigger for timeIn. if (this->findOutputValue(difftime) == this->outputvalue) return; } // FIXME: the code handling the other fields is a horrible // mess. 20000919 mortene. if (which == &this->on) { if (this->on.getValue() && this->ispaused) { this->starttime = this->timeIn.getValue().getValue() - this->pausetimeincycle; this->ispaused = FALSE; } else if (!this->on.getValue() && !this->ispaused) { this->ispaused = TRUE; this->pausetimeincycle = this->timeIn.getValue().getValue() - this->starttime; } } else if (which == &this->frequency) { this->cyclelen = 1.0 / this->frequency.getValue(); this->calcDutySteps(); } else if (which == &this->duty) { this->calcDutySteps(); } else if (which == &this->reset) { short minval = this->min.getValue(); short val = SbClamp(this->reset.getValue(), minval, this->max.getValue()); short offset = val - minval; short stepval = this->step.getValue(); if ((offset % stepval) != 0) { val = minval + (offset / stepval) * stepval; } this->calcStarttime(val); } else if (which == &this->syncIn) { this->starttime = this->timeIn.getValue().getValue(); } else if (which == &this->max) { if (this->max.getValue() < this->min.getValue()) this->min.setValue(this->max.getValue()); this->calcNumSteps(); this->calcDutySteps(); if (this->max.getValue() < this->outputvalue) { this->starttime = this->timeIn.getValue().getValue(); } } else if (which == &this->min) { if (this->max.getValue() < this->min.getValue()) this->max.setValue(this->min.getValue()); this->calcNumSteps(); this->calcDutySteps(); short value = this->min.getValue() + this->step.getValue() * this->stepnum; if (value > this->max.getValue()) { this->starttime = this->timeIn.getValue().getValue(); } } else if (which == &this->step) { this->calcNumSteps(); this->calcDutySteps(); short value = this->min.getValue() + this->step.getValue() * this->stepnum; if (value > this->max.getValue()) { this->starttime = this->timeIn.getValue().getValue(); } } // Either the timeIn field changed enough to reach a new step, or // some of the control fields changed -- so lets notify the slaves. // // Could also optimize for the case where changes to the control // fields doesn't yield an immediate change to the output value, but // that doesn't seem worthwhile. this->output.enable(TRUE); } // calculates # of steps in cycle void SoTimeCounter::calcNumSteps(void) { short stepval = this->step.getValue(); this->numsteps = (this->max.getValue() - this->min.getValue()) / SbAbs(stepval) + 1; } // recalculate duty steps. void SoTimeCounter::calcDutySteps(void) { if (this->duty.getNum() == this->numsteps) { int i; double dutysum = 0.0; for (i = 0; i < this->numsteps; i++) { dutysum += (double)this->duty[i]; } double currsum = 0.0; this->dutylimits.truncate(0); for (i = 0; i < this->numsteps; i++) { currsum += (double) this->duty[i]; this->dutylimits.append(currsum/dutysum * this->cyclelen); } } else { // ignore duty values this->dutylimits.truncate(0); } } // calculates current output value based on time in cycle short SoTimeCounter::findOutputValue(double timeincycle) const { assert(timeincycle <= cyclelen); short val; short minval = this->min.getValue(); short maxval = this->max.getValue(); short stepval = this->step.getValue(); if (this->dutylimits.getLength()) { // FIXME: use binary search if > 64 or something values int i = 0; for (i = 0; i < this->numsteps; i++) { if (timeincycle <= this->dutylimits[i]) break; } if (stepval >= 0) { val = minval + i * stepval; } else { val = maxval + i*stepval; } } else { double steptime = this->cyclelen / this->numsteps; if (stepval >= 0) { val = minval + (short)(timeincycle / steptime) * stepval; } else { val = maxval + (short)(timeincycle / steptime) * stepval; } if (val > maxval) val = maxval; if (val < minval) val = minval; } assert(val >= minval && val <= maxval); return val; } // calculates cycle starttime based on counter value and timeIn. // also sets stepnum. value must be in legal range void SoTimeCounter::calcStarttime(short value) { this->stepnum = (value - this->min.getValue()) / this->step.getValue(); if (this->dutylimits.getLength()) { this->starttime = this->timeIn.getValue().getValue() - this->dutylimits[this->stepnum]; } else { this->starttime = this->timeIn.getValue().getValue() - double(this->stepnum) * this->cyclelen / double(this->numsteps); } }