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

#include "sceneGraph/sceneState.h"
#include "sceneGraph/sgUtil.h"
#include "sim/sceneObject.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneGraph.h"
#include "terrain/sky.h"
#include "platform/profiler.h"

namespace {

S32 FN_CDECL
cmpImageFunc(const void* p1, const void* p2)
{
   const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1);
   const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2);
   // Compares only non-transcluent images
   AssertFatal(psri1->isTranslucent == false && psri2->isTranslucent == false,
               "Error, only non-translucent images allowed here.");

   if (psri1->sortType != psri2->sortType)
   {
      // Normal render images are setup in such a way that increasing order
      //  renders
      return S32(psri1->sortType) - S32(psri2->sortType);
   }
   else
   {
      // Otherwise, sort on primary texture, as set by the sort key
      return S32(psri1->textureSortKey) - S32(psri2->textureSortKey);
   }
}

S32 FN_CDECL
cmpTPImageFunc(const void* p1, const void* p2)
{
   const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1);
   const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2);
   // Compares only non-transcluent images
   AssertFatal(psri1->isTranslucent == true && psri2->isTranslucent == true,
               "Error, only non-translucent images allowed here.");

   return S32(psri1->textureSortKey) - S32(psri2->textureSortKey);
}

S32 FN_CDECL
cmpPlaneImageFunc(const void* p1, const void* p2)
{
   const SceneRenderImage** psri1 = (const SceneRenderImage**)p1;
   const SceneRenderImage** psri2 = (const SceneRenderImage**)p2;

   // Normal render images are setup in such a way that increasing order
   //  renders
   if (((*psri2)->polyArea - (*psri1)->polyArea) < 0.0)
      return -1;
   else if (((*psri2)->polyArea - (*psri1)->polyArea) == 0.0)
      return 0;
   else
      return 1;
}


S32 FN_CDECL
cmpPointImageFunc(const void* p1, const void* p2)
{
   const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1);
   const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2);

   if (psri1->pointDistSq != psri2->pointDistSq)
   {
      if (psri1->pointDistSq > psri2->pointDistSq)
      {
         return -1;
      }
      else
      {
         return 1;
      }
   }
   else
   {
      if (psri1->tieBreaker == true)
      {
         return -1;
      }
      else
      {
         return 1;
      }
   }
}

inline void renderImage(SceneState* state, SceneRenderImage* image)
{
   PROFILE_START(SceneStateRenderImage);
#if defined(TORQUE_DEBUG)
   S32 m, p, t0, t1, v[4];
   F32 t0m[16], t1m[16];
   dglGetTransformState(&m, &p, &t0, t0m, &t1, t1m, v);
#endif
   image->obj->renderObject(state, image);

#if defined(TORQUE_DEBUG)
   if (dglCheckState(m, p, t0, t0m, t1, t1m, v) == false) {
      S32 bm, bp, bt0, bt1, bv[4];
      F32 bt0m[16], bt1m[16];
      dglGetTransformState(&bm, &bp, &bt0, bt0m, &bt1, bt1m, bv);
      AssertFatal(false,
                  avar("Error, object of class %s either unbalanced the xform stacks, or didn't reset the viewport!"
                       " mv(%d %d) proj(%d %d) t0(%d %d), t1(%d %d) (%d %d %d %d: %d %d %d %d)",
                       image->obj->getClassName(),
                       m, bm, p, bp, t0, bt0, t1, bt1, v[0], v[1], v[2], v[3], bv[0], bv[1], bv[2], bv[3]));
   }
#endif
   PROFILE_END();
}



} // namespace {}


// MM/JF: Added for mirrorSubObject fix.
void SceneState::setupClipPlanes(ZoneState& rState)
{
	F32 farOverNear = getFarPlane() / getNearPlane();

   Point3F farPosLeftUp = Point3F(rState.frustum[0] * farOverNear, getFarPlane(), rState.frustum[3] * farOverNear);
	Point3F farPosLeftDown = Point3F(rState.frustum[0] * farOverNear, getFarPlane(), rState.frustum[2] * farOverNear);
	Point3F farPosRightUp = Point3F(rState.frustum[1] * farOverNear, getFarPlane(), rState.frustum[3] * farOverNear);
	Point3F farPosRightDown = Point3F(rState.frustum[1] * farOverNear, getFarPlane(), rState.frustum[2] * farOverNear);

   MatrixF temp = mModelview;
	temp.inverse();
	temp.mulP(farPosLeftUp);
	temp.mulP(farPosLeftDown);
	temp.mulP(farPosRightUp);
	temp.mulP(farPosRightDown);

   sgOrientClipPlanes(&rState.clipPlanes[0], getCameraPosition(), farPosLeftUp, farPosLeftDown, farPosRightUp, farPosRightDown);

   rState.clipPlanesValid = true;
}


//--------------------------------------------------------------------------
//--------------------------------------
SceneState::SceneState(SceneState*    parent,
                       const U32      numZones,
                       F64            left,
                       F64            right,
                       F64            bottom,
                       F64            top,
                       F64            nearPlane,
                       F64            farPlane,
                       RectI          viewport,
                       const Point3F& camPos,
                       const MatrixF& modelview,
                       F32            fogDistance,
                       F32            visibleDistance,
                       ColorF         fogColor,
                       U32            numFogVolumes,
                       FogVolume*     fogVolumes,
                       TextureHandle  envMap,
                       F32            visFactor)
{
   mVisFactor = visFactor;

   mParent   = parent;
   mFlipCull = false;

   mBaseZoneState.render          = false;
   mBaseZoneState.clipPlanesValid = false;
   mBaseZoneState.frustum[0] = left;
   mBaseZoneState.frustum[1] = right;
   mBaseZoneState.frustum[2] = bottom;
   mBaseZoneState.frustum[3] = top;
   mBaseZoneState.viewport = viewport;
#if defined(TORQUE_DEBUG)
   // Avoid FPU exceptions in ZoneState constructors
   dMemset(mBaseZoneState.clipPlanes, 0, (sizeof mBaseZoneState.clipPlanes));
#endif

   mNearPlane   = nearPlane;
   mFarPlane    = farPlane;

   mModelview   = modelview;
   mCamPosition = camPos;
   mFogDistance = fogDistance;
   mVisibleDistance = visibleDistance;
   mFogColor = fogColor;

   mZoneStates.setSize(numZones);
   for (U32 i = 0; i < numZones; i++)
   {
      mZoneStates[i].render          = false;
      mZoneStates[i].clipPlanesValid = false;
   }

   mPortalOwner = NULL;
   mPortalIndex = 0xFFFFFFFF;

   mNumFogVolumes = numFogVolumes;
   mFogVolumes = fogVolumes;
   setupFog();

   mTerrainOverride = false;

   mEnvironmentMap  = envMap;

   mRenderImages.reserve(128);
   mTranslucentPlaneImages.reserve(128);
   mTranslucentPointImages.reserve(128);
   mTranslucentBeginImages.reserve(32);
   mTranslucentEndImages.reserve(32);
   mTranslucentBSP.reserve(64);
   mTranslucentBSP.setSize(1);
   mTranslucentBSP[0].riList = NULL;
   mTranslucentBSP[0].frontIndex = 0xFFFF;
   mTranslucentBSP[0].backIndex = 0xFFFF;
   mTranslucentBSP[0].rimage = NULL;
}

SceneState::~SceneState()
{
   U32 i;
   for (i = 0; i < mSubsidiaries.size(); i++)
      delete mSubsidiaries[i];

   for (i = 0; i < mRenderImages.size(); i++)
      delete mRenderImages[i];
   for (i = 0; i < mTranslucentPlaneImages.size(); i++)
      delete mTranslucentPlaneImages[i];
   for (i = 0; i < mTranslucentPointImages.size(); i++)
      delete mTranslucentPointImages[i];
   for (i = 0; i < mTranslucentEndImages.size(); i++)
      delete mTranslucentEndImages[i];
   for (i = 0; i < mTranslucentBeginImages.size(); i++)
      delete mTranslucentBeginImages[i];
}

void SceneState::setPortal(SceneObject* owner, const U32 index)
{
   mPortalOwner = owner;
   mPortalIndex = index;
}

void SceneState::insertRenderImage(SceneRenderImage* ri)
{
   if (ri->isTranslucent == false)
      mRenderImages.push_back(ri);
   else
   {
      if (ri->sortType == SceneRenderImage::Plane)
      {
         mTranslucentPlaneImages.push_back(ri);
      }
      else if (ri->sortType == SceneRenderImage::Point)
      {
         mTranslucentPointImages.push_back(ri);
      }
      else if (ri->sortType == SceneRenderImage::BeginSort)
      {
         mTranslucentBeginImages.push_back(ri);
      }
      else
      {
         AssertFatal(ri->sortType == SceneRenderImage::EndSort, "Error, bad transcluent sortType");
         mTranslucentEndImages.push_back(ri);
      }
   }
}

void SceneState::insertTransformPortal(SceneObject* owner, U32 portalIndex,
                                       U32 globalZone,     const Point3F& traversalStartPoint,
                                       const bool flipCull)
{
   mTransformPortals.increment();
   mTransformPortals.last().owner         = owner;
   mTransformPortals.last().portalIndex   = portalIndex;
   mTransformPortals.last().globalZone    = globalZone;
   mTransformPortals.last().traverseStart = traversalStartPoint;
   mTransformPortals.last().flipCull      = flipCull;
}

void SceneState::sortRenderImages()
{
   dQsort(mRenderImages.address(), mRenderImages.size(), sizeof(SceneRenderImage*), cmpImageFunc);
   dQsort(mTranslucentPointImages.address(), mTranslucentPointImages.size(), sizeof(SceneRenderImage*), cmpTPImageFunc);
   dQsort(mTranslucentPlaneImages.address(), mTranslucentPlaneImages.size(), sizeof(SceneRenderImage*), cmpPlaneImageFunc);
}

void SceneState::insertIntoNode(RenderBSPNode& rNode, SceneRenderImage* pImage, bool rendered)
{
   if (rNode.frontIndex == 0xFFFF)
   {
      // Split the node
      rNode.plane = pImage->plane;
      rNode.frontIndex = mTranslucentBSP.size() + 0;
      rNode.backIndex  = mTranslucentBSP.size() + 1;
      if (rendered)
         rNode.rimage = pImage;

      mTranslucentBSP.increment(2);
      mTranslucentBSP[rNode.frontIndex].riList     = NULL;
      mTranslucentBSP[rNode.frontIndex].frontIndex = 0xFFFF;
      mTranslucentBSP[rNode.frontIndex].backIndex  = 0xFFFF;
      mTranslucentBSP[rNode.frontIndex].rimage     = NULL;
      mTranslucentBSP[rNode.backIndex].riList      = NULL;
      mTranslucentBSP[rNode.backIndex].frontIndex  = 0xFFFF;
      mTranslucentBSP[rNode.backIndex].backIndex   = 0xFFFF;
      mTranslucentBSP[rNode.backIndex].rimage      = NULL;

      return;
   }

   // Determine which side we're on...
   U32 mask = 0;
   F32 dist = 0.0f;
   for (U32 i = 0; i < 4; i++)
   {
      F32 d = rNode.plane.distToPlane(pImage->poly[i]);
      if (d >= 0.0f)
      {
         mask |= (1 << i);
      }
      dist += d;
   }

   if (mask == 0xF)
   {
      // Front only
      insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage);
   }
   else if (mask == 0)
   {
      // Back only
      insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage);
   }
   else
   {
      // Both
      if (dist >= 0.0f)
      {
         // Render front
         insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage, true);
         insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage, false);
      }
      else
      {
         // Render back
         insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage, false);
         insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage, true);
      }
   }
}


void SceneState::buildTranslucentBSP()
{
   U32 i;
   for (i = 0; i < mTranslucentPlaneImages.size(); i++)
   {
      SceneRenderImage* pImage = mTranslucentPlaneImages[i];
      AssertFatal(pImage->sortType == SceneRenderImage::Plane, "Error, bad sort type on plane list!");

      insertIntoNode(mTranslucentBSP[0], pImage);
   }

   for (i = 0; i < mTranslucentPointImages.size(); i++)
   {
      SceneRenderImage* pImage = mTranslucentPointImages[i];
      AssertFatal(pImage->sortType == SceneRenderImage::Point, "Error, bad sort type on point list!");

      RenderBSPNode* pNode = &mTranslucentBSP[0];
      while (true)
      {
         if (pNode->frontIndex != 0xFFFF)
         {
            if (pNode->plane.distToPlane(pImage->poly[0]) >= 0)
               pNode = &mTranslucentBSP[pNode->frontIndex];
            else
               pNode = &mTranslucentBSP[pNode->backIndex];
         }
         else
         {
            pImage->pNext = pNode->riList;
            pNode->riList = pImage;
            break;
         }
      }
   }
}

void SceneState::renderNode(RenderBSPNode& rNode)
{
   if (rNode.frontIndex != 0xFFFF)
   {
      if (rNode.plane.distToPlane(mCamPosition) >= 0)
      {
         renderNode(mTranslucentBSP[rNode.backIndex]);
         if (rNode.rimage != NULL)
            renderImage(this, rNode.rimage);

         renderNode(mTranslucentBSP[rNode.frontIndex]);
      }
      else
      {
         renderNode(mTranslucentBSP[rNode.frontIndex]);
         if (rNode.rimage != NULL)
            renderImage(this, rNode.rimage);

         renderNode(mTranslucentBSP[rNode.backIndex]);
      }
   }
   else
   {
      Vector<SceneRenderImage*> imageList(128);
      SceneRenderImage* pImage = rNode.riList;
      while (pImage != NULL)
      {
         pImage->pointDistSq = (mCamPosition - pImage->poly[0]).lenSquared();
         imageList.push_back(pImage);
         pImage = pImage->pNext;
      }

      dQsort(imageList.address(), imageList.size(), sizeof(SceneRenderImage*), cmpPointImageFunc);

      for (U32 i = 0; i < imageList.size(); i++)
      {
         renderImage(this, imageList[i]);
      }
   }
}


void SceneState::renderCurrentImages()
{
   sortRenderImages();
   buildTranslucentBSP();

   if (mPortalOwner != NULL) 
   {
      // If we're a portalized object, we need to setup a user clip plane...
      PlaneF clipPlane;
      mPortalOwner->getWSPortalPlane(mPortalIndex, &clipPlane);

      if (mFlipCull)
         clipPlane.neg();

      GLdouble planeEQ[4];
      planeEQ[0] = clipPlane.x;
      planeEQ[1] = clipPlane.y;
      planeEQ[2] = clipPlane.z;
      planeEQ[3] = clipPlane.d;
      glClipPlane(GL_CLIP_PLANE0, planeEQ);
      glEnable(GL_CLIP_PLANE0);
   }

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   dglLoadMatrix(&mModelview);

   U32 i;
   for (i = 0; i < mRenderImages.size(); i++)
      renderImage(this, mRenderImages[i]);

   for (i = 0; i < mTranslucentBeginImages.size(); i++)
      renderImage(this, mTranslucentBeginImages[i]);

   renderNode(mTranslucentBSP[0]);

   for (i = 0; i < mTranslucentEndImages.size(); i++)
      renderImage(this, mTranslucentEndImages[i]);

   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();

   if (mPortalOwner != NULL)
      glDisable(GL_CLIP_PLANE0);
}

void SceneState::setupZoneProjection(const U32 zone)
{
   const ZoneState& rState = getZoneState(zone);
   AssertFatal(rState.render == true, "Error, should never set up a non-rendering zone!");

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   dglSetFrustum(rState.frustum[0], rState.frustum[1],
                 rState.frustum[2], rState.frustum[3],
                 getNearPlane(), getFarPlane(), dglIsOrtho());
   glMatrixMode(GL_MODELVIEW);
   dglSetViewport(rState.viewport);
}

void SceneState::setupObjectProjection(const SceneObject* obj)
{
   RectI viewport;
   F64   frustum[4] = { 1e10, -1e10, 1e10, -1e10 };

   bool init = false;
   SceneObjectRef* pWalk = obj->mZoneRefHead;
   AssertFatal(pWalk != NULL, "Error, object must exist in at least one zone to call this!");
   while (pWalk) 
   {
      const ZoneState& rState = getZoneState(pWalk->zone);
      if (rState.render == true) 
      {
         // frustum
         if (rState.frustum[0] < frustum[0]) frustum[0] = rState.frustum[0];
         if (rState.frustum[1] > frustum[1]) frustum[1] = rState.frustum[1];
         if (rState.frustum[2] < frustum[2]) frustum[2] = rState.frustum[2];
         if (rState.frustum[3] > frustum[3]) frustum[3] = rState.frustum[3];

         // viewport
         if (init == false)
            viewport = rState.viewport;
         else
            viewport.unionRects(rState.viewport);

         init = true;
      }
      pWalk = pWalk->nextInObj;
   }
   //AssertFatal(init, "Error, at least one zone must be rendered here!");

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   dglSetFrustum(frustum[0], frustum[1],
                 frustum[2], frustum[3],
                 getNearPlane(), getFarPlane(), dglIsOrtho());
   glMatrixMode(GL_MODELVIEW);
   dglSetViewport(viewport);
}

void SceneState::setupBaseProjection()
{
   const ZoneState& rState = getBaseZoneState();

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   dglSetFrustum(rState.frustum[0], rState.frustum[1],
                 rState.frustum[2], rState.frustum[3],
                 getNearPlane(), getFarPlane(), dglIsOrtho());
   glMatrixMode(GL_MODELVIEW);
   dglSetViewport(rState.viewport);
}


bool SceneState::isObjectRendered(const SceneObject* obj)
{
   // Don't bother if it's globally bounded.
   const SceneObjectRef* pWalk = obj->mZoneRefHead;

   static F32 darkToOGLCoord[16] = { 1, 0,  0, 0,
                                     0, 0, -1, 0,
                                     0, 1,  0, 0,
                                     0, 0,  0, 1 };
   static MatrixF darkToOGLMatrix;
   static bool matrixInitialized = false;
   if (matrixInitialized == false)
   {
      F32* m = darkToOGLMatrix;
      for (U32 i = 0; i < 16; i++)
         m[i] = darkToOGLCoord[i];
      darkToOGLMatrix.transpose();
      matrixInitialized = true;
   }

   while (pWalk != NULL) 
   {
      if (getZoneState(pWalk->zone).render == true)
      {
         ZoneState& rState = getZoneStateNC(pWalk->zone);
         if (rState.clipPlanesValid == false)
         {
            setupClipPlanes(rState);
         }

         if(obj->isGlobalBounds())
            return true;

         const Box3F& rObjBox = obj->getObjBox();
         const Point3F& rScale = obj->getScale();

         Point3F center;
         rObjBox.getCenter(&center);
         center.convolve(rScale);

         Point3F xRad((rObjBox.max.x - rObjBox.min.x) * 0.5 * rScale.x, 0, 0);
         Point3F yRad(0, (rObjBox.max.y - rObjBox.min.y) * 0.5 * rScale.y, 0);
         Point3F zRad(0, 0, (rObjBox.max.z - rObjBox.min.z) * 0.5 * rScale.z);

         obj->getRenderTransform().mulP(center);
         obj->getRenderTransform().mulV(xRad);
         obj->getRenderTransform().mulV(yRad);
         obj->getRenderTransform().mulV(zRad);

         bool render = true;
         for (U32 i = 0; i < 5; i++) 
         {
            if (rState.clipPlanes[i].whichSideBox(center, xRad, yRad, zRad, Point3F(0, 0, 0)) == PlaneF::Back) 
            {
               render = false;
               break;
            }
         }

         if (render)
            return true;
      }

      pWalk = pWalk->nextInObj;
   }

   return false;
}

//--------------------------------------------------------------------------
//--------------------------------------

bool checkFogBandBoxVisible(F32 dist, F32 haze, F32 low, F32 high, Vector<SceneState::FogBand> &fb)
{
   // if there are no fog bands, no fog - it's visible
   if(!fb.size())
      return true;
   // if the first fog band is unfogged and the box
   // is inside the band, it's visible
   if(fb[0].isFog == false && low < fb[0].cap)
      return true;

   // check the case of the camera in a fog band
   if(fb[0].isFog)
   {
      // if the low point is in the fog, we check that

      if(low < fb[0].cap)
      {
         if(haze + dist * fb[0].factor < 1)
            return true;
         // if low and high are both in the fog band
         // and low isn't visible, neither is high
         if(high < fb[0].cap)
            return false;
         // check the high point...
         F32 highDist = mSqrt(high * high + dist * dist - low * low);
         return haze + (fb[0].cap / high) * highDist * fb[0].factor < 1;
      }
      // ok, both low and high are above the cap of the plane
      // so we have to check only the high point (bigger triangle means less fog
      // applied (higher top means steeper slope on the hypotenuse))

      F32 highDist = mSqrt(high * high + dist * dist - low * low);
      return haze + (fb[0].cap / high) * highDist * fb[0].factor < 1;
   }
   // ok, fb[0] is not fogged, meaning there is an empty layer
   // followed by a fog plane, followed by the box.

   // we only test the one fog volume for visibility of the box...
   F32 fogStart = fb[0].cap;
   F32 fogEnd = fogStart + fb[1].cap;

   // if the low is in the fog band, we have to check
   // low, followed by possibly high
   // if low is above the fog band we only have to check high point
   if(low > fogEnd)
   {
      // only check the high point through the fog
      F32 highDist = mSqrt(high * high + dist * dist - low * low);
      return haze + (fb[1].cap / high) * highDist * fb[1].factor < 1;
   }
   // last case, low is in the fog band
   // check low vis:
   if(haze + fb[1].factor * dist * (low - fogStart) / low < 1)
      return true;
   // if the high point is in the same fog band, it's not visible
   if(high < fogEnd)
      return false;
   // ok, check the high point
   F32 highDist = mSqrt(high * high + dist * dist - low * low);
   return haze + (fb[1].cap / high) * highDist * fb[1].factor < 1;
}

bool SceneState::isBoxFogVisible(F32 dist, F32 top, F32 bottom)
{   
   F32 camZ = mCamPosition.z;
   float haze = 0;
   if(dist > mFogDistance) 
   {
      float distFactor = (dist - mFogDistance) * mFogScale - 1.0;
      haze = 1.0 - distFactor * distFactor;
   }
   F32 distSq = dist * dist;
   
   // the object is below:
   if(top < camZ)
   {
      return checkFogBandBoxVisible(dist, haze, camZ - top, camZ - bottom, mNegFogBands);
   }
   else if(bottom > camZ)
   {
      return checkFogBandBoxVisible(dist, haze, bottom - camZ, top - camZ, mPosFogBands);
   }
   else
   {
      // spans the fog...
      if(!mNegFogBands.size() || !mPosFogBands.size() || !mPosFogBands[0].isFog)
         return true;
      // ok, we know there is at least one fog band and the camera is in it.
      // check if the object is visible through the fog...
      if(haze + dist * mPosFogBands[0].factor < 1)
         return true;

      // ok, check the top stretch...
      // we know now that since the box spans the horizontal,
      // that dist is a horizontal (deltaZ = 0)
      // so we want the segment of the hypotenuse that goes through
      // the fog.

      F32 ht = top - camZ;
      // don't do it if the top is in the fog
      if(ht > mPosFogBands[0].cap)
      {
         if(haze + (mPosFogBands[0].cap / ht) * mSqrt(dist * dist + ht * ht) * mPosFogBands[0].factor < 1)
            return true;
      }

      // ok, last chance, check the bottom segment
      ht = camZ - bottom;
      if(ht < mNegFogBands[0].cap)
         return false;
      return haze + (mNegFogBands[0].cap / ht) * mSqrt(dist * dist + ht * ht) * mNegFogBands[0].factor < 1;
   }
}

void SceneState::setupFog()
{
   if( mVisibleDistance == mFogDistance ) 
   {
      // FIXME: arbitrary large constant
      mFogScale = 1000.0f;
   }
   else 
   {
      mFogScale = 1.0 / (mVisibleDistance - mFogDistance);
   }

   // construct positive fog volumes
   mPosFogBands.clear();
   F32 camZ = mCamPosition.z;

   S32 i;
   for(i = 0; i < mNumFogVolumes; i++)
   {
      if(camZ < mFogVolumes[i].maxHeight)
         break;
   }

   if(i < mNumFogVolumes)
   {
      float prevHeight = camZ;
      for(;i < mNumFogVolumes; i++)
      {
         if(prevHeight < mFogVolumes[i].minHeight)
         {
            FogBand fb;
            fb.isFog = false;
            fb.color.set(mFogVolumes[i].color.red,
                         mFogVolumes[i].color.green,
                         mFogVolumes[i].color.blue,
                         mFogVolumes[i].color.alpha);
            fb.cap     = mFogVolumes[i].minHeight - prevHeight;
            prevHeight = mFogVolumes[i].minHeight;
            mPosFogBands.push_back(fb);
         }
         FogBand fb;
         fb.isFog = true;
         fb.cap = mFogVolumes[i].maxHeight - prevHeight;
         fb.color.set(mFogVolumes[i].color.red,
                      mFogVolumes[i].color.green,
                      mFogVolumes[i].color.blue,
                      mFogVolumes[i].color.alpha);
         fb.factor = (1 / (mFogVolumes[i].visibleDistance * mVisFactor)) * mFogVolumes[i].percentage;
         prevHeight = mFogVolumes[i].maxHeight;
         mPosFogBands.push_back(fb);
      }
   }

   // construct negative fog volumes
   mNegFogBands.clear();
   for(i = mNumFogVolumes - 1; i >= 0; i--)
   {
      if(camZ > mFogVolumes[i].minHeight)
         break;
   }

   if(i >= 0)
   {
      float prevHeight = camZ;
      for(;i >= 0; i--)
      {
         if(prevHeight > mFogVolumes[i].maxHeight)
         {
            FogBand fb;
            fb.isFog = false;
            fb.cap = prevHeight - mFogVolumes[i].maxHeight;
            prevHeight = mFogVolumes[i].maxHeight;
            fb.color.set(mFogVolumes[i].color.red,
                         mFogVolumes[i].color.green,
                         mFogVolumes[i].color.blue,
                         mFogVolumes[i].color.alpha);
            mNegFogBands.push_back(fb);
         }
         FogBand fb;
         fb.isFog = true;
         fb.cap = prevHeight - mFogVolumes[i].minHeight;
         fb.factor = (1 / (mFogVolumes[i].visibleDistance * mVisFactor)) * mFogVolumes[i].percentage;
         prevHeight = mFogVolumes[i].minHeight;
         fb.color.set(mFogVolumes[i].color.red,
                      mFogVolumes[i].color.green,
                      mFogVolumes[i].color.blue,
                      mFogVolumes[i].color.alpha);
         mNegFogBands.push_back(fb);
      }
   }
}

void SceneState::getFogs(float dist, float deltaZ, ColorF *array, U32 &numFogs)
{
   numFogs = 0;
   Vector<FogBand> *band;
   if(deltaZ < 0)
   {
      deltaZ = -deltaZ;
      band = &mNegFogBands;
   }
   else
      band = &mPosFogBands;

   float ht = deltaZ;
   for(int i = 0; i < band->size(); i++)
   {
      FogBand &bnd = (*band)[i];

      if(ht < bnd.cap)
      {
         if(bnd.isFog)
            array[numFogs++] = ColorF(bnd.color.red, bnd.color.green, bnd.color.blue, dist * bnd.factor);
         break;
      }
      float subDist = dist * bnd.cap / ht;
      if(bnd.isFog)
      {
         array[numFogs++] = ColorF(bnd.color.red,
                                   bnd.color.green,
                                   bnd.color.blue,
                                   subDist * bnd.factor);
      }
      dist -= subDist;
      ht   -= bnd.cap;
   }
}

F32 SceneState::getFog(float dist, float deltaZ, S32 volKey)
{
   float haze = 0;
   Vector<FogBand> *band;
   if(deltaZ < 0)
   {
      deltaZ = -deltaZ;
      band = &mNegFogBands;
   }
   else
      band = &mPosFogBands;

   if(band->size() < 1)
      return haze;

   float ht = deltaZ;
   FogBand &bnd = (*band)[volKey];

   if(ht < bnd.cap)
   {
      if(bnd.isFog)
         haze += dist * bnd.factor;
   }
   else
   {
      float subDist = dist * bnd.cap / ht;
      if(bnd.isFog)
         haze += subDist * bnd.factor;
   }

   return haze;
}

F32 SceneState::getFog(float dist, float deltaZ)
{
   float haze = 0;
   Vector<FogBand> *band;
   if(deltaZ < 0)
   {
      deltaZ = -deltaZ;
      band = &mNegFogBands;
   }
   else
      band = &mPosFogBands;

   float ht = deltaZ;
   for(int i = 0; i < band->size(); i++)
   {
      FogBand &bnd = (*band)[i];

      if(ht < bnd.cap)
      {
         if(bnd.isFog)
            haze += dist * bnd.factor;
         break;
      }
      float subDist = dist * bnd.cap / ht;
      if(bnd.isFog)
         haze += subDist * bnd.factor;
      dist -= subDist;
      ht -= bnd.cap;
   }
   return haze;
}

F32 SceneState::getHazeAndFog(float dist, float deltaZ) const
{
   float haze = 0;

   if(dist > mFogDistance) {
      if (dist > mVisibleDistance)
         return 1.0;

      float distFactor = (dist - mFogDistance) * mFogScale - 1.0;
      haze = 1.0 - distFactor * distFactor;
   }

   const Vector<FogBand> *band;
   if(deltaZ < 0)
   {
      deltaZ = -deltaZ;
      band = &mNegFogBands;
   }
   else
      band = &mPosFogBands;

   float ht = deltaZ;
   for(int i = 0; i < band->size(); i++)
   {
      const FogBand &bnd = (*band)[i];

      if(ht < bnd.cap)
      {
         if(bnd.isFog)
            haze += dist * bnd.factor;
         break;
      }
      float subDist = dist * bnd.cap / ht;
      if(bnd.isFog)
         haze += subDist * bnd.factor;
      dist -= subDist;
      ht -= bnd.cap;
   }
   if(haze > 1)
      return 1;
   return haze;
}


void SceneState::setImageRefPoint(SceneObject* obj, SceneRenderImage* image) const
{
   const Box3F& rBox = obj->getObjBox();
   Point3F objSpaceCamPosition = mCamPosition;

   obj->getRenderWorldTransform().mulP(objSpaceCamPosition);
   objSpaceCamPosition.convolveInverse(obj->getScale());

   image->poly[0] = rBox.getClosestPoint(objSpaceCamPosition);
   image->poly[0].convolve(obj->getScale());

   obj->getRenderTransform().mulP(image->poly[0]);
}

//--------------------------------------------------------------------------
SceneRenderImage::~SceneRenderImage()
{

}