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

#include "platform/platform.h"
#include "dgl/dgl.h"
#include "game/game.h"
#include "math/mMath.h"
#include "console/simBase.h"
#include "console/consoleTypes.h"
#include "console/console.h"
#include "game/collisionTest.h"

static F32 BoxSize = 2;

bool CollisionTest::testPolytope = false;
bool CollisionTest::testClippedPolyList = false;
bool CollisionTest::testDepthSortList = false;
bool CollisionTest::testExtrudedPolyList = false;
bool CollisionTest::depthSort = false;
bool CollisionTest::depthRender = false;
bool CollisionTest::renderAlways = false;


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

CollisionTest::CollisionTest()
{
}

CollisionTest::~CollisionTest()
{
}

void CollisionTest::consoleInit()
{
   Con::addVariable("Collision::boxSize",TypeF32,&BoxSize);

   Con::addVariable("Collision::testPolytope",TypeBool,&testPolytope);
   Con::addVariable("Collision::testClippedPolyList",TypeBool,&testClippedPolyList);
   Con::addVariable("Collision::testExtrudedPolyList",TypeBool,&testExtrudedPolyList);
   Con::addVariable("Collision::testDepthSortList",TypeBool,&testDepthSortList);

   Con::addVariable("Collision::depthSort",TypeBool,&depthSort);
   Con::addVariable("Collision::depthRender",TypeBool,&depthRender);
   Con::addVariable("Collision::renderAlways",TypeBool,&renderAlways);
}

void CollisionTest::collide(const MatrixF& transform)
{
   //
   Point3F pos;
   transform.getColumn(3,&pos);
   boundingBox.min = pos - Point3F(BoxSize,BoxSize,BoxSize);
   boundingBox.max = pos + Point3F(BoxSize,BoxSize,BoxSize);
   boundingSphere.center = pos;
   boundingSphere.radius = BoxSize * 1.5;

   if (testPolytope) {
      MatrixF imat(true);
      volume.buildBox(imat,boundingBox);
      tree.clear();
   }

   if (testClippedPolyList) {
      polyList.clear();
      polyList.mPlaneList.clear();
      polyList.mNormal.set(0,0,0);

      // Planes bounding the square.
      polyList.mPlaneList.setSize(6);
      polyList.mPlaneList[0].set(boundingBox.min,VectorF(-1,0,0));
      polyList.mPlaneList[1].set(boundingBox.max,VectorF(0,1,0));
      polyList.mPlaneList[2].set(boundingBox.max,VectorF(1,0,0));
      polyList.mPlaneList[3].set(boundingBox.min,VectorF(0,-1,0));
      polyList.mPlaneList[4].set(boundingBox.min,VectorF(0,0,-1));
      polyList.mPlaneList[5].set(boundingBox.max,VectorF(0,0,1));
   }

   if (testDepthSortList) {
      depthSortList.clear();
      mDepthSortExtent.set(5,20,5); // hard-code for now
      MatrixF mat = transform;
      mat.inverse(); // we want world to camera (or whatever transform represents)
      depthSortList.set(mat,mDepthSortExtent);

      // we use a different box and sphere...
      // our box starts at the camera and goes forward mDepthSortExtent.y
      // with width and height of mDepthSortExtent.x and mDepthSortExtent.z
      Point3F x,y,z,p;
      transform.getColumn(0,&x);
      transform.getColumn(1,&y);
      transform.getColumn(2,&z);
      transform.getColumn(3,&p);
      x *= 0.5f * mDepthSortExtent.x;
      y *= mDepthSortExtent.y;
      z *= 0.5f * mDepthSortExtent.z;
      Point3F boxMin = p;
      Point3F boxMax = p;
      boxMin.setMin(p-x-z);
      boxMin.setMin(p-x+z);
      boxMin.setMin(p+x-z);
      boxMin.setMin(p+x+z);
      boxMin.setMin(p-x-z+y);
      boxMin.setMin(p-x+z+y);
      boxMin.setMin(p+x-z+y);
      boxMin.setMin(p+x+z+y);

      boxMax.setMax(p-x-z);
      boxMax.setMax(p-x+z);
      boxMax.setMax(p+x-z);
      boxMax.setMax(p+x+z);
      boxMax.setMax(p-x-z+y);
      boxMax.setMax(p-x+z+y);
      boxMax.setMax(p+x-z+y);
      boxMax.setMax(p+x+z+y);

      Point3F boxCenter = boxMin + boxMax;
      boxCenter *= 0.5f;
      F32 boxRadius = (boxMax-boxMin).len();

      mDepthBox.min = boxMin;
      mDepthBox.max = boxMax;
      mDepthSphere.center = boxCenter;
      mDepthSphere.radius = boxRadius;
   }

   if (testExtrudedPolyList) {
      MatrixF imat(1);
      polyhedron.buildBox(imat,boundingBox);
      VectorF v1(0,3,0);
      transform.mulV(v1,&extrudeVector);
      extrudedList.extrude(polyhedron,extrudeVector);
      extrudedList.setVelocity(extrudeVector);
      extrudedList.setCollisionList(&collisionList);

      Point3F p1 = pos + extrudeVector;
      boundingBox.min = boundingBox.max = pos;
      boundingBox.min.setMin(p1);
      boundingBox.max.setMax(p1);
      boundingBox.min -= Point3F(BoxSize,BoxSize,BoxSize);
      boundingBox.max += Point3F(BoxSize,BoxSize,BoxSize);
      boundingSphere.radius += extrudeVector.len();
   }

   if (testPolytope || testClippedPolyList || testExtrudedPolyList || testDepthSortList) {
      testPos = boundingSphere.center;
      gClientContainer.findObjects(0xFFFFFFFF,CollisionTest::callback,this);
   }

   if (testExtrudedPolyList) {
      extrudedList.adjustCollisionTime();
   }
}

void CollisionTest::callback(SceneObject* obj, void *thisPtr)
{
   CollisionTest* ptr = reinterpret_cast<CollisionTest*>(thisPtr);

   if (testPolytope) {
      if (BSPNode* root = obj->buildCollisionBSP(&ptr->tree,ptr->boundingBox,ptr->boundingSphere))
         ptr->volume.intersect(obj,root);
   }
   if (testClippedPolyList) {
      obj->buildPolyList(&ptr->polyList,ptr->boundingBox,ptr->boundingSphere);
   }
   if (testExtrudedPolyList) {
      obj->buildPolyList(&ptr->extrudedList,ptr->boundingBox,ptr->boundingSphere);
   }
   if (testDepthSortList) {
      obj->buildPolyList(&ptr->depthSortList,ptr->mDepthBox,ptr->mDepthSphere);
   }
}

extern void wireCube(F32 size,Point3F pos);

void CollisionTest::render()
{
   bool collision = false;
   if (testPolytope || renderAlways) {
      if (volume.didIntersect())
         volume.render();
   }
   if (testClippedPolyList || renderAlways) {
      if (polyList.mPolyList.size())
         collision = true;
      polyList.render();
   }
   if (testExtrudedPolyList || renderAlways) {
      if (collisionList.count)
         collision = true;
      extrudedList.render();
      glPushAttrib(GL_DEPTH_BUFFER_BIT);
      glEnable(GL_DEPTH_TEST);
      polyhedron.render(extrudeVector,collisionList.t);
      glPopAttrib();
   }
   if (testDepthSortList || renderAlways) {
      if (depthSort && testDepthSortList)
         depthSortList.sort();
      // should we write depth values of polys...
      // if polys are correctly sorted then writing depth values
      // should result in no overlap of polys when looking down
      // from camera...otoh, if polys are out of order, we should
      // see overlap
      DepthSortList::renderWithDepth = depthRender;
      depthSortList.render();
   }

   if (collision || renderAlways)
     wireCube(BoxSize,testPos);
}

