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

#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sgUtil.h"
#include "sim/sceneObject.h"
#include "sceneGraph/sceneState.h"
#include "math/mMatrix.h"
#include "dgl/dgl.h"
#include "core/polyList.h"
#include "terrain/terrData.h"

namespace {

class PotentialRenderList
{
  public:
   Point3F farPosLeftUp;
   Point3F farPosLeftDown;
   Point3F farPosRightUp;
   Point3F farPosRightDown;
   Point3F camPos;
   F32     viewDistSquared;
   Box3F   mBox;

   Vector<SceneObject*> mList;

   PlaneF viewPlanes[5];
   SceneState* mState;

  public:
   void insertObject(SceneObject* obj);
   void setupClipPlanes(SceneState*);
};

// MM/JF: Added for mirrorSubObject fix.
void PotentialRenderList::setupClipPlanes(SceneState* state)
{
	mState = state;
	camPos = state->getCameraPosition();
	F32 farOverNear = state->getFarPlane() / state->getNearPlane();
	farPosLeftUp = Point3F(state->getBaseZoneState().frustum[0] * farOverNear, state->getFarPlane(), state->getBaseZoneState().frustum[3] * farOverNear);
	farPosLeftDown = Point3F(state->getBaseZoneState().frustum[0] * farOverNear, state->getFarPlane(), state->getBaseZoneState().frustum[2] * farOverNear);
	farPosRightUp = Point3F(state->getBaseZoneState().frustum[1] * farOverNear, state->getFarPlane(), state->getBaseZoneState().frustum[3] * farOverNear);
	farPosRightDown = Point3F(state->getBaseZoneState().frustum[1] * farOverNear, state->getFarPlane(), state->getBaseZoneState().frustum[2] * farOverNear);
	MatrixF temp = state->mModelview;
	temp.inverse();
	temp.mulP(farPosLeftUp);
	temp.mulP(farPosLeftDown);
	temp.mulP(farPosRightUp);
	temp.mulP(farPosRightDown);
	mBox.min = camPos;
	mBox.min.setMin(farPosLeftUp);
	mBox.min.setMin(farPosLeftDown);
	mBox.min.setMin(farPosRightUp);
	mBox.min.setMin(farPosRightDown);
	mBox.max = camPos;
	mBox.max.setMax(farPosLeftUp);
	mBox.max.setMax(farPosLeftDown);
	mBox.max.setMax(farPosRightUp);
	mBox.max.setMax(farPosRightDown);
	sgOrientClipPlanes(&viewPlanes[0], camPos, farPosLeftUp, farPosLeftDown, farPosRightUp, farPosRightDown);
}


void PotentialRenderList::insertObject(SceneObject* obj)
{
   // Check to see if we need to render always.
   if(obj->isGlobalBounds())
   {
      mList.push_back(obj);
      return;
   }

   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 (viewPlanes[i].whichSideBox(center, xRad, yRad, zRad, Point3F(0, 0, 0)) == PlaneF::Back) 
      {
         render = false;
         break;
      }
   }

   if (render)
      mList.push_back(obj);
}

void prlInsertionCallback(SceneObject* obj, void *key)
{
   PotentialRenderList* prList = (PotentialRenderList*)key;

   if(obj->isGlobalBounds())
   {
      prList->insertObject(obj);
      return;
   }

   Point3F closestPt = obj->getWorldBox().getClosestPoint(prList->camPos);
   F32 lenSquared = (closestPt - prList->camPos).lenSquared();
   if (lenSquared >= prList->viewDistSquared)
      return;

   F32 len    = mSqrt(lenSquared);
   F32 top    = obj->getWorldBox().max.z;
   F32 bottom = obj->getWorldBox().min.z;
   if (prList->mState->isBoxFogVisible(len, top, bottom))
      prList->insertObject(obj);
}

} // namespace {}

void SceneGraph::buildSceneTree(SceneState*  state,
                                SceneObject* baseObject,
                                const U32    baseZone,
                                const U32    currDepth,
                                const U32    objectMask )
{
   AssertFatal(this == gClientSceneGraph, "Error, only the client scenegraph can support this call!");

   // Search proceeds from the baseObject, and starts in the baseZone.
   // General Outline:
   //    - Traverse up the tree, stopping at either the root, or the last interior
   //       that prevents traversal outside
   //    - Query the container database for all objects intersecting the viewcone,
   //       which is clipped to the bounding box returned at the last stage of the
   //       above traversal.
   //    - Topo sort the returned objects.
   //    - Traverse through the list, calling setupZones on zone managers,
   //       and retreiving render images from all applicable objects (including
   //       ZM's)
   //    - This process may return "Transform portals", i.e., mirrors, rendered
   //       teleporters, etc.  For each of these, create a new SceneState object
   //       subsidiary to state, and restart the traversal, with the new parameters,
   //       and the correct baseObject and baseZone.

   // Objects (in particular, those managers that are part of the initial up
   //  traversal) keep track of whether or not they have returned a render image
   //  to the current state by a key, and the state object pointer.
   smStateKey++;

   // Save off the base state...
   SceneState::ZoneState saveBase = state->getBaseZoneState();

   SceneObject* pTraversalRoot = baseObject;
   U32          rootZone       = baseZone;
   while (true) 
   {
      if (pTraversalRoot->prepRenderImage(state, smStateKey, rootZone, true)) 
      {
         if (pTraversalRoot->getNumCurrZones() != 1)
            Con::errorf(ConsoleLogEntry::General,
                        "Error, must have one and only one zone to be a traversal root.  %s has %d",
                        pTraversalRoot->getName(), pTraversalRoot->getNumCurrZones());

         rootZone       = pTraversalRoot->getCurrZone(0);
         pTraversalRoot = getZoneOwner(rootZone);
      } 
      else 
      {
         break;
      }
   }

   // Restore the base state...
   SceneState::ZoneState& rState = state->getBaseZoneStateNC();
   rState = saveBase;

   // Ok.  Now we have renderimages for anything north of the object in the
   //  tree.  Create the query polytope, and clip it to the bounding box of
   //  the traversalRoot object.
   PotentialRenderList prl;
   prl.setupClipPlanes(state);
   prl.viewDistSquared = getVisibleDistanceMod() * getVisibleDistanceMod();

   // We only have to clip the mBox field
   AssertFatal(prl.mBox.isOverlapped(pTraversalRoot->getWorldBox()),
               "Error, prl box must overlap the traversal root");
   prl.mBox.min.setMax(pTraversalRoot->getWorldBox().min);
   prl.mBox.max.setMin(pTraversalRoot->getWorldBox().max);
   prl.mBox.min -= Point3F(5, 5, 5);
   prl.mBox.max += Point3F(5, 5, 5);
   AssertFatal(prl.mBox.isValidBox(), "Error, invalid query box created!");

   // Query against the container database, storing the objects in the
   //  potentially rendered list.  Note: we can query against the client
   //  container without testing, since only the client will be calling this
   //  function.  This is assured by the assert at the top...
   gClientContainer.findObjects(prl.mBox, objectMask, prlInsertionCallback, &prl);

   // Clear the object colors
   U32 i;
   for (i = 0; i < prl.mList.size(); i++)
      prl.mList[i]->setTraversalState( SceneObject::Pending );

   for (i = 0; i < prl.mList.size(); i++)
      if( prl.mList[i]->getTraversalState() == SceneObject::Pending )
         treeTraverseVisit(prl.mList[i], state, smStateKey);

   if (currDepth < csmMaxTraversalDepth && state->mTransformPortals.size() != 0) 
   {
      // Need to handle the transform portals here.
      //
      for (U32 i = 0; i < state->mTransformPortals.size(); i++) 
      {
         const SceneState::TransformPortal& rPortal  = state->mTransformPortals[i];
         const SceneState::ZoneState&       rPZState = state->getZoneState(rPortal.globalZone);
         AssertFatal(rPZState.render == true, "Error, should not have returned a portal if the zone isn't rendering!");

         Point3F cameraPosition = state->getCameraPosition();
         rPortal.owner->transformPosition(rPortal.portalIndex, cameraPosition);

         // Setup the new modelview matrix...
         MatrixF oldMV;
         MatrixF newMV;
         dglGetModelview(&oldMV);
         rPortal.owner->transformModelview(rPortal.portalIndex, oldMV, &newMV);

         // Here's the tricky bit.  We have to derive a new frustum and viewport
         //  from the portal, but we have to do it in the NEW coordinate space.
         //  Seems easiest to dump the responsibility on the object that was rude
         //  enough to make us go to all this trouble...
         F64   newFrustum[4];
         RectI newViewport;

         bool goodPortal = rPortal.owner->computeNewFrustum(rPortal.portalIndex, // which portal?
                                                            rPZState.frustum,    // old view params
                                                            state->mNearPlane,
                                                            state->mFarPlane,
                                                            rPZState.viewport,
                                                            newFrustum,          // new view params
                                                            newViewport,
                                                            state->mFlipCull);

         if (goodPortal == false) 
         {
            // Portal isn't visible, or is clipped out by the zone parameters...
            continue;
         }

         SceneState* newState = new SceneState(state,
                                               mCurrZoneEnd,
                                               newFrustum[0],
                                               newFrustum[1],
                                               newFrustum[2],
                                               newFrustum[3],
                                               state->mNearPlane,
                                               state->mFarPlane,
                                               newViewport,
                                               cameraPosition,
                                               newMV,
                                               mFogDistance,
                                               mVisibleDistance,
                                               mFogColor,
                                               mNumFogVolumes,
                                               mFogVolumes,
                                               state->getEnvironmentMap(),
                                               smVisibleDistanceMod);
         newState->mFlipCull = state->mFlipCull ^ rPortal.flipCull;
         newState->setPortal(rPortal.owner, rPortal.portalIndex);


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

         // Find the start zone.  Note that in a traversal descent, we start from
         //  the traversePoint of the transform portal, which is conveniently in
         //  world space...
         SceneObject* startObject;
         U32          startZone;
         findZone(rPortal.traverseStart, startObject, startZone);

         buildSceneTree(newState, startObject, startZone, currDepth + 1, objectMask);

         // Pop off the new modelview
         glMatrixMode(GL_MODELVIEW);
         glPopMatrix();

         // Push the subsidiary...
         state->mSubsidiaries.push_back(newState);
      }
   }

   // Ok, that's it!
}

bool terrCheck(TerrainBlock* pBlock,
               SceneObject*  pObj,
               const Point3F camPos);

void SceneGraph::treeTraverseVisit(SceneObject* obj,
                                   SceneState*  state,
                                   const U32    stateKey)
{
   if (obj->getNumCurrZones() == 0) 
   {
      obj->setTraversalState( SceneObject::Done );
      return;
   }

   AssertFatal(obj->getTraversalState() == SceneObject::Pending,
               "Wrong state for this stage of the traversal!");
   obj->setTraversalState(SceneObject::Working); //	TraversalState Not being updated correctly 'Gonzo'

   SceneObjectRef* pWalk = obj->mZoneRefHead;
   AssertFatal(pWalk != NULL, "Error, must belong to something!");
   while (pWalk) 
   {
      // Determine who owns this zone...
      SceneObject* pOwner = getZoneOwner(pWalk->zone);
      if( pOwner->getTraversalState() == SceneObject::Pending )
         treeTraverseVisit(pOwner, state, stateKey);

      pWalk = pWalk->nextInObj;
   }

   obj->setTraversalState( SceneObject::Done );//obj->setTraverseColor(SceneObject::Black);

   // Cull it, but not if it's too low or there's no terrain to occlude against, or if it's global...
   if (getCurrentTerrain() != NULL && obj->getWorldBox().min.x > -1e5 && !obj->isGlobalBounds())
   {
      bool doTerrCheck = true;
      SceneObjectRef* pRef = obj->mZoneRefHead;
      while (pRef != NULL)
      {
         if (pRef->zone != 0)
         {
            doTerrCheck = false;
            break;
         }
         pRef = pRef->nextInObj;
      }

      if (doTerrCheck == true && terrCheck(getCurrentTerrain(), obj, state->getCameraPosition()) == true)
         return;
   }

   obj->prepRenderImage(state, stateKey, 0xFFFFFFFF);
}

bool terrCheck(TerrainBlock* pBlock,
               SceneObject*  pObj,
               const Point3F camPos)
{
   // Don't try to occlude globally bounded objects.
   if(pObj->isGlobalBounds())
      return false;

   Point3F localCamPos = camPos;
   pBlock->getWorldTransform().mulP(localCamPos);
   F32 height;
   pBlock->getHeight(Point2F(localCamPos.x, localCamPos.y), &height);
   bool aboveTerrain = (height <= localCamPos.z);

   // Don't occlude if we're below the terrain.  This prevents problems when
   //  looking out from underground bases...
   if (aboveTerrain == false)
      return false;

   const Box3F& oBox = pObj->getObjBox();
   F32 minSide = getMin(oBox.len_x(), oBox.len_y());
   if (minSide > 85.0f)
      return false;

   const Box3F& rBox = pObj->getWorldBox();
   Point3F ul(rBox.min.x, rBox.min.y, rBox.max.z);
   Point3F ur(rBox.min.x, rBox.max.y, rBox.max.z);
   Point3F ll(rBox.max.x, rBox.min.y, rBox.max.z);
   Point3F lr(rBox.max.x, rBox.max.y, rBox.max.z);

   pBlock->getWorldTransform().mulP(ul);
   pBlock->getWorldTransform().mulP(ur);
   pBlock->getWorldTransform().mulP(ll);
   pBlock->getWorldTransform().mulP(lr);

   Point3F xBaseL0_s = ul - localCamPos;
   Point3F xBaseL0_e = lr - localCamPos;
   Point3F xBaseL1_s = ur - localCamPos;
   Point3F xBaseL1_e = ll - localCamPos;

   static F32 checkPoints[3] = {0.75, 0.5, 0.25};
   RayInfo rinfo;
   for (U32 i = 0; i < 3; i++)
   {
      Point3F start = (xBaseL0_s * checkPoints[i]) + localCamPos;
      Point3F end   = (xBaseL0_e * checkPoints[i]) + localCamPos;

      if (pBlock->castRay(start, end, &rinfo))
         continue;

      pBlock->getHeight(Point2F(start.x, start.y), &height);
      if ((height <= start.z) == aboveTerrain)
         continue;

      start = (xBaseL1_s * checkPoints[i]) + localCamPos;
      end   = (xBaseL1_e * checkPoints[i]) + localCamPos;

      if (pBlock->castRay(start, end, &rinfo))
         continue;

      Point3F test = (start + end) * 0.5;
      if (pBlock->castRay(localCamPos, test, &rinfo) == false)
         continue;

      return true;
   }

   return false;
}
