//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "terrain/sky.h"
#include "math/mMath.h"
#include "dgl/dgl.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "terrain/terrData.h"
#include "sim/sceneObject.h"
#include "math/mathIO.h"
#include "sceneGraph/windingClipper.h"
#include "platform/profiler.h"

// This dependency on game code needs to be removed.
#include "game/fx/particleEngine.h"

//
#define HORIZON         0.0f
#define OFFSET_HEIGHT   60.0f
#define FOG_BAN_DETAIL  8
#define RAD             (2 * M_PI)


IMPLEMENT_CO_NETOBJECT_V1(Sky);

//Static Sky variables
bool Sky::smCloudsOn       = true;
bool Sky::smCloudOutlineOn = false;
bool Sky::smSkyOn          = true;
S32  Sky::smNumCloudsOn    = MAX_NUM_LAYERS;

//Static Cloud variables
StormInfo Cloud::mGStormData;
F32 Cloud::mRadius;


//---------------------------------------------------------------------------
Sky::Sky()
{
   mNumCloudLayers = 0;
   mTypeMask |= EnvironmentObjectType;
   mNetFlags.set(Ghostable | ScopeAlways);

   mVisibleDistance = 250;
   mFogDistance     = 250;
   mSkyTexturesOn   = true;
   mRenderBoxBottom = false;
   mNumFogVolumes   = 0;
   mFogLine         = 0.0f;
   mRealFog         = 0;
   mFogColor.set(128, 128, 128);
   mSolidFillColor.set(0.0, 1.0, 0,0);
   mWindVelocity.set(1.f, 0.f, 0.f);
   mWindDir.set(0.f, 0.f);
   mEffectPrecip = false;
   mNoRenderBans = false;

   mLastVisDisMod = -1;

   for(U32 j = 0; j < MaxFogVolumes; j++)
   {
      mFogVolumes[j].visibleDistance = -1;
      mFogVolumes[j].percentage = 1.0f;

      mFogVolumes[j].color.red = mFogColor.red;
      mFogVolumes[j].color.green = mFogColor.green;
      mFogVolumes[j].color.blue = mFogColor.blue;
   }

   for(S32 i = 0; i < MAX_NUM_LAYERS; ++i)
   {
      mCloudText[i] = "";
      mCloudSpeed[i]  = 0.0001 * (1 + (i * 1));
      mCloudHeight[i] = 0;
   }

   mStormCloudData.state = isDone;
   mStormCloudData.speed = 0.0f;
   mStormCloudData.time  = 0.0f;

   mStormFogData.time = 0.0f;
   mStormFogData.current = 0;
   mStormFogData.lastTime = 0;
   mStormFogData.startTime = 0;
   mStormFogData.endPercentage = -1.0f;
   for(S32 x = 0; x < MaxFogVolumes; ++x)
   {
      mStormFogData.volume[x].active = false;
      mStormFogData.volume[x].state = isDone;
      mStormFogData.volume[x].speed = 0.0f;
      mStormFogData.volume[x].endPercentage = 1.0f;
   }
   mFogTime = 0.0f;
   mFogVolume = -1;
   mStormCloudsOn = true;
   mStormFogOn = false;
   mSetFog = true;

   mLastForce16Bit = false;
   mLastForcePaletted = false;
}

//------------------------------------------------------------------------------
Sky::~Sky()
{

}

//---------------------------------------------------------------------------
bool Sky::onAdd()
{
   if(!Parent::onAdd())
      return false;

   mObjBox.min.set(-1e9, -1e9, -1e9);
   mObjBox.max.set( 1e9,  1e9,  1e9);
   resetWorldBox();

   // Find out how many fog layers we have.
   for(mNumFogVolumes = 0; mNumFogVolumes < MaxFogVolumes; mNumFogVolumes++)
      if(mFogVolumes[mNumFogVolumes].visibleDistance == -1 || mFogVolumes[mNumFogVolumes].visibleDistance == 0)
         break;

   // Storm fog layers are off by default, others are on.
   for(S32 i = 0; i < mNumFogVolumes; ++i)
      mFogVolumes[i].percentage = mStormFogData.volume[i].active? 0.0f: 1.0f;

   if(isClientObject())
   {
      if(!loadDml())
         return false;

      initSkyData();
   }
   else
   {
      setWindVelocity(mWindVelocity);
      assignName("Sky");
   }

   addToScene();
   setVisibility();
   return true;
}

//---------------------------------------------------------------------------
void Sky::initSkyData()
{
   calcPoints();
   mWindDir = Point2F(mWindVelocity.x, mWindVelocity.y);
   mWindDir.normalize();
   for(S32 i = 0; i < MAX_NUM_LAYERS; ++i)
   {
      mCloudLayer[i].setHeights(mCloudHeight[i], mCloudHeight[i]-0.05f, 0.05f);
      mCloudLayer[i].setSpeed(mWindDir * mCloudSpeed[i]);
      mCloudLayer[i].setPoints();
   }
   setVisibility();
}

//---------------------------------------------------------------------------
void Sky::setVisibility()
{
   if(mSceneManager)
   {
      extern bool sgForce16BitTexture;
      extern bool sgForcePalettedTexture;
      mRealFogColor.red = S32(mCeil(mFogColor.red * 255.0f));
      mRealFogColor.green = S32(mCeil(mFogColor.green * 255.0f));
      mRealFogColor.blue = S32(mCeil(mFogColor.blue * 255.0f));
      mRealFogColor.alpha = 255;
      if(sgForce16BitTexture)
      {
         U8 temp = (mRealFogColor.red >> 4) & 0xF;
         mRealFogColor.red = (temp << 4) | temp;

         temp = (mRealFogColor.green >> 4) & 0xF;
         mRealFogColor.green = (temp << 4) | temp;

         temp = (mRealFogColor.blue >> 4) & 0xF;
         mRealFogColor.blue = (temp << 4) | temp;

         mSceneManager->setFogColor((ColorF)mRealFogColor);
      }
      else
         mSceneManager->setFogColor(mFogColor);

      mRealSkyColor.red = S32(mSolidFillColor.red * 255.0f);
      mRealSkyColor.green = S32(mSolidFillColor.green * 255.0f);
      mRealSkyColor.blue = S32(mSolidFillColor.blue * 255.0f);

      mSceneManager->setFogDistance(mFogDistance);
      mSceneManager->setVisibleDistance(mVisibleDistance);
      mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes);
      mLastForce16Bit = sgForce16BitTexture;
      mLastForcePaletted = sgForcePalettedTexture;
   }
}

//---------------------------------------------------------------------------
void Sky::setWindVelocity(const Point3F & vel)
{
   mWindVelocity = vel;
   ParticleEngine::setWindVelocity(vel);
   if(isServerObject())
      setMaskBits(WindMask);
}

Point3F Sky::getWindVelocity()
{
   return(mWindVelocity);
}

//---------------------------------------------------------------------------
void Sky::onRemove()
{
   removeFromScene();
   Parent::onRemove();
}

//---------------------------------------------------------------------------
ConsoleMethod( Sky, stormClouds, void, 4, 4, "(bool show, float duration)")
{
   object->stormCloudsOn(dAtoi(argv[2]), dAtof(argv[3]));
}

ConsoleMethod( Sky, stormFog, void, 4, 4, "(float percent, float duration)")
{
   object->stormFogOn(dAtof(argv[2]), dAtof(argv[3]));
}

ConsoleMethod( Sky, realFog, void, 6, 6, "( bool show, float max, float min, float speed )")
{
   object->stormRealFog(dAtoi(argv[2]), dAtof(argv[3]), dAtof(argv[4]), dAtof(argv[5]));
}

ConsoleMethod( Sky, getWindVelocity, const char *, 2, 2, "()")
{
   char * retBuf = Con::getReturnBuffer(128);

   Point3F vel = object->getWindVelocity();
   dSprintf(retBuf, 128, "%g %g %g", vel.x, vel.y, vel.z);
   return(retBuf);
}
//---------------------------------------------------------------------------

ConsoleMethod( Sky, setWindVelocity, void, 5, 5, "(float x, float y, float z)")
{
   if(object->isClientObject())
      return;

   Point3F vel(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
   object->setWindVelocity(vel);
}

ConsoleMethod( Sky, stormCloudsShow, void, 3, 3, "(bool showClouds)")
{
   object->stormCloudsShow(dAtob(argv[2]));
}

ConsoleMethod( Sky, stormFogShow, void, 3, 3, "(bool show)")
{
   object->stormFogShow(dAtob(argv[2]));
}

//---------------------------------------------------------------------------
bool Sky::processArguments(S32, const char**)
{
   return true;
}

//---------------------------------------------------------------------------
void Sky::initPersistFields()
{
   Parent::initPersistFields();

   addGroup("Media");	
   addField("materialList",            TypeFilename,  Offset(mMaterialListName,Sky));
   endGroup("Media");	

   addGroup("Clouds");	
   addField("cloudText",               TypeString,    Offset(mCloudText,Sky),MAX_NUM_LAYERS);
   addField("cloudHeightPer",          TypeF32,       Offset(mCloudHeight,Sky),MAX_NUM_LAYERS);
   addField("cloudSpeed1",             TypeF32,       Offset(mCloudSpeed[0],Sky));
   addField("cloudSpeed2",             TypeF32,       Offset(mCloudSpeed[1],Sky));
   addField("cloudSpeed3",             TypeF32,       Offset(mCloudSpeed[2],Sky));
   endGroup("Clouds");	

   addGroup("Visibility");	
   addField("visibleDistance",         TypeF32,       Offset(mVisibleDistance, Sky));
   endGroup("Visibility");	

   addGroup("Fog");	
   addField("fogDistance",             TypeF32,       Offset(mFogDistance, Sky));
   addField("fogColor",                TypeColorF,    Offset(mFogColor, Sky));
   // Should be using the console's array support for these...
   addField("fogStorm1",               TypeBool,      Offset(mStormFogData.volume[0].active, Sky));
   addField("fogStorm2",               TypeBool,      Offset(mStormFogData.volume[1].active, Sky));
   addField("fogStorm3",               TypeBool,      Offset(mStormFogData.volume[2].active, Sky));
   addField("fogVolume1",              TypePoint3F,   Offset(mFogVolumes[0], Sky));
   addField("fogVolume2",              TypePoint3F,   Offset(mFogVolumes[1], Sky));
   addField("fogVolume3",              TypePoint3F,   Offset(mFogVolumes[2], Sky));
   addField("fogVolumeColor1",         TypeColorF,    Offset(mFogVolumes[0].color, Sky));
   addField("fogVolumeColor2",         TypeColorF,    Offset(mFogVolumes[1].color, Sky));
   addField("fogVolumeColor3",         TypeColorF,    Offset(mFogVolumes[2].color, Sky));
   endGroup("Fog");	

   addGroup("Wind");	
   addField("windVelocity",            TypePoint3F,   Offset(mWindVelocity, Sky));
   addField("windEffectPrecipitation", TypeBool,      Offset(mEffectPrecip, Sky));
   endGroup("Wind");	

   addGroup("Misc");	
   addField("SkySolidColor",           TypeColorF,    Offset(mSolidFillColor, Sky));
   addField("useSkyTextures",          TypeBool,      Offset(mSkyTexturesOn, Sky));
   addField("renderBottomTexture",     TypeBool,      Offset(mRenderBoxBottom, Sky));
   addField("noRenderBans",            TypeBool,      Offset(mNoRenderBans, Sky));
   endGroup("Misc");	
}

void Sky::consoleInit()
{
#if defined(TORQUE_DEBUG)
   Con::addVariable("pref::CloudOutline", TypeBool, &smCloudOutlineOn);
#endif
   Con::addVariable("pref::CloudsOn",    TypeBool, &smCloudsOn);

   Con::addVariable("pref::NumCloudLayers", TypeS32, &smNumCloudsOn);
   Con::addVariable("pref::SkyOn",          TypeBool, &smSkyOn);
}
//---------------------------------------------------------------------------
void Sky::unpackUpdate(NetConnection *, BitStream *stream)
{
   if(stream->readFlag())
   {
      mMaterialListName = stream->readSTString();
      loadDml();

      stream->read(&mFogColor.red);
      stream->read(&mFogColor.green);
      stream->read(&mFogColor.blue);
      stream->read(&mNumFogVolumes);
      stream->read(&mSkyTexturesOn);
      stream->read(&mRenderBoxBottom);
      stream->read(&mSolidFillColor.red);
      stream->read(&mSolidFillColor.green);
      stream->read(&mSolidFillColor.blue);
      stream->read(&mEffectPrecip);
      mNoRenderBans = stream->readFlag();

      U32 i;
      for(i = 0; i < mNumFogVolumes; i++)
      {
         stream->read(&mFogVolumes[i].visibleDistance);
         stream->read(&mFogVolumes[i].minHeight);
         stream->read(&mFogVolumes[i].maxHeight);

         stream->read(&mFogVolumes[i].color.red);
         stream->read(&mFogVolumes[i].color.green);
         stream->read(&mFogVolumes[i].color.blue);

         stream->read(&mStormFogData.volume[i].active);
      }
      for(i = 0; i < MAX_NUM_LAYERS; i++)
      {
         mCloudText[i] = stream->readSTString();
         stream->read(&mCloudHeight[i]);
         stream->read(&mCloudSpeed[i]);
      }
      initSkyData();
      Point3F vel;
      if(mathRead(*stream, &vel))
         setWindVelocity(vel);

      stream->read(&mFogVolume);
      if(stream->readFlag())
      {
         U32 state, stormTimeDiff;
         stream->read(&mFogPercentage);
         stream->read(&mStormFogData.time);
         stream->read(&state);
         stream->read(&stormTimeDiff);
         stream->read(&mStormFogData.endPercentage);
         mStormFogData.state = SkyState(state);
         if(mStormFogData.time)
         {
            Con::printf("Server Storm Time: %u",stormTimeDiff);
            Con::printf("Get Current Time: %u",Sim::getCurrentTime());
            mStormFogOn = true;
            mStormFogData.lastTime = Sim::getCurrentTime() - stormTimeDiff;
            Con::printf("READ OFFSET: %g", F32(Sim::getCurrentTime() - mStormFogData.lastTime) / 32.0f);
            for(S32 x = 0; x < mNumFogVolumes; ++x)
            {
               if (mStormFogData.volume[x].active)
                  mFogVolumes[x].percentage = mFogPercentage;
               mStormFogData.volume[x].endPercentage =
                  mStormFogData.volume[x].active? mStormFogData.endPercentage:
                  mFogVolumes[x].percentage;
               mStormFogData.volume[x].speed =  (mStormFogData.endPercentage - mFogVolumes[x].percentage) / ((mStormFogData.time * 32.0f) / (F32)mNumFogVolumes);
               mStormFogData.volume[x].state = mStormFogData.state;
               if(mStormFogData.volume[x].state == comingIn)
                  mStormFogData.current = 0;
               else
                  mStormFogData.current = mNumFogVolumes-1;
            }
         }
      }
   }

   if(stream->readFlag())
      stream->read(&mStormCloudsOn);

   if(stream->readFlag())
   {
      stream->read(&mStormFogOn);
      if(!mStormFogOn)
      {
         for(S32 x = 0; x < mNumFogVolumes; x++)
            if (mStormFogData.volume[x].active)
               mFogVolumes[x].percentage = 0.0f;
         mSetFog = true;
      }
   }

   if(stream->readFlag())
   {
      stream->read(&mVisibleDistance);
      stream->read(&mFogDistance);
      initSkyData();
   }
   if(stream->readFlag())
   {
      U32 state;
      stream->read(&state);
      mStormCloudData.state = SkyState(state);
      stream->read(&mStormCloudData.time);

      if(mStormCloudData.time > 0.0f)
      {
         mStormCloudData.speed = ((mRadius * 2) * F32(mNumCloudLayers + 1)) / (mStormCloudData.time * 32.0f);
         if(mNumCloudLayers)
            mStormCloudData.fadeSpeed = 1.0f / (((mStormCloudData.time * 32.0f) / F32(mNumCloudLayers + 1)) / mNumCloudLayers);
         startStorm();
      }
   }
   if(stream->readFlag())
   {
      stream->read(&mFogPercentage);
      stream->read(&mStormFogData.time);
      stream->read(&mFogVolume);
      if(mStormFogData.time)
      {
         mStormFogData.lastTime = Sim::getCurrentTime();
         startStormFog();
      }
   }
   if(stream->readFlag())
   {
      stream->read(&mRealFog);
      stream->read(&mRealFogMax);
      stream->read(&mRealFogMin);
      stream->read(&mRealFogSpeed);
      if(mRealFog)
      {
         for(S32 x = 0; x < mNumFogVolumes; ++x)
         {
            mStormFogData.volume[x].lastPercentage = mRealFogMax;
            mStormFogData.volume[x].endPercentage = mRealFogMin;
            mStormFogData.volume[x].speed = -(((mNumFogVolumes-x)*(mNumFogVolumes-x)) * mRealFogSpeed);
         }
         F32 save = mStormFogData.volume[0].speed;
         mStormFogData.volume[0].speed = mStormFogData.volume[1].speed;
         mStormFogData.volume[1].speed = save;
      }
   }

   if(stream->readFlag())
   {
      Point3F vel;
      if(mathRead(*stream, &vel))
         setWindVelocity(vel);
   }
}

//---------------------------------------------------------------------------
U32 Sky::packUpdate(NetConnection *, U32 mask, BitStream *stream)
{
   if(stream->writeFlag(mask & InitMask))
   {
      stream->writeString(mMaterialListName);
      stream->write(mFogColor.red);
      stream->write(mFogColor.green);
      stream->write(mFogColor.blue);
      stream->write(mNumFogVolumes);
      stream->write(mSkyTexturesOn);
      stream->write(mRenderBoxBottom);
      stream->write(mSolidFillColor.red);
      stream->write(mSolidFillColor.green);
      stream->write(mSolidFillColor.blue);
      stream->write(mEffectPrecip);
      stream->writeFlag(mNoRenderBans);

      U32 i;
      for(i = 0; i < mNumFogVolumes; i++)
      {
         stream->write(mFogVolumes[i].visibleDistance);
         stream->write(mFogVolumes[i].minHeight);
         stream->write(mFogVolumes[i].maxHeight);

         stream->write(mFogVolumes[i].color.red);
         stream->write(mFogVolumes[i].color.green);
         stream->write(mFogVolumes[i].color.blue);

         stream->write(mStormFogData.volume[i].active);
      }
      for(i = 0; i < MAX_NUM_LAYERS; i++)
      {
         stream->writeString(mCloudText[i]);
         stream->write(mCloudHeight[i]);
         stream->write(mCloudSpeed[i]);
      }
      mathWrite(*stream, mWindVelocity);

      stream->write(mFogVolume);
      U32 currentTime = Sim::getCurrentTime();
      U32 stormTimeDiff = currentTime - mStormFogData.startTime;
      if(stream->writeFlag(F32(stormTimeDiff) / 1000.0f < mStormFogData.time))
      {
         stream->write(mFogPercentage);
         stream->write(mStormFogData.time);
         stream->write(U32(mStormFogData.state));
         stream->write(stormTimeDiff);
         stream->write(mStormFogData.endPercentage);
         Con::printf("WRITE OFFSET: %g", F32(stormTimeDiff) / 32.0f);
      }
   }

   if(stream->writeFlag(mask & StormCloudsOnMask))
      stream->write(mStormCloudsOn);

   if(stream->writeFlag(mask & StormFogOnMask && !(mask & InitMask)))
      stream->write(mStormFogOn);

   if(stream->writeFlag(mask & VisibilityMask))
   {
      stream->write(mVisibleDistance);
      stream->write(mFogDistance);
   }

   if(stream->writeFlag(mask & StormCloudMask))
   {
      stream->write(U32(mStormCloudData.state));
      stream->write(mStormCloudData.time);
   }

   if(stream->writeFlag(mask & StormFogMask && !(mask & InitMask)) )
   {
      stream->write(mStormFogData.endPercentage);
      stream->write(mStormFogData.time);
      stream->write(mFogVolume);
      mStormFogData.startTime = Sim::getCurrentTime();
   }

   if(stream->writeFlag(mask & StormRealFogMask))
   {
      stream->write(mRealFog);
      stream->write(mRealFogMax);
      stream->write(mRealFogMin);
      stream->write(mRealFogSpeed);
   }
   if(stream->writeFlag(mask & WindMask))
      mathWrite(*stream, mWindVelocity);
   return 0;
}

//---------------------------------------------------------------------------
void Sky::inspectPostApply()
{
   for(mNumFogVolumes = 0; mNumFogVolumes < MaxFogVolumes; mNumFogVolumes++)
      if(mFogVolumes[mNumFogVolumes].visibleDistance == -1 || mFogVolumes[mNumFogVolumes].visibleDistance == 0)
         break;
   setMaskBits(InitMask | VisibilityMask);
}

//---------------------------------------------------------------------------
void Sky::renderObject(SceneState* state, SceneRenderImage*)
{
   AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");

   extern bool sgForce16BitTexture;
   extern bool sgForcePalettedTexture;

   if(mLastForce16Bit != sgForce16BitTexture || mLastForcePaletted != sgForcePalettedTexture)
      setVisibility();

   RectI viewport;

   dglGetViewport(&viewport);

   // Clear the objects viewport to the fog color.  This is something of a dirty trick,
   //  since we want an identity projection matrix here...
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();

   state->setupObjectProjection(this);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
   glEnable(GL_BLEND);
   glBlendFunc(GL_ONE, GL_ZERO);
   glColor3ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue));
   glBegin(GL_TRIANGLE_FAN);
      glVertex3f(-1, -1, 1);
      glVertex3f(-1,  1, 1);
      glVertex3f( 1,  1, 1);
      glVertex3f( 1, -1, 1);
   glEnd();
   glPopMatrix();

   // On input.  Finalize the projection matrix...
   state->setupObjectProjection(this);

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   Point3F camPos = state->getCameraPosition();
   glTranslatef(camPos.x,camPos.y,camPos.z);

   glEnable(GL_TEXTURE_2D);
   glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);
   glDepthMask(GL_FALSE);

   render(state);

   glDisable(GL_TEXTURE_2D);
   glDisable(GL_BLEND);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
   glDepthMask(GL_TRUE);

   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();

   dglSetViewport(viewport);

   AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}

//---------------------------------------------------------------------------
bool Sky::prepRenderImage(SceneState* state, const U32 stateKey,
                          const U32 startZone, const bool modifyBaseState)
{
   startZone, modifyBaseState;
   AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set");
   AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1");

   if (isLastState(state, stateKey))
      return false;
   setLastState(state, stateKey);

   // This should be sufficient for most objects that don't manage zones, and
   //  don't need to return a specialized RenderImage...
   if (state->isObjectRendered(this)) {
      SceneRenderImage* image = new SceneRenderImage;
      image->obj = this;
      image->sortType = SceneRenderImage::Sky;
      state->insertRenderImage(image);
   }

   return false;
}

//---------------------------------------------------------------------------
void Sky::render(SceneState *state)
{
   PROFILE_START(SkyRender);
   F32 banHeights[2] = {-(mSpherePt.z-1),-(mSpherePt.z-1)};
   F32 alphaBan[2] = {0.0f, 0.0f};
   F32 depthInFog = 0.0f;
   Point3F camPos;
   S32 index=0;

   if(gClientSceneGraph)
   {
      F32 currentVisDis = gClientSceneGraph->getVisibleDistanceMod();
      if(mLastVisDisMod != currentVisDis)
      {
         calcPoints();
         for(S32 i = 0; i < MAX_NUM_LAYERS; ++i)
            mCloudLayer[i].setPoints();

         mLastVisDisMod = currentVisDis;
      }
   }
   if(mNumFogVolumes)
   {
      camPos = state->getCameraPosition();
      depthInFog = -(camPos.z - mFogLine);
   }
   // Calculats alpha values and ban heights
   if(depthInFog > 0.0f)
      calcAlphas_Heights(camPos.z, banHeights, alphaBan, depthInFog);
   else // Not in fog so setup default values
   {
      alphaBan[0] = 0.0f;
      alphaBan[1] = 0.0f;
      banHeights[0] = HORIZON;
      banHeights[1] = banHeights[0] + OFFSET_HEIGHT;
   }

   // if lower ban is at top of box then no cliping plan is needed
   if(banHeights[0] >= mSpherePt.z)
      banHeights[0] = banHeights[1] = mSpherePt.z;

   //Renders the 6 sides of the sky box
   if(alphaBan[1] < 1.0f || mNumFogVolumes == 0)
      renderSkyBox(banHeights[0], alphaBan[1]);

   // if completly fogged out then no need to render
   if(alphaBan[1] < 1.0f || depthInFog < 0.0f)
   {
      if(smCloudsOn && mStormCloudsOn && smSkyOn)
      {
         F32 ang   = mAtan(banHeights[0],mSkyBoxPt.x);
         F32 xyval = mSin(ang);
         F32 zval  = mCos(ang);
         PlaneF planes[4];
         planes[0] = PlaneF(xyval,  0.0f,   zval, 0.0f);
         planes[1] = PlaneF(-xyval, 0.0f,   zval, 0.0f);
         planes[2] = PlaneF(0.0f,   xyval,  zval, 0.0f);
         planes[3] = PlaneF(0.0f,   -xyval, zval, 0.0f);

         S32 numRender = (smNumCloudsOn > mNumCloudLayers) ? mNumCloudLayers : smNumCloudsOn;
         for(S32 x = 0; x < numRender; ++x)
            mCloudLayer[x].render(Sim::getCurrentTime(), x, smCloudOutlineOn, mNumCloudLayers, planes);
      }
      if(!mNoRenderBans)
      {
         Point3F banPoints[2][MAX_BAN_POINTS];
         Point3F cornerPoints[MAX_BAN_POINTS];

         // Calculate upper, lower, and corner ban points
         calcBans(banHeights, banPoints, cornerPoints);

         glDisable(GL_TEXTURE_2D);
         glEnable(GL_BLEND);
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

         // Renders the side, top, and corner bans
         renderBans(alphaBan, banHeights, banPoints, cornerPoints);
      }
      glDisable(GL_BLEND);
      glEnable(GL_TEXTURE_2D);
   }

   if(mSetFog)
   {
      mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes);
      mSetFog = false;
   }
   if(mStormFogOn && mStormFogData.volume[mStormFogData.current].state != isDone)
      updateFog();
   if(mStormFogOn && mRealFog)
      updateRealFog();
   PROFILE_END();
}

//---------------------------------------------------------------------------
void Sky::calcAlphas_Heights(F32 zCamPos, F32 *banHeights,
                             F32 *alphaBan, F32 depthInFog)
{
   F32 sideA, lower = 0.0f, upper = 0.0f;
   F32 visValue, visDis = 0.0f;
   F32 visRatio, ratioVal, setVis;

   visDis = setVis = mVisibleDistance +
      (mFogVolumes[mNumFogVolumes-1].visibleDistance - mVisibleDistance) *
         mFogVolumes[mNumFogVolumes-1].percentage;
   for(S32 x = 0; x < mNumFogVolumes-1; ++x)
   {
      F32 distance = mVisibleDistance +
         (mFogVolumes[x].visibleDistance - mVisibleDistance) *
            mFogVolumes[x].percentage;
      if (distance < setVis)
      {
         visValue = (zCamPos < mFogVolumes[x].minHeight) ?
                     mFogVolumes[x].maxHeight - mFogVolumes[x].minHeight :
                     mFogVolumes[x].maxHeight - zCamPos;
         if(visValue > 0.0f)
         {
            ratioVal = setVis / distance;
            visRatio = visValue / distance;
            visDis -= (distance * visRatio) * ratioVal;
         }
      }
   }

   //Calculate upper Height
   if(visDis > 0.0f)
      upper =  (mSkyBoxPt.x*depthInFog)/(visDis * 0.2f);

   banHeights[1] = mSpherePt.z;

   if(upper < mSpherePt.z)
   {
      banHeights[1] = upper;
      if(banHeights[1] < OFFSET_HEIGHT)
         banHeights[1] = OFFSET_HEIGHT + HORIZON;
   }

   if(visDis > depthInFog)
   {
      sideA = mSqrt((visDis*visDis)-(depthInFog*depthInFog));
      lower = (mSkyBoxPt.x*depthInFog)/sideA;

      //Calculate lower Height
      banHeights[0] = mSpherePt.z;
      if(lower < mSpherePt.z)
         banHeights[0] = lower;

      if(banHeights[0] == mSpherePt.z && banHeights[1] == mSpherePt.z)
      {
         F32 temp =  ((lower - mSpherePt.z) * (sideA/depthInFog));
         if(temp <= mSkyBoxPt.x)
            alphaBan[1] = temp/mSkyBoxPt.x;
         else
            alphaBan[1] = 1.0f;

         alphaBan[0] = 1.0f;
      }
      else
      {
         alphaBan[0] = banHeights[0]/mSpherePt.z;
         alphaBan[1] = 0.0f;
      }
   }
   else
   {
      alphaBan[1] = alphaBan[0] = 1.0f;
      banHeights[0] = banHeights[1] = mSpherePt.z;
   }
   banHeights[0] *= mFogVolumes[0].percentage;
   banHeights[1] *= mFogVolumes[0].percentage;

   if(banHeights[1] < OFFSET_HEIGHT)
      banHeights[1] = OFFSET_HEIGHT + HORIZON;
}

void Sky::setRenderPoints(Point3F* renderPoints, S32 index)
{
   renderPoints[0].set(mPoints[index].x, mPoints[index].y, mPoints[index].z);
   renderPoints[1].set(mPoints[index+1].x, mPoints[index+1].y, mPoints[index+1].z);
   renderPoints[2].set(mPoints[index+6].x, mPoints[index+6].y, mPoints[index+6].z);
   renderPoints[3].set(mPoints[index+5].x, mPoints[index+5].y, mPoints[index+5].z);
}

void Sky::calcTexCoords(Point2F* texCoords, Point3F* renderPoints, S32 index)
{
   for(S32 x = 0; x < 4; ++x)
      texCoords[x].set(mTexCoord[x].x, mTexCoord[x].y);
   S32 length = (S32)(mFabs(mPoints[index].z) + mFabs(mPoints[index + 5].z));
   F32 per = mPoints[index].z - renderPoints[3].z;

   texCoords[3].y = texCoords[2].y = (per / length);
}

//---------------------------------------------------------------------------
void Sky::renderSkyBox(F32 lowerBanHeight, F32 alphaBanUpper)
{
   S32 side, index=0, val;
   U32 numPoints;
   Point3F renderPoints[4];
   Point2F texCoords[4];
   if(!mSkyTexturesOn || !smSkyOn)
   {
      glDisable(GL_TEXTURE_2D);
      glColor3ub(mRealSkyColor.red, mRealSkyColor.green, mRealSkyColor.blue);
   }
   for(side = 0; side < ((mRenderBoxBottom) ? 6 : 5); ++side)
   {
      if((lowerBanHeight != mSpherePt.z  || (side == 4 && alphaBanUpper < 1.0f)) && mSkyHandle[side])
      {
         glBindTexture(GL_TEXTURE_2D,mSkyHandle[side].getGLName());
         if(side < 4)
         {
            numPoints = 4;
            setRenderPoints(renderPoints, index);
            if(!mNoRenderBans)
               sgUtil_clipToPlane(renderPoints, numPoints, PlaneF(0.0f, 0.0f, 1.0f, -lowerBanHeight));
            if(numPoints)
            {
               calcTexCoords(texCoords, renderPoints, index);
               glBegin(GL_QUADS);
                  glTexCoord2f(texCoords[0].x, texCoords[0].y);
                  glVertex3f(renderPoints[0].x, renderPoints[0].y, renderPoints[0].z);
                  glTexCoord2f(texCoords[1].x, texCoords[1].y);
                  glVertex3f(renderPoints[1].x, renderPoints[1].y, renderPoints[1].z);
                  glTexCoord2f(texCoords[3].x, texCoords[3].y);
                  glVertex3f(renderPoints[2].x, renderPoints[2].y, renderPoints[2].z);
                  glTexCoord2f(texCoords[2].x, texCoords[2].y);
                  glVertex3f(renderPoints[3].x, renderPoints[3].y, renderPoints[3].z);
               glEnd();
            }
            ++index;
         }
         else
         {
            index = 3;
            val = -1;
            if(side == 5)
            {
               index = 5;
               val = 1;
            }
            glBegin(GL_QUADS);
               glTexCoord2f(mTexCoord[0].x, mTexCoord[0].y);
               glVertex3f(mPoints[index].x, mPoints[index].y, mPoints[index].z);
               glTexCoord2f(mTexCoord[1].x, mTexCoord[1].y);
               glVertex3f(mPoints[index+(1*val)].x, mPoints[index+(1*val)].y, mPoints[index+(1*val)].z);
               glTexCoord2f(mTexCoord[3].x, mTexCoord[3].y);
               glVertex3f(mPoints[index+(2*val)].x, mPoints[index+(2*val)].y, mPoints[index+(2*val)].z);
               glTexCoord2f(mTexCoord[2].x, mTexCoord[2].y);
               glVertex3f(mPoints[index+(3*val)].x, mPoints[index+(3*val)].y, mPoints[index+(3*val)].z);
            glEnd();
         }

      }
   }
   if(!mSkyTexturesOn)
      glEnable(GL_TEXTURE_2D);
}

//---------------------------------------------------------------------------
void Sky::calcBans(F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints)
{
   F32 incRad = RAD / F32(FOG_BAN_DETAIL*2);
   MatrixF ban;
   Point4F point;
   S32 index, x;
   F32 height = banHeights[0];

   F32 value = banHeights[0] / mSkyBoxPt.z;
   F32 mulVal = -(mSqrt(1-(value*value))); // lowerBan Multiple
   index=0;

   // Calculates the upper and lower bans
   for(x=0; x < 2; ++x)
   {
      for(F32 angle=0.0f; angle <= RAD+incRad ; angle+=incRad)
      {
         ban.set(Point3F(0.0f, 0.0f, angle));
         point.set(mulVal*mSkyBoxPt.x,0.0f,0.0f,1.0f);
         ban.mul(point);
         banPoints[x][index++].set(point.x,point.y,height);
      }
      height = banHeights[1];
      value  = banHeights[1] / mSkyBoxPt.x;
      mulVal = -(mSqrt(1-(value*value))); // upperBan Multiple
      index = 0;
   }

   // Calculates the filler points needed between the lower ban and the clipping plane
   index = 2;
   cornerPoints[0].set(mPoints[3].x, mPoints[3].y, banHeights[0]-1);
   cornerPoints[1].set(mPoints[3].x, 0.0f, banHeights[0]-1);

   for(x = 0; x < (FOG_BAN_DETAIL/2.0f) + 1.0f; ++x)
      cornerPoints[index++].set(banPoints[0][x].x, banPoints[0][x].y, banPoints[0][x].z);
   cornerPoints[index].set(0.0f, mPoints[3].y, banHeights[0]-1 );
}

//---------------------------------------------------------------------------
void Sky::renderBans(F32 *alphaBan, F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints)
{
   S32 side, x, index = 0;
   F32 angle;
   U8 UalphaIn = U8(alphaBan[1]*255);
   U8 UalphaOut = U8(alphaBan[0]*255);

  //Renders the side bans
   if(banHeights[0] < mSpherePt.z)
   {
      glBegin(GL_TRIANGLE_STRIP);
         for(x=0;x<FOG_BAN_DETAIL*2+1;++x)
         {
            glColor4ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue), 255);
            glVertex3f(banPoints[0][index].x,banPoints[0][index].y,banPoints[0][index].z);

            glColor4ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue), UalphaOut);
            glVertex3f(banPoints[1][index].x,banPoints[1][index].y,banPoints[1][index].z);
            ++index;
         }
      glEnd();
   }
   //Renders the top ban
   glBegin(GL_TRIANGLE_FAN);
      glColor4ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue), UalphaIn);
      glVertex3f(mTopCenterPt.x, mTopCenterPt.y, mTopCenterPt.z);

      for(x=0;x<FOG_BAN_DETAIL*2+1;++x)
      {
         glColor4ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue), UalphaOut);
         glVertex3f(banPoints[1][x].x, banPoints[1][x].y, banPoints[1][x].z);
      }

   glEnd();

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   angle = 0.0f;
   //Renders the filler
   for(side=0;side<4;++side)
   {
      glRotatef(angle,0.0,0.0,1.0);

      glBegin(GL_TRIANGLE_FAN);
         for(x=0;x<FOG_BAN_DETAIL;++x)
         {
            glColor4ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue), 255);
            glVertex3f(cornerPoints[x].x, cornerPoints[x].y, cornerPoints[x].z);
         }

      glEnd();

      angle += 90.0f;
   }

   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();
}

//---------------------------------------------------------------------------
void Sky::startStorm()
{
   mStormCloudsOn = true;
   Cloud::startStorm(mStormCloudData.state);
   for(int i = 0; i < mNumCloudLayers; ++i)
      mCloudLayer[i].calcStorm(mStormCloudData.speed, mStormCloudData.fadeSpeed);
}

void Sky::stormCloudsShow(bool show)
{
   mStormCloudsOn = show;
   setMaskBits(StormCloudsOnMask);
}

void Sky::stormFogShow(bool show)
{
   mStormFogOn = show;
   setMaskBits(StormFogOnMask);
}

//---------------------------------------------------------------------------
void Sky::startStormFog()
{
   if(mStormFogOn)
      for(S32 x = 0; x < mNumFogVolumes; ++x)
         mFogVolumes[x].percentage = mStormFogData.volume[x].endPercentage;

   mStormFogOn = true;
   if(mFogVolume < 0)
      for(S32 x = 0; x < mNumFogVolumes; ++x)
      {
         mStormFogData.volume[x].speed =  (mFogPercentage - mFogVolumes[x].percentage) / ((mStormFogData.time * 32.0f) / (F32)mNumFogVolumes);
         mStormFogData.volume[x].state = (mStormFogData.volume[x].endPercentage > mFogPercentage) ? goingOut : comingIn;
         mStormFogData.volume[x].endPercentage = mStormFogData.volume[x].active? mFogPercentage:
            mFogVolumes[x].percentage;
         if(mStormFogData.volume[x].state == comingIn)
            mStormFogData.current = 0;
         else
            mStormFogData.current = mNumFogVolumes-1;
      }
   else if(mFogVolume < mNumFogVolumes)
   {
      mStormFogData.volume[mFogVolume].speed =  (mFogPercentage - mFogVolumes[mFogVolume].percentage) / ((mStormFogData.time * 32.0f) / (F32)mNumFogVolumes);
      mStormFogData.volume[mFogVolume].state = (mFogVolumes[mFogVolume].percentage > mFogPercentage) ? goingOut : comingIn;
      mStormFogData.volume[mFogVolume].endPercentage = mFogPercentage;
      mStormFogData.current = mFogVolume;
   }
}

//---------------------------------------------------------------------------
void Sky::updateFog()
{
   F32 overFlow, offset = 1.0f;
   U32 currentTime = Sim::getCurrentTime();

   if(mStormFogData.lastTime != 0)
      offset = F32(currentTime - mStormFogData.lastTime) / 32.0f;
   Con::printf("OFFSET: %g", offset);
   mStormFogData.lastTime = currentTime;

   mFogVolumes[mStormFogData.current].percentage += (mStormFogData.volume[mStormFogData.current].speed * offset);
   do
   {
      Con::printf("CURRENT: %d PERCENTAGE: %g TIME: %u",mStormFogData.current, mFogVolumes[mStormFogData.current].percentage, currentTime);
      overFlow = 0.0f;
      if(mStormFogData.volume[mStormFogData.current].state == comingIn && mFogVolumes[mStormFogData.current].percentage >= mStormFogData.volume[mStormFogData.current].endPercentage)
      {
         overFlow = mFogVolumes[mStormFogData.current].percentage - mStormFogData.volume[mStormFogData.current].endPercentage;
         mFogVolumes[mStormFogData.current].percentage = mStormFogData.volume[mStormFogData.current].endPercentage;
         mStormFogData.volume[mStormFogData.current].state = isDone;
         if(++mStormFogData.current >= mNumFogVolumes)
         {
            mStormFogData.current -= 1;
            mStormFogData.lastTime = 0;
            mStormFogOn = false;
            Con::printf("FOG IS DONE");
         }
         else
            mFogVolumes[mStormFogData.current].percentage += overFlow;
      }
      else if(mStormFogData.volume[mStormFogData.current].state == goingOut && mFogVolumes[mStormFogData.current].percentage <= mStormFogData.volume[mStormFogData.current].endPercentage)
      {
         overFlow = mStormFogData.volume[mStormFogData.current].endPercentage - mFogVolumes[mStormFogData.current].percentage;
         mFogVolumes[mStormFogData.current].percentage = mStormFogData.volume[mStormFogData.current].endPercentage;
         mStormFogData.volume[mStormFogData.current].state = isDone;
         if(--mStormFogData.current < 0)
         {
            mStormFogData.current += 1;
            mStormFogData.lastTime = 0;
            mStormFogOn = false;
            Con::printf("FOG IS DONE");
         }
         else
            mFogVolumes[mStormFogData.current].percentage -= overFlow;
      }
   } while(overFlow > 0.0f && mStormFogOn);
//   if(mStormFogData.volume[mStormFogData.current].state != done)
      mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes);
}

//---------------------------------------------------------------------------
void Sky::updateRealFog()
{
   for(S32 x = 0; x < mNumFogVolumes; ++x)
   {
      mFogVolumes[x].percentage += mStormFogData.volume[x].speed;
      if((mStormFogData.volume[x].speed < 0.0f && mFogVolumes[x].percentage <= mStormFogData.volume[x].endPercentage) ||
         (mStormFogData.volume[x].speed > 0.0f && mFogVolumes[x].percentage >= mStormFogData.volume[x].endPercentage))
      {
            mFogVolumes[x].percentage = mStormFogData.volume[x].endPercentage;
            F32 save = mStormFogData.volume[x].lastPercentage;
            mStormFogData.volume[x].lastPercentage = mStormFogData.volume[x].endPercentage;
            mStormFogData.volume[x].endPercentage = save;

            mStormFogData.volume[x].speed *= -1;
      }
   }
   mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes);
}

//---------------------------------------------------------------------------
void Sky::calcPoints()
{
   S32 x, y, xval = 1, yval = -1, zval = 1;
   F32 textureDem;

   F32 visDisMod = mVisibleDistance;
   if(gClientSceneGraph)
      visDisMod = gClientSceneGraph->getVisibleDistanceMod();
   mRadius = visDisMod * 0.95f;

   Cloud::setRadius(mRadius);

   Point3F tpt(1,1,1);
   tpt.normalize(mRadius);

   mPoints[0] = mPoints[4] = Point3F(-tpt.x, -tpt.y, tpt.z);
   mPoints[5] = mPoints[9] = Point3F(-tpt.x, -tpt.y, -tpt.z);

   for(x = 1; x < 4; ++x)
   {
      mPoints[x]   = Point3F(tpt.x * xval, tpt.y * yval, tpt.z);
      mPoints[x+5] = Point3F(tpt.x * xval, tpt.y * yval, -tpt.z);

      if(yval > 0 && xval > 0)
         xval *= -1;
      if(yval < 0)
         yval *= -1;
   }

   mFogLine = 0.0f;
   for(x = 0; x < mNumFogVolumes; ++x)
      mFogLine = (mFogVolumes[x].maxHeight > mFogLine) ? mFogVolumes[x].maxHeight : mFogLine;

   textureDem = 512;
   if(mSkyHandle[0])
      textureDem = mSkyHandle[0].getWidth();

   for(y = 0; y < 2 ; ++y)
      for(x = 0; x < 2 ; ++x)
      {
         mTexCoord[x+(y*2)].set(x,y);
         mTexCoord[x+(y*2)] *= (textureDem-1.0f)/textureDem;
         mTexCoord[x+(y*2)] += Point2F(0.5 / textureDem, 0.5 / textureDem);
      }

   mSpherePt = mSkyBoxPt = mPoints[1];
   mSpherePt.set(mSpherePt.x,0.0f,mSpherePt.z);
   mSpherePt.normalize(mSkyBoxPt.x);
   mTopCenterPt.set(0.0f,0.0f,mSkyBoxPt.z);
}

//---------------------------------------------------------------------------
bool Sky::loadDml()
{
   char path[1024], *p;
   dStrcpy(path, mMaterialListName);
   if ((p = dStrrchr(path, '/')) != NULL)
      *p = 0;
   mNumCloudLayers = 0;
   Stream *stream = ResourceManager->openStream(mMaterialListName);
   if (stream==NULL)
   {
#if defined(TORQUE_DEBUG)
		Con::warnf("Sky material list is missing: %s", mMaterialListName);
#else
		// ASSERT?? !!!!!!TBD
#endif
      return false;
   }
   else
   {// !!!!!TBD dhc - there's no fricking error checking here or in materialList.read!!!!
      mMaterialList.read(*stream);
      ResourceManager->closeStream(stream);
      if(!mMaterialList.load(SkyTexture, path))
         return false;
      for(S32 x = 0; x < 6; ++x)
      {
         mMaterialList.getMaterial(x).setClamp(true);
         mSkyHandle[x] = mMaterialList.getMaterial(x);
      }
      for(S32 x = 0; x < mMaterialList.size() - CloudMaterialOffset; ++x, ++mNumCloudLayers)
      {
         mMaterialList.getMaterial(x + CloudMaterialOffset).setClamp(false);
         mCloudLayer[x].setTexture(mMaterialList.getMaterial(x + CloudMaterialOffset));
      }
   }
   return true;
}

//---------------------------------------------------------------------------
void Sky::updateVisibility()
{
    setVisibility();

    setMaskBits(VisibilityMask);
}

//---------------------------------------------------------------------------
void Sky::stormCloudsOn(S32 state, F32 time)
{
   mStormCloudData.state = (state) ? comingIn : goingOut;

   mStormCloudData.time = time;
   setMaskBits(StormCloudMask);
}

//---------------------------------------------------------------------------
void Sky::stormFogOn(F32 percentage, F32 time)
{
   mStormFogData.time = time;

   if(mStormFogData.endPercentage >= 0.0f)
   {
      mStormFogData.state = (mStormFogData.endPercentage > percentage) ? goingOut : comingIn;
      mFogPercentage = mStormFogData.endPercentage;
   }
   else
      mStormFogData.state = (mFogPercentage > percentage) ? goingOut : comingIn;
   mStormFogData.endPercentage = percentage;

   setMaskBits(StormFogMask);
}

//---------------------------------------------------------------------------
void Sky::stormRealFog(S32 value, F32 max, F32 min, F32 speed)
{
   mRealFog = value;
   mRealFogMax = max;
   mRealFogMin = min;
   mRealFogSpeed = speed;
   setMaskBits(StormRealFogMask);
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Cloud Code
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
Cloud::Cloud()
{
   mDown=5;
   mOver=1;
   mBaseOffset.set(0, 0);
   mTextureScale.set(1, 1);
   mCenterHeight=0.5;
   mInnerHeight=0.45;
   mEdgeHeight=0.4;
   mLastTime = 0;
   mOffset=0;
   mSpeed.set(1,1);
   mGStormData.currentCloud = MAX_NUM_LAYERS;
   mGStormData.fadeSpeed = 0.0f;
   mGStormData.StormOn = false;
   mGStormData.stormState = isDone;
   for(int i = 0; i < 25; ++i)
      stormAlpha[i] = 1.0f;
   mRadius = 1.0f;
}

//---------------------------------------------------------------------------
Cloud::~Cloud()
{
}

//---------------------------------------------------------------------------
void Cloud::updateCoord()
{
   mBaseOffset += mSpeed*mOffset;
   if(mSpeed.x < 0)
      mBaseOffset.x -= mCeil(mBaseOffset.x);
   else
      mBaseOffset.x -= mFloor(mBaseOffset.x);
   if(mSpeed.y < 0)
      mBaseOffset.y -= mCeil(mBaseOffset.y);
   else
      mBaseOffset.y -= mFloor(mBaseOffset.y);
}

//---------------------------------------------------------------------------
void Cloud::setHeights(F32 cHeight, F32 iHeight, F32 eHeight)
{
   mCenterHeight = cHeight;
   mInnerHeight = iHeight;
   mEdgeHeight = eHeight;
}

//---------------------------------------------------------------------------
void Cloud::setTexture(TextureHandle textHand)
{
   if(textHand)
   {
      mCloudHandle = textHand;
      AssertFatal(bool(mCloudHandle) != false, "Error, couldn't load cloud layer bitmap");
   }
}

//---------------------------------------------------------------------------
void Cloud::setSpeed(Point2F setSpeed)
{
   mSpeed = setSpeed;
}

//---------------------------------------------------------------------------
void Cloud::setPoints()
{
   S32 x, y;
   F32 xyDiff = (mRadius - -mRadius)/4;
   F32 cDis  = mRadius*mCenterHeight,
       upDis = mRadius*mInnerHeight,
       edgeZ = mRadius*mEdgeHeight;

   F32 zValue[25] = {edgeZ,edgeZ,edgeZ,edgeZ,edgeZ,edgeZ,upDis,upDis,upDis,edgeZ,edgeZ,upDis,cDis,upDis,edgeZ,edgeZ,upDis,upDis,upDis,edgeZ,edgeZ,edgeZ,edgeZ,edgeZ,edgeZ};

   for(y = 0; y < 5; ++y)
      for(x = 0; x < 5; ++x)
         mPoints[y*5+x].set(-mRadius+(xyDiff*x),mRadius - (xyDiff*y),zValue[y*5+x]);

   //Used to modify the corners of the cloud layers to make them one plane..
   Point3F vec = (mPoints[5]  + ((mPoints[1]  - mPoints[5])  * 0.5f)) - mPoints[6];
   mPoints[0] =   mPoints[6]  + (vec * 2.0f);

   vec =         (mPoints[9]  + ((mPoints[3]  - mPoints[9])  * 0.5f)) - mPoints[8];
   mPoints[4] =   mPoints[8]  + (vec * 2.0f);

   vec =         (mPoints[21] + ((mPoints[15] - mPoints[21]) * 0.5f)) - mPoints[16];
   mPoints[20] =  mPoints[16] + (vec * 2.0f);

   vec =         (mPoints[23] + ((mPoints[19] - mPoints[23]) * 0.5f)) - mPoints[18];
   mPoints[24] =  mPoints[18] + (vec * 2.0f);

   calcAlpha();
}

//---------------------------------------------------------------------------
void Cloud::calcAlpha()
{
   for(S32 i = 0; i < 25; ++i)
   {
      mAlpha[i] = 1.3f - ((mPoints[i] - Point3F(0, 0, mPoints[i].z)).len())/mRadius;
      if(mAlpha[i] < 0.4f)
         mAlpha[i]=0.0f;
      else if(mAlpha[i] > 0.8f)
         mAlpha[i] = 1.0f;
   }
}

//---------------------------------------------------------------------------
void Cloud::render(U32 currentTime, U32 cloudLayer, bool outlineOn, S32 numLayers, PlaneF* planes)
{
   mGStormData.numCloudLayers = numLayers;
   mOffset = 1.0f;
   U32 numPoints;
   Point3F renderPoints[128];
   Point2F renderTexPoints[128];
   F32 renderAlpha[128];
   F32 renderSAlpha[128];
   if(mLastTime != 0)
      mOffset = (currentTime - mLastTime)/32.0f;

   mLastTime=currentTime;

   if(!mCloudHandle || (mGStormData.StormOn && mGStormData.currentCloud < cloudLayer))
      return;

   S32 start=0, i, j, k;

   updateCoord();

   for(S32 x = 0; x < 5; x++)
      for(S32 y = 0; y < 5; y++)
         mTexCoords[y * 5 + x].set ( x * mTextureScale.x + mBaseOffset.x,
                                     y * mTextureScale.y - mBaseOffset.y);

   if(mGStormData.StormOn && mGStormData.currentCloud == cloudLayer)
      updateStorm();

   if(!outlineOn)
   {
      glBindTexture(GL_TEXTURE_2D, mCloudHandle.getGLName());
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   }
   for(i = 0; i < 4; ++i)
   {
      start = i * 5;
      for(j = 0; j < 4; ++j )
      {
         numPoints = 4;
         setRenderPoints(renderPoints, renderTexPoints, renderAlpha, renderSAlpha, start);
         for(S32 i = 0; i < 4; ++i)
            clipToPlane(renderPoints, renderTexPoints, renderAlpha, renderSAlpha,
                        numPoints, planes[i]);

         if(!outlineOn)
            glBegin(GL_TRIANGLE_FAN);
         else
            glBegin(GL_LINE_LOOP);

         for(k = 0; k < numPoints; ++k)
         {
            glColor4f(1.0,1.0,1.0, renderAlpha[k]*renderSAlpha[k]);
            glTexCoord2f(renderTexPoints[k].x,renderTexPoints[k].y);
            glVertex3f(renderPoints[k].x,renderPoints[k].y,renderPoints[k].z);
         }
         glEnd();

         ++start;
      }
   }
   glDisable(GL_BLEND);
}

void Cloud::setRenderPoints(Point3F* renderPoints, Point2F* renderTexPoints,
                            F32* renderAlpha, F32* renderSAlpha, S32 index)
{
   S32 offset[4] = {0,5,6,1};
   for(S32 x = 0; x < 4; ++x)
   {
      renderPoints[x].set(mPoints[index+offset[x]].x, mPoints[index+offset[x]].y, mPoints[index+offset[x]].z);
      renderTexPoints[x].set(mTexCoords[index+offset[x]].x, mTexCoords[index+offset[x]].y);
      renderAlpha[x] = mAlpha[index+offset[x]];
      renderSAlpha[x] = stormAlpha[index+offset[x]];
   }
}


//---------------------------------------------------------------------------
void Cloud::setTextPer(F32 cloudTextPer)
{
   mTextureScale.set(cloudTextPer / 4.0, cloudTextPer / 4.0);
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//    Storm Code
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void Cloud::updateStorm()
{
   if(!mGStormData.FadeOut && !mGStormData.FadeIn) {
      alphaCenter += (stormUpdate * mOffset);
      F32 update, center;
      if(mGStormData.stormDir == 'x') {
         update = stormUpdate.x;
         center = alphaCenter.x;
      }
      else {
         update = stormUpdate.y;
         center = alphaCenter.y;
      }

      if(mGStormData.stormState == comingIn) {
         if((update > 0 && center > 0) || (update < 0 && center < 0))
            mGStormData.FadeIn = true;
      }
      else
         if((update > 0 && center > mRadius*2) || (update < 0 && center < -mRadius*2)) {
//            Con::printf("Cloud %d is done.", mGStormData.currentCloud);
            mGStormData.StormOn = --mGStormData.currentCloud >= 0;
            if(mGStormData.StormOn) {
               mGStormData.FadeOut = true;
               return;
            }
         }
   }
   calcStormAlpha();
}

//---------------------------------------------------------------------------
void Cloud::calcStormAlpha()
{
   if(mGStormData.FadeIn)
   {
      bool done = true;
      for(int i = 0; i < 25; ++i)
      {
         stormAlpha[i] += (mGStormData.fadeSpeed * mOffset);
         if(stormAlpha[i] >= 1.0f)
            stormAlpha[i] = 1.0f;
         else
            done = false;
      }
      if(done)
      {
//         Con::printf("Cloud %d is done.", mGStormData.currentCloud);
         mGStormData.StormOn = ++mGStormData.currentCloud < mGStormData.numCloudLayers;
         mGStormData.FadeIn = false;
      }
   }
   else if(mGStormData.FadeOut)
   {
      bool done = true;
      for(int i = 0; i < 25; ++i)
      {
         stormAlpha[i] -= (mGStormData.fadeSpeed * mOffset);
         if(stormAlpha[i] <= mAlphaSave[i])
            stormAlpha[i] = mAlphaSave[i];
         else
            done = false;
      }
      if(done)
         mGStormData.FadeOut = false;
   }
   else
      for(int i = 0; i < 25; ++i)
      {
         stormAlpha[i] = 1.0f -((Point3F(mPoints[i].x-alphaCenter.x, mPoints[i].y-alphaCenter.y, mPoints[i].z).len())/mRadius);
         if(stormAlpha[i] < 0.0f)
            stormAlpha[i]=0.0f;
         else if(stormAlpha[i] > 1.0f)
            stormAlpha[i] = 1.0f;
      }
}

//---------------------------------------------------------------------------
void Cloud::calcStorm(F32 speed, F32 fadeSpeed)
{
   float tempX, tempY;
   float windSlop = 0.0f;

   if(mSpeed.x != 0)
      windSlop = mSpeed.y/mSpeed.x;

   tempX = (mSpeed.x < 0) ? -mSpeed.x : mSpeed.x;
   tempY = (mSpeed.y < 0) ? -mSpeed.y : mSpeed.y;

   if(tempX >= tempY)
   {
      alphaCenter.x =(mSpeed.x < 0) ? mRadius * -2 : mRadius * 2;
      alphaCenter.y = windSlop*alphaCenter.x;

      stormUpdate.x = alphaCenter.x > 0.0f ? -speed : speed;
      stormUpdate.y = alphaCenter.y > 0.0f ? -speed * windSlop : speed * windSlop;
      mGStormData.stormDir = 'x';
   }
   else
   {
      alphaCenter.y = (mSpeed.y < 0) ? mRadius * 2 : mRadius * -2;
      alphaCenter.x = windSlop * alphaCenter.y;

/*      if(windSlop != 0)
         alphaCenter.x = (1/windSlop)*alphaCenter.y;
      else
         alphaCenter.x = 0.0f;
*/
      stormUpdate.y = alphaCenter.y > 0.0f ? -speed : speed;
      stormUpdate.x = alphaCenter.x > 0.0f ? -speed * (1/windSlop) : speed * (1/windSlop);

      mGStormData.stormDir = 'y';
   }

   mGStormData.fadeSpeed = fadeSpeed;

   for(int i = 0; i < 25; ++i)
   {
      mAlphaSave[i] = 1.0f - (mPoints[i].len()/mRadius);
      if(mAlphaSave[i] < 0.0f)
         mAlphaSave[i]=0.0f;
      else if(mAlphaSave[i] > 1.0f)
         mAlphaSave[i] = 1.0f;
   }
   if(mGStormData.stormState == goingOut)
      alphaCenter.set(0.0f, 0.0f);
}

//---------------------------------------------------------------------------
void Cloud::startStorm(SkyState state)
{
   mGStormData.StormOn = true;
   mGStormData.stormState = state;
   if(state == goingOut)
   {
      mGStormData.FadeOut= true;
      mGStormData.FadeIn = false;
      mGStormData.currentCloud = mGStormData.numCloudLayers - 1;
   }
   else
   {
      mGStormData.FadeIn = false;
      mGStormData.FadeOut= false;
      mGStormData.currentCloud = 0;
   }
}

void Cloud::clipToPlane(Point3F* points, Point2F* texPoints, F32* alphaPoints,
                        F32* sAlphaPoints, U32& rNumPoints, const PlaneF& rPlane)
{
   S32 start = -1;
   for (U32 i = 0; i < rNumPoints; i++) {
      if (rPlane.whichSide(points[i]) == PlaneF::Front) {
         start = i;
         break;
      }
   }

   // Nothing was in front of the plane...
   if (start == -1) {
      rNumPoints = 0;
      return;
   }

   U32     numFinalPoints = 0;
   Point3F finalPoints[128];
   Point2F finalTexPoints[128];
   F32     finalAlpha[128];
   F32     finalSAlpha[128];

   U32 baseStart = start;
   U32 end       = (start + 1) % rNumPoints;

   while (end != baseStart) {
      const Point3F& rStartPoint = points[start];
      const Point3F& rEndPoint   = points[end];

      const Point2F& rStartTexPoint = texPoints[start];
      const Point2F& rEndTexPoint   = texPoints[end];

      PlaneF::Side fSide = rPlane.whichSide(rStartPoint);
      PlaneF::Side eSide = rPlane.whichSide(rEndPoint);

      S32 code = fSide * 3 + eSide;
      switch (code) {
        case 4:   // f f
        case 3:   // f o
        case 1:   // o f
        case 0:   // o o
         // No Clipping required

         //Alpha
         finalAlpha[numFinalPoints] = alphaPoints[start];
         finalSAlpha[numFinalPoints] = sAlphaPoints[start];

         //Points
         finalPoints[numFinalPoints] = points[start];
         finalTexPoints[numFinalPoints++] = texPoints[start];

         start = end;
         end   = (end + 1) % rNumPoints;
         break;


        case 2: { // f b
            // In this case, we emit the front point, Insert the intersection,
            //  and advancing to point to first point that is in front or on...

            //Alpha
            finalAlpha[numFinalPoints] = alphaPoints[start];
            finalSAlpha[numFinalPoints] = sAlphaPoints[start];

            //Points
            finalPoints[numFinalPoints] = points[start];
            finalTexPoints[numFinalPoints++] = texPoints[start];

            Point3F vector = rEndPoint - rStartPoint;
            F32 t        = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector));

            //Alpha
            finalAlpha[numFinalPoints] = alphaPoints[start]+ ((alphaPoints[end] - alphaPoints[start]) * t);
            finalSAlpha[numFinalPoints] = sAlphaPoints[start]+ ((sAlphaPoints[end] - sAlphaPoints[start]) * t);

            //Polygon Points
            Point3F intersection = rStartPoint + (vector * t);
            finalPoints[numFinalPoints] = intersection;

            //Texture Points
            Point2F texVec = rEndTexPoint - rStartTexPoint;

            Point2F texIntersection = rStartTexPoint + (texVec * t);
            finalTexPoints[numFinalPoints++] = texIntersection;

            U32 endSeek = (end + 1) % rNumPoints;
            while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
               endSeek = (endSeek + 1) % rNumPoints;

            end   = endSeek;
            start = (end + (rNumPoints - 1)) % rNumPoints;

            const Point3F& rNewStartPoint = points[start];
            const Point3F& rNewEndPoint   = points[end];

            const Point2F& rNewStartTexPoint = texPoints[start];
            const Point2F& rNewEndTexPoint   = texPoints[end];

            vector = rNewEndPoint - rNewStartPoint;
            t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));

            //Alpha
            alphaPoints[start] = alphaPoints[start]+ ((alphaPoints[end] - alphaPoints[start]) * t);
            sAlphaPoints[start] = sAlphaPoints[start]+ ((sAlphaPoints[end] - sAlphaPoints[start]) * t);

            //Polygon Points
            intersection = rNewStartPoint + (vector * t);
            points[start] = intersection;

            //Texture Points
            texVec = rNewEndTexPoint - rNewStartTexPoint;

            texIntersection = rNewStartTexPoint + (texVec * t);
            texPoints[start] = texIntersection;
         }
         break;

        case -1: {// o b
            // In this case, we emit the front point, and advance to point to first
            //  point that is in front or on...
            //

            //Alpha
            finalAlpha[numFinalPoints] = alphaPoints[start];
            finalSAlpha[numFinalPoints] = sAlphaPoints[start];

            //Points
            finalPoints[numFinalPoints] = points[start];
            finalTexPoints[numFinalPoints++] = texPoints[start];

            U32 endSeek = (end + 1) % rNumPoints;
            while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
               endSeek = (endSeek + 1) % rNumPoints;

            end   = endSeek;
            start = (end + (rNumPoints - 1)) % rNumPoints;

            const Point3F& rNewStartPoint = points[start];
            const Point3F& rNewEndPoint   = points[end];

            const Point2F& rNewStartTexPoint = texPoints[start];
            const Point2F& rNewEndTexPoint   = texPoints[end];

            Point3F vector = rNewEndPoint - rNewStartPoint;
            F32 t        = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));

            //Alpha
            alphaPoints[start]  = alphaPoints[start]  + ((alphaPoints[end]  - alphaPoints[start]) * t);
            sAlphaPoints[start] = sAlphaPoints[start] + ((sAlphaPoints[end] - sAlphaPoints[start]) * t);

            //Polygon Points
            Point3F intersection = rNewStartPoint + (vector * t);
            points[start] = intersection;

            //Texture Points
            Point2F texVec = rNewEndTexPoint - rNewStartTexPoint;

            Point2F texIntersection = rNewStartTexPoint + (texVec * t);
            texPoints[start] = texIntersection;
         }
         break;

        case -2:  // b f
        case -3:  // b o
        case -4:  // b b
         // In the algorithm used here, this should never happen...
         AssertISV(false, "SGUtil::clipToPlane: error in polygon clipper");
         break;

        default:
         AssertFatal(false, "SGUtil::clipToPlane: bad outcode");
         break;
      }
   }

   // Emit the last point.

   //Alpha
   finalAlpha[numFinalPoints] = alphaPoints[start];
   finalSAlpha[numFinalPoints] = sAlphaPoints[start];

   //Points
   finalPoints[numFinalPoints] = points[start];
   finalTexPoints[numFinalPoints++] = texPoints[start];
   AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen!  Invalid winding in clipToPlane: %d", numFinalPoints));

   // Copy the new rWinding, and we're set!

   //Alpha
   dMemcpy(alphaPoints, finalAlpha, numFinalPoints * sizeof(F32));
   dMemcpy(sAlphaPoints, finalSAlpha, numFinalPoints * sizeof(F32));

   //Points
   dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F));
   dMemcpy(texPoints, finalTexPoints, numFinalPoints * sizeof(Point2F));

   rNumPoints = numFinalPoints;
   AssertISV(rNumPoints <= 128, "MaxWindingPoints exceeded in scenegraph.  Fatal error.");
}
