timeOfDay.cpp

Engine/source/environment/timeOfDay.cpp

More...

Public Functions

ConsoleDocClass(TimeOfDay , "@brief Environmental object that triggers <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> day/night cycle in <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">level.\n\n</a>" "@note <a href="/coding/class/classtimeofday/">TimeOfDay</a> only works in Advanced Lighting with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Sub object or <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ScatterSky\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classtimeofday/">TimeOfDay</a>(tod)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   axisTilt = \"23.44\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   dayLength = \"120\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   startTime = \"0.15\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   time = \"0.15\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   play = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   azimuthOverride = \"572.958\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   dayScale = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   nightScale = \"1.5\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   position = \"598.399 550.652 196.297\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   rotation = \"1 0 0 0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   scale = \"1 1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   canSave = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup enviroMisc" )
DefineEngineMethod(TimeOfDay , addTimeOfDayEvent , void , (F32 elevation, const char *identifier) , "" )
DefineEngineMethod(TimeOfDay , animate , void , (F32 elevation, F32 degreesPerSecond) , "" )
DefineEngineMethod(TimeOfDay , setDayLength , void , (F32 seconds) , "" )
DefineEngineMethod(TimeOfDay , setPlay , void , (bool enabled) , "" )
DefineEngineMethod(TimeOfDay , setTimeOfDay , void , (F32 time) , "" )

Detailed Description

Public Functions

ConsoleDocClass(TimeOfDay , "@brief Environmental object that triggers <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> day/night cycle in <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">level.\n\n</a>" "@note <a href="/coding/class/classtimeofday/">TimeOfDay</a> only works in Advanced Lighting with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Sub object or <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ScatterSky\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classtimeofday/">TimeOfDay</a>(tod)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   axisTilt = \"23.44\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   dayLength = \"120\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   startTime = \"0.15\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   time = \"0.15\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   play = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   azimuthOverride = \"572.958\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   dayScale = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   nightScale = \"1.5\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   position = \"598.399 550.652 196.297\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   rotation = \"1 0 0 0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   scale = \"1 1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   canSave = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup enviroMisc" )

DefineEngineMethod(TimeOfDay , addTimeOfDayEvent , void , (F32 elevation, const char *identifier) , "" )

DefineEngineMethod(TimeOfDay , animate , void , (F32 elevation, F32 degreesPerSecond) , "" )

DefineEngineMethod(TimeOfDay , setDayLength , void , (F32 seconds) , "" )

DefineEngineMethod(TimeOfDay , setPlay , void , (bool enabled) , "" )

DefineEngineMethod(TimeOfDay , setTimeOfDay , void , (F32 time) , "" )

IMPLEMENT_CO_NETOBJECT_V1(TimeOfDay )

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "environment/timeOfDay.h"
 26
 27#include "console/consoleTypes.h"
 28#include "core/stream/bitStream.h"
 29#include "T3D/gameBase/gameConnection.h"
 30#include "environment/sun.h"
 31#include "console/engineAPI.h"
 32
 33
 34TimeOfDayUpdateSignal TimeOfDay::smTimeOfDayUpdateSignal;
 35
 36IMPLEMENT_CO_NETOBJECT_V1(TimeOfDay);
 37
 38ConsoleDocClass( TimeOfDay,
 39   "@brief Environmental object that triggers a day/night cycle in level.\n\n"
 40
 41   "@note TimeOfDay only works in Advanced Lighting with a Sub object or ScatterSky\n\n"
 42
 43   "@tsexample\n"
 44   "new TimeOfDay(tod)\n"
 45   "{\n"
 46   "   axisTilt = \"23.44\";\n"
 47   "   dayLength = \"120\";\n"
 48   "   startTime = \"0.15\";\n"
 49   "   time = \"0.15\";\n"
 50   "   play = \"0\";\n"
 51   "   azimuthOverride = \"572.958\";\n"
 52   "   dayScale = \"1\";\n"
 53   "   nightScale = \"1.5\";\n"
 54   "   position = \"598.399 550.652 196.297\";\n"
 55   "   rotation = \"1 0 0 0\";\n"
 56   "   scale = \"1 1 1\";\n"
 57   "   canSave = \"1\";\n"
 58   "   canSaveDynamicFields = \"1\";\n"
 59   "};\n"
 60   "@endtsexample\n\n"
 61   "@ingroup enviroMisc"
 62);
 63
 64TimeOfDay::TimeOfDay() 
 65   :  mStartTimeOfDay( 0.5f ),   // High noon
 66      mDayLen( 120.0f ),         // 2 minutes
 67      mAxisTilt( 23.44f ),       // 35 degree tilt
 68      mAzimuth( 0.0f ),
 69      mElevation( 0.0f ),
 70      mTimeOfDay( 0.0f ),        // initialized to StartTimeOfDay in onAdd
 71      mDayScale( 1.0f ),
 72      mPlay( true ),
 73      mNightScale( 1.5f ),
 74      mAnimateTime( 0.0f ),
 75      mAnimateSpeed( 0.0f ),
 76      mAnimate( false )
 77{
 78   mNetFlags.set( Ghostable | ScopeAlways );
 79   mTypeMask = EnvironmentObjectType;
 80
 81   // Sets the sun vector directly overhead for lightmap generation
 82   // The value of mSunVector is grabbed by the terrain lighting stuff.
 83   /*
 84   F32 ele, azi;
 85   ele = azi = TORADIANS(90);
 86   MathUtils::getVectorFromAngles(mSunVector, azi, ele);
 87   */
 88   mPrevElevation = 0;
 89   mNextElevation = 0;
 90   mAzimuthOverride = 1.0f;
 91
 92   _initColors();
 93}
 94
 95TimeOfDay::~TimeOfDay()
 96{
 97}
 98
 99bool TimeOfDay::setTimeOfDay( void *object, const char *index, const char *data )
100{
101   TimeOfDay *tod = static_cast<TimeOfDay*>(object);
102   tod->setTimeOfDay( dAtof( data ) );
103
104   return false;
105}
106
107bool TimeOfDay::setPlay( void *object, const char *index, const char *data )
108{
109   TimeOfDay *tod = static_cast<TimeOfDay*>(object);
110   tod->setPlay( dAtob( data ) );
111
112   return false;
113}
114
115bool TimeOfDay::setDayLength( void *object, const char *index, const char *data )
116{
117   TimeOfDay *tod = static_cast<TimeOfDay*>(object);
118   F32 length = dAtof( data );
119   if( length != 0 )
120      tod->setDayLength( length );
121
122   return false;
123
124}
125
126void TimeOfDay::initPersistFields()
127{
128     addGroup( "TimeOfDay" );
129
130      addField( "axisTilt", TypeF32, Offset( mAxisTilt, TimeOfDay ),
131            "The angle in degrees between global equator and tropic." );
132
133      addProtectedField( "dayLength", TypeF32, Offset( mDayLen, TimeOfDay ), &setDayLength, &defaultProtectedGetFn,
134            "The length of a virtual day in real world seconds." );
135
136      addField( "startTime", TypeF32, Offset( mStartTimeOfDay, TimeOfDay ),
137         "" );
138
139      addProtectedField( "time", TypeF32, Offset( mTimeOfDay, TimeOfDay ), &setTimeOfDay, &defaultProtectedGetFn, "Current time of day." );
140
141      addProtectedField( "play", TypeBool, Offset( mPlay, TimeOfDay ), &setPlay, &defaultProtectedGetFn, "True when the TimeOfDay object is operating." );
142
143      addField( "azimuthOverride", TypeF32, Offset( mAzimuthOverride, TimeOfDay ), "" );
144
145      addField( "dayScale", TypeF32, Offset( mDayScale, TimeOfDay ), "Scalar applied to time that elapses while the sun is up." );
146
147      addField( "nightScale", TypeF32, Offset( mNightScale, TimeOfDay ), "Scalar applied to time that elapses while the sun is down." );
148
149   endGroup( "TimeOfDay" );
150
151   Parent::initPersistFields();
152}
153
154void TimeOfDay::consoleInit()
155{
156   Parent::consoleInit();
157
158   //addVariable( "$TimeOfDay::currentTime", &TimeOfDay::smCurrentTime );
159   //addVariable( "$TimeOfDay::timeScale", TypeF32, &TimeOfDay::smTimeScale );
160}
161
162void TimeOfDay::inspectPostApply()
163{
164   _updatePosition();
165   setMaskBits( OrbitMask );
166}
167
168void TimeOfDay::_onGhostAlwaysDone()
169{
170   _updatePosition();
171}
172
173bool TimeOfDay::onAdd()
174{
175   if ( !Parent::onAdd() )
176      return false;
177   
178   // The server initializes to the specified starting values.
179   // The client initializes itself to the server time from
180   // unpackUpdate.
181   if ( isServerObject() )
182   {
183      mTimeOfDay = mStartTimeOfDay;
184      _updatePosition();
185   }
186
187   // We don't use a bounds.
188   setGlobalBounds();
189   resetWorldBox();
190   addToScene();
191
192   // Lets receive ghost events so we can resolve
193   // the sun object.
194   if ( isClientObject() )
195      NetConnection::smGhostAlwaysDone.notify( this, &TimeOfDay::_onGhostAlwaysDone );
196
197   if ( isServerObject() )   
198      Con::executef( this, "onAdd" );   
199
200   setProcessTick( true );
201
202   return true;
203}
204
205void TimeOfDay::onRemove()
206{
207   if ( isClientObject() )
208      NetConnection::smGhostAlwaysDone.remove( this, &TimeOfDay::_onGhostAlwaysDone );
209
210   removeFromScene();
211   Parent::onRemove();
212}
213
214U32 TimeOfDay::packUpdate(NetConnection *conn, U32 mask, BitStream *stream )
215{
216   U32 retMask = Parent::packUpdate( conn, mask, stream );
217
218   if ( stream->writeFlag( mask & OrbitMask ) )
219   {
220      stream->write( mStartTimeOfDay );
221      stream->write( mDayLen );
222      stream->write( mTimeOfDay );
223      stream->write( mAxisTilt );
224      stream->write( mAzimuthOverride );
225
226      stream->write( mDayScale );
227      stream->write( mNightScale );
228
229      stream->writeFlag( mPlay );
230   }
231
232   if ( stream->writeFlag( mask & AnimateMask ) )
233   {
234      stream->write( mAnimateTime );
235      stream->write( mAnimateSpeed );
236   }
237
238   return retMask;
239}
240
241void TimeOfDay::unpackUpdate( NetConnection *conn, BitStream *stream )
242{
243   Parent::unpackUpdate( conn, stream );
244
245   if ( stream->readFlag() ) // OrbitMask
246   {
247      stream->read( &mStartTimeOfDay );
248      stream->read( &mDayLen );
249      stream->read( &mTimeOfDay );
250      stream->read( &mAxisTilt );
251      stream->read( &mAzimuthOverride );
252
253      stream->read( &mDayScale );
254      stream->read( &mNightScale );
255
256      mPlay = stream->readFlag();
257
258      _updatePosition();
259   }
260
261   if ( stream->readFlag() ) // AnimateMask
262   {
263      F32 time, speed;
264      stream->read( &time );
265      stream->read( &speed );
266
267      if( isProperlyAdded() )
268         animate( time, speed );
269   }
270}
271
272void TimeOfDay::processTick( const Move *move )
273{
274   if ( mAnimate )
275   {
276      F32 current = mTimeOfDay * 360.0f;      
277      F32 next = current + (mAnimateSpeed * TickSec);
278
279      // Protect for wrap around.
280      while ( next > 360.0f )
281         next -= 360.0f;
282
283      // Clamp to make sure we don't pass the target time.
284      if ( next >= mAnimateTime )
285      {
286         next = mAnimateTime;
287         mAnimate = false;
288      }
289
290      // Set the new time of day.
291      mTimeOfDay = next / 360.0f;
292
293      _updatePosition();
294      _updateTimeEvents();
295
296      if ( !mAnimate && isServerObject() )
297         Con::executef( this, "onAnimateDone" );
298   }
299   else if ( mPlay )
300   {
301      F32 dt = TickSec;
302      F32 current = mRadToDeg( mNextElevation );
303
304      if ( current > 350.0f || ( 0.0f <= current && current < 190.0f ) )
305         dt *= mDayScale;
306      else
307         dt *= mNightScale;
308
309      mTimeOfDay += dt / mDayLen;
310
311      // It could be possible for more than a full day to 
312      // pass in a single advance time, so I put this inside a loop
313      // but timeEvents will not actually be called for the
314      // skipped day.
315      while ( mTimeOfDay > 1.0f )
316         mTimeOfDay -= 1.0f;
317
318      _updatePosition();
319      _updateTimeEvents();
320   }
321   else
322      _updatePosition();
323}
324
325void TimeOfDay::_updatePosition()
326{
327   mPrevElevation = mNextElevation;
328
329   if ( mFabs( mAzimuthOverride ) )
330   {
331      mElevation = mDegToRad( mTimeOfDay * 360.0f );
332      mAzimuth = mAzimuthOverride;
333
334      mNextElevation = mElevation;  // already normalized
335   }
336   else
337   {
338      //// Full azimuth/elevation calculation.
339      //// calculate sun decline and meridian angle (in radians)
340      //F32 sunDecline = mSin( M_2PI * mTimeOfYear ) * mDegToRad( mAxisTilt );
341      //F32 meridianAngle = mTimeOfDay * M_2PI - mDegToRad( mLongitude );
342
343      //// calculate the elevation and azimuth (in radians)
344      //mElevation = _calcElevation( mDegToRad( mLatitude ), sunDecline, meridianAngle );
345      //mAzimuth = _calcAzimuth( mDegToRad( mLatitude ), sunDecline, meridianAngle );
346
347      // Simplified azimuth/elevation calculation.
348      // calculate sun decline and meridian angle (in radians)
349      F32 sunDecline = mDegToRad( mAxisTilt );
350      F32 meridianAngle = mTimeOfDay * M_2PI;
351
352      // calculate the elevation and azimuth (in radians)
353      mElevation = _calcElevation( 0.0f, sunDecline, meridianAngle );
354      mAzimuth = _calcAzimuth( 0.0f, sunDecline, meridianAngle );
355
356      // calculate 'normalized' elevation (0=sunrise, PI/2=zenith, PI=sunset, 3PI/4=nadir)
357      F32 normElevation = M_PI_F * mElevation / ( 2 * _calcElevation( 0.0f, sunDecline, 0.0f ) );
358      if ( mAzimuth > M_PI_F )
359         normElevation = M_PI_F - normElevation;
360      else if ( mElevation < 0 )
361         normElevation = M_2PI_F + normElevation;
362
363      mNextElevation = normElevation;
364   }
365
366   // Only the client updates the sun position!
367   if ( isClientObject() )
368      smTimeOfDayUpdateSignal.trigger( this, mTimeOfDay );
369}
370
371F32 TimeOfDay::_calcElevation( F32 lat, F32 dec, F32 mer )
372{
373   return mAsin( mSin(lat) * mSin(dec) + mCos(lat) * mCos(dec) * mCos(mer) );
374}
375
376F32 TimeOfDay::_calcAzimuth( F32 lat, F32 dec, F32 mer )
377{
378   // Add PI to normalize this from the range of -PI/2 to PI/2 to 0 to 2 * PI;
379     return mAtan2( mSin(mer), mCos(mer) * mSin(lat) - mTan(dec) * mCos(lat) ) + M_PI_F;
380}
381
382void TimeOfDay::_getSunColor( LinearColorF *outColor ) const
383{
384     const COLOR_TARGET *ct = NULL;
385
386   F32 ele = mClampF( M_2PI_F - mNextElevation, 0.0f, M_PI_F );
387     F32 phase = -1.0f;
388     F32 div;
389
390   if (!mColorTargets.size())
391   {
392      outColor->set(1.0f,1.0f,1.0f);
393      return;
394   }
395
396   if (mColorTargets.size() == 1)
397   {
398      ct = &mColorTargets[0];
399      outColor->set(ct->color.red, ct->color.green, ct->color.blue);
400      return;
401   }
402
403   //simple check
404   if ( mColorTargets[0].elevation != 0.0f )
405   {
406      AssertFatal(0, "TimeOfDay::GetColor() - First elevation must be 0.0 radians");
407      outColor->set(1.0f, 1.0f, 1.0f);
408      //mBandMod = 1.0f;
409      //mCurrentBandColor = color;
410      return;
411   }
412
413   if ( mColorTargets[mColorTargets.size()-1].elevation != M_PI_F )
414   {
415      AssertFatal(0, "Celestails::GetColor() - Last elevation must be PI");
416      outColor->set(1.0f, 1.0f, 1.0f);
417      //mBandMod = 1.0f;
418      //mCurrentBandColor = color;
419      return;
420   }
421
422   //we need to find the phase and interp... also loop back around
423   U32 count=0;
424   for (;count < mColorTargets.size() - 1; count++)
425   {
426      const COLOR_TARGET *one = &mColorTargets[count];
427      const COLOR_TARGET *two = &mColorTargets[count+1];
428
429      if (ele >= one->elevation && ele <= two->elevation)
430      {
431               div = two->elevation - one->elevation;
432         
433         //catch bad input divide by zero
434         if ( mFabs( div ) < 0.01f )
435            div = 0.01f;
436         
437               phase = (ele - one->elevation) / div;
438               outColor->interpolate( one->color, two->color, phase );
439
440               //mCurrentBandColor.interpolate(one->bandColor, two->bandColor, phase);
441               //mBandMod = one->bandMod * (1.0f - phase) + two->bandMod * phase;
442
443               return;
444          }
445     }
446
447     AssertFatal(0,"This isn't supposed to happen");
448}
449
450void TimeOfDay::_initColors()
451{
452   // NOTE: The elevation targets represent distances 
453   // from PI/2 radians (strait up).
454
455   LinearColorF c;
456   LinearColorF bc;
457
458   // e is for elevation
459   F32 e = M_PI_F / 13.0f; // (semicircle in radians)/(number of color target entries);
460
461   // Day
462   c.set(1.0f,1.0f,1.0f);
463   _addColorTarget(0, c, 1.0f, c); // High noon at equanox
464   c.set(.9f,.9f,.9f);
465   _addColorTarget(e * 1.0f, c, 1.0f, c);
466   c.set(.9f,.9f,.9f);
467   _addColorTarget(e * 2.0f, c, 1.0f, c);
468   c.set(.8f,.75f,.75f);
469   _addColorTarget(e * 3.0f, c, 1.0f, c);
470   c.set(.7f,.65f,.65f);
471   _addColorTarget(e * 4.0f, c, 1.0f, c);
472
473   //Dawn and Dusk (3 entries)
474   c.set(.7f,.65f,.65f);
475   bc.set(.8f,.6f,.3f);
476   _addColorTarget(e * 5.0f, c, 3.0f, bc);
477   c.set(.65f,.54f,.4f);
478   bc.set(.75f,.5f,.4f);
479   _addColorTarget(e * 6.0f, c, 2.75f, bc);
480   c.set(.55f,.45f,.25f);
481   bc.set(.65f,.3f,.3f);
482   _addColorTarget(e * 7.0f, c, 2.5f, bc);
483
484   //NIGHT
485   c.set(.3f,.3f,.3f);
486   bc.set(.7f,.4f,.2f);
487   _addColorTarget(e * 8.0f, c, 1.25f, bc);
488   c.set(.25f,.25f,.3f);
489   bc.set(.8f,.3f,.2f);
490   _addColorTarget(e * 9.0f, c, 1.00f, bc);
491   c.set(.25f,.25f,.4f);
492   _addColorTarget(e * 10.0f, c, 1.0f, c);
493   c.set(.2f,.2f,.35f);
494   _addColorTarget(e * 11.0f, c, 1.0f, c);
495   c.set(.15f,.15f,.2f);
496   _addColorTarget(M_PI_F, c, 1.0f, c); // Midnight at equanox.
497}
498
499void TimeOfDay::_addColorTarget( F32 ele, const LinearColorF &color, F32 bandMod, const LinearColorF &bandColor )
500{
501   COLOR_TARGET  newTarget;
502
503   newTarget.elevation = ele;
504   newTarget.color = color;
505   newTarget.bandMod = bandMod;
506   newTarget.bandColor = bandColor;
507
508   mColorTargets.push_back(newTarget);
509}
510
511void TimeOfDay::_updateTimeEvents()
512{
513   if ( mTimeEvents.empty() )
514      return;
515
516   // Get the prev, next elevation in degrees since TimeOfDayEvent is specified
517   // in degrees.
518   F32 prevElevation = mRadToDeg( mPrevElevation );
519   F32 nextElevation = mRadToDeg( mNextElevation );
520
521   // If prevElevation is less than nextElevation then its the next day.
522   // Unroll it so we can just loop forward in time and simplify our loop.
523   if ( nextElevation < prevElevation )
524      nextElevation += 360.0f;
525
526   const U32 evtCount = mTimeEvents.size();
527
528   // Find where in the event list we need to start...
529   // The first timeEvent with elevation greater than our previous elevation.
530   
531   U32 start = 0;   
532   for ( ; start < evtCount; start++ )
533   {
534      if ( mTimeEvents[start].triggerElevation > prevElevation )
535         break;
536   }
537
538   bool onNextDay = false;
539
540   // Nothing between prevElevation and the end of the day...
541   // Check between start of the day and nextElevation...
542   if ( start == evtCount )
543   {
544      start = 0;
545      for ( ; start < evtCount; start++ )
546      {
547         if ( mTimeEvents[start].triggerElevation <= nextElevation )
548         {
549            onNextDay = true;
550            break;
551         }
552      }
553   }
554
555   // No events were hit...
556   if ( start == evtCount )
557      return;
558
559   U32 itr = start;
560   while ( true )
561   {
562      TimeOfDayEvent &timeEvent = mTimeEvents[itr];
563      
564      F32 elev = timeEvent.triggerElevation;
565      if ( onNextDay )
566         elev += 360.0f;
567
568      // Hit an event that happens later after nextElevation so we
569      // have checked everything within the range and are done.
570      if ( elev > nextElevation )
571         break;
572
573      // If its not greater than the nextElevation it must be less, and if
574      // we are here we already know its greater than prevElevation.
575      
576      AssertFatal( elev >= prevElevation && elev <= nextElevation, "TimeOfDay::_updateTimeEvents - Logical error in here!" );
577      AssertFatal( !timeEvent.deleteMe, "TimeOfDay::_updateTimeEvents - tried to fire the same event twice!" );
578
579      _onTimeEvent( timeEvent.identifier );
580
581      if ( timeEvent.oneShot )
582         timeEvent.deleteMe = true;
583
584      // On to the next time event...
585      itr++;
586
587      // We hit the end of the day?
588      if ( itr == evtCount )
589      {
590         // We are already on the next day so we have checked everything.
591         if ( onNextDay )
592            break;
593         // Check events for the next day
594         else
595         {            
596            itr = 0;
597            onNextDay = true;
598         }         
599      }
600   }
601
602   // Cleanup one-shot events that fired...
603
604   for ( S32 i = 0; i < mTimeEvents.size(); i++ )
605   {
606      if ( mTimeEvents[i].deleteMe )
607      {
608         // Don't use erase_fast, there are ordered.
609         mTimeEvents.erase( i );
610         i--;
611      }
612   }
613}
614
615void TimeOfDay::addTimeEvent( F32 triggerElevation, const UTF8 *identifier )
616{
617   // Insert in ascending order of elevation.
618   // Note that having more than one TimeEvent with the same triggerElevation
619   // may cause undefined behavior.
620
621   TimeOfDayEvent *pEvent = NULL;
622   
623   if ( mTimeEvents.empty() || mTimeEvents.last().triggerElevation <= triggerElevation )
624   {
625      mTimeEvents.increment();
626      pEvent = &mTimeEvents.last();
627   }
628   else 
629   {   
630      for ( S32 i = 0; i < mTimeEvents.size(); i++ )
631      {
632         if ( mTimeEvents[i].triggerElevation > triggerElevation )
633         {
634            mTimeEvents.insert( i );
635            pEvent = &mTimeEvents[i];
636            break;
637         }
638      }
639   }
640
641   AssertFatal( pEvent, "TimeOfDay::addTimeEvent - could not find place to insert event." );
642
643   pEvent->triggerElevation = triggerElevation;
644   pEvent->identifier = identifier;
645   pEvent->oneShot = false;
646      
647   pEvent->deleteMe = false;
648}
649
650void TimeOfDay::setTimeOfDay( F32 time )
651{ 
652   mTimeOfDay = time;
653
654   while ( mTimeOfDay > 1.0f )
655      mTimeOfDay -= 1.0f;
656   while ( mTimeOfDay < 0.0f )
657      mTimeOfDay += 1.0f;
658
659   _updatePosition();
660
661   //if ( isServerObject() )
662   _updateTimeEvents();
663
664   setMaskBits( OrbitMask ); 
665}
666
667void TimeOfDay::_onTimeEvent( const String &identifier )
668{
669   // Client doesn't do onTimeEvent callbacks.
670   if ( isClientObject() )
671      return;
672
673   String strCurrentTime = String::ToString( "%g", mTimeOfDay );
674
675   F32 elevation = mRadToDeg( mNextElevation );
676   while( elevation < 0 )
677      elevation += 360.0f;
678   while( elevation > 360.0f )
679      elevation -= 360.0f;
680
681   String strCurrentElevation = String::ToString( "%g", elevation );
682
683   Con::executef( this, "onTimeEvent", identifier.c_str(), strCurrentTime.c_str(), strCurrentElevation.c_str() );
684}
685
686void TimeOfDay::animate( F32 time, F32 speed )
687{
688   // Stop any existing animation... this one
689   // becomes the new one.
690   mAnimate = false;
691
692   // Set the target time to hit.
693   mAnimateTime = mClamp(time, 0.0f, 360.0f);
694
695   F32 current = mTimeOfDay * 360.0f;
696   F32 target = mAnimateTime;
697   if ( target < current )
698      target += 360.0f;
699
700   // If we're already at the current time then
701   // we have nothing more to do... the animation is here.
702   F32 dif = target - current;
703   if ( mIsZero( dif ) )
704      return;
705
706   // Start playback.
707   mAnimateSpeed = speed;
708   mAnimate = true;
709
710   if ( isServerObject() )
711   {
712      Con::executef( this, "onAnimateStart" );
713      setMaskBits( AnimateMask );
714   }
715}
716
717DefineEngineMethod( TimeOfDay, addTimeOfDayEvent, void, (F32 elevation, const char *identifier ),,
718   "" )
719{
720   object->addTimeEvent( elevation, identifier );
721}
722
723DefineEngineMethod( TimeOfDay, setTimeOfDay, void, ( F32 time ),,
724   "" )
725{
726   object->setTimeOfDay( time );
727}
728
729DefineEngineMethod( TimeOfDay, setPlay, void, ( bool enabled ),,
730   "")
731{
732   object->setPlay( enabled );
733}
734
735DefineEngineMethod( TimeOfDay, setDayLength, void, ( F32 seconds ),,
736   "" )
737{
738   if ( seconds > 0.0f )
739      object->setDayLength( seconds );
740}
741
742DefineEngineMethod( TimeOfDay, animate, void, ( F32 elevation, F32 degreesPerSecond ),,
743   "")
744{
745   object->animate( elevation, degreesPerSecond );
746}
747