#include "collision/convexBrush.h"

#include "dgl/dgl.h"
#include "math/mRandom.h"
#include "game/gameConnection.h"
#include "math/mPlaneTransformer.h"

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

ConvexBrush::ConvexBrush()
{
   mBounds.min = Point3F(0, 0, 0);
   mBounds.max = Point3F(0, 0, 0);

   mBrushScale = 1.0f;
   mType = 0;
   mCentroid = Point3F(0.0f, 0.0f, 0.0f);
   mStatus = Unprocessed;
   mSelected = false;

   // Set our transform to no translation or rotation
   mTransform.identity();

   mTransformScratch.identity();
   mRotationScratch.identity();

   mScale.set(1.0f, 1.0f, 1.0f);
   mRotation.set(EulerF(0.0f, 0.0f, 0.0f));

   // No id assigned yet
   mID = -1;

   mOwner = NULL;
}

ConvexBrush::~ConvexBrush()
{
}

// Just adds a plane to the list and default values for texture info
bool ConvexBrush::addPlane(PlaneF pln)
{
   mFaces.mPolyList.increment();
   mFaces.mPolyList.last().material = 0;
   mFaces.mPolyList.last().object = NULL;
   mFaces.mPolyList.last().surfaceKey = 0;
   mFaces.mPolyList.last().vertexCount = 0;
   mFaces.mPolyList.last().vertexStart = 0;
   mFaces.mPolyList.last().plane = mFaces.addPlane(pln);

   // The texgens and the faces increase together
   mTexInfos.increment();
   mTexInfos.last().texGens[0] = PlaneF(1.0f, 0.0f, 0.0f, 1.0f);
   mTexInfos.last().texGens[1] = PlaneF(0.0f, 0.0f, 1.0f, 1.0f);
   mTexInfos.last().scale[0]   = 1.0f;
   mTexInfos.last().scale[1]   = 1.0f;
   mTexInfos.last().rot        = 0.0f;
   mTexInfos.last().texDiv[0]  = 1.0f;
   mTexInfos.last().texDiv[1]  = 1.0f;
   mTexInfos.last().texture = StringTable->insert("");

   return true;
}

bool ConvexBrush::addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture)
{
   addPlane(pln);

   mTexInfos.last().texGens[0] = texGen[0];
   mTexInfos.last().texGens[1] = texGen[1];
   mTexInfos.last().scale[0]   = scale[0];
   mTexInfos.last().scale[1]   = scale[1];
   mTexInfos.last().texture    = StringTable->insert(texture);

   return true;
}

bool ConvexBrush::addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture, U32 matIdx)
{
   addFace(pln, texGen, scale, texture);

   mFaces.mPolyList.last().material = matIdx;

   return true;
}

bool ConvexBrush::addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], U32 matIdx)
{
   return addFace(pln, texGen, scale, "", matIdx);;
}

// Call this after you have added the planes to generate the rest of the data
bool ConvexBrush::processBrush()
{
   // It is possible that our status was changed externally by the parser
   if (mStatus != Unprocessed)
      return false;

   // First check to see if we have enough planes
   if (mFaces.mPlaneList.size() < 4)
   {
      mStatus = ShortPlanes;
      char debug[512];
      dSprintf(debug, 512, "This brush only has %d planes which is insufficent to create a convex volume. Possibly a badly formatted .map?", mFaces.mPlaneList.size());
      mDebugInfo = StringTable->insert(debug);
      return false;
   }

   // Generate the geometry
   selfClip();

   // Verify that all of the faces have at least 3 vertices
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      if (mFaces.mPolyList[i].vertexCount < 3)
      {
         mStatus = BadFaces;
         mDebugInfo = StringTable->insert("There are bad faces on this brush. This is most likely caused by a flat or non-volumetric brush.");
         return false;
      }
   }

   // Generate proper windings for the faces
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      // Create a temporary index list
      Vector<U32> indexes;
      for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount; j++)
         indexes.push_back(mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j]);

      // Create a buffer to return the winding to
      Vector<U32> winding;

      if (createWinding(indexes, mFaces.mPlaneList[mFaces.mPolyList[i].plane], winding))
      {
         // Copy the correctly order indices back
         for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount; j++)
            mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j] = winding[j];
      }
      else
      {
         mStatus = BadWinding;
         mDebugInfo = StringTable->insert("Unable to generate windings for some of the faces. This is often caused by having brushes that are too thin.");
         return false;
      }

      indexes.clear();
      winding.clear();
   }

   //validateWindings();

   generateEdgelist();

   if (!validateEdges())
      return false;

   calcBounds();

   // If we got this far without any errors consider the brush to be good
   if (mStatus == Unprocessed)
      mStatus = Good;
   else
      return false;

   return true;
}

void ConvexBrush::calcCentroid()
{
   // First find the centroid
   for (U32 i = 0; i < mFaces.mVertexList.size(); i++)
      mCentroid += mFaces.mVertexList[i];

   mCentroid /= mFaces.mVertexList.size();
}

void ConvexBrush::setupTransform()
{
   // First calculate the centroid
   calcCentroid();

   mTransform.setColumn(3, mCentroid);

   mCentroid.set(0.0f, 0.0f, 0.0f);

   // Now untransform the verts and planes
   MatrixF invTrans = mTransform;
   invTrans.inverse();

   // Untransform our verts
   for (U32 i = 0; i < mFaces.mVertexList.size(); i++)
      invTrans.mulP(mFaces.mVertexList[i]);

   // Untransform our bounds
   invTrans.mulP(mBounds.min);
   invTrans.mulP(mBounds.max);

   // Untransform our planes
   PlaneTransformer trans;
   trans.set(invTrans, Point3F(1.0f, 1.0f, 1.0f));

   for (U32 i = 0; i < mFaces.mPlaneList.size(); i++)
   {
      PlaneF temp;
      trans.transform(mFaces.mPlaneList[i], temp);
      mFaces.mPlaneList[i] = temp;
   }
}

bool ConvexBrush::validateEdges()
{
   // Check to make sure that ever edge has two faces
   for (U32 i = 0; i < mFaces.mEdgeList.size(); i++)
   {
      OptimizedPolyList::Edge ed = mFaces.mEdgeList[i];
      if (mFaces.mEdgeList[i].faces[1] == -1)
      {
         mStatus = Concave;
         mDebugInfo = StringTable->insert("There are missing faces on this brush. This is caused by having concave edges in the brush.");
         return false;
      }
   }

   // Now test for concavity
   for (U32 i = 0; i < mFaces.mEdgeList.size(); i++)
   {
      U32 face0 = mFaces.mEdgeList[i].faces[0];
      U32 face1 = mFaces.mEdgeList[i].faces[1];

      // Get the indices that make up the edge
      U32 i0 = mFaces.mEdgeList[i].vertexes[0];
      U32 i1 = mFaces.mEdgeList[i].vertexes[1];

      // Now get a point from face0 that isn't on the edge
      U32 nsx;
      for (U32 j = 0; j < mFaces.mPolyList[face0].vertexCount; j++)
      {
         nsx = mFaces.mIndexList[mFaces.mPolyList[face0].vertexStart + j];

         if (nsx != i0 && nsx != i1)
            break;
      }

      // Get the vertices
      Point3F ns = mFaces.mVertexList[nsx];

      // Grab the plane of face1
      PlaneF pln = mFaces.mPlaneList[mFaces.mPolyList[face1].plane];

      // Test to see if the point is in front or behind
      if (pln.whichSide(ns) == PlaneF::Back)
         mFaces.mEdgeList[i].type = OptimizedPolyList::Convex;
      else
      {
         mFaces.mEdgeList[i].type = OptimizedPolyList::Concave;
         mStatus = Concave;
         mDebugInfo = StringTable->insert("Found some concave edges on this brush.");
      }
   }

   if (mStatus == Concave)
      return false;
   else
      return true;
}

void ConvexBrush::validateWindings()
{
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      if (mFaces.mPolyList[i].vertexCount < 3)
         continue;

      U32 i0 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart];
      U32 i1 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + 1];
      U32 i2 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + 2];

      Point3F p0 = mFaces.mVertexList[i0];
      Point3F p1 = mFaces.mVertexList[i1];
      Point3F p2 = mFaces.mVertexList[i2];

      PlaneF wnorm(p0, p1, p2);

      if (wnorm.whichSide(mCentroid) == PlaneF::Front)
      {
         Con::errorf("Face %d's winding is inverted", i);

         // Let's reverse the winding
         Vector<U32> fixed;

         for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount; j++)
            fixed.push_front(mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j]);

         for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount; j++)
            mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j] = fixed[j];
      }
   }
}

void ConvexBrush::addIndex(Vector<U32>& indices, U32 index)
{
   for (U32 i = 0; i < indices.size(); i++)
   {
      if (indices[i] == index)
         return;
   }

   indices.push_back(index);
}

// This clips the planes against each other
bool ConvexBrush::selfClip()
{
   // Used to hold our indices temporarily
   Vector<U32>* faceIndices = new Vector<U32>[mFaces.mPolyList.size()];

   //Con::errorf("New brush");
   //for (U32 i = 0; i < mFaces.mPlaneList.size(); i++)
   //   Con::printf("plane %d(%g, %g, %g, %g)", i, mFaces.mPlaneList[i].x, mFaces.mPlaneList[i].y, mFaces.mPlaneList[i].z, mFaces.mPlaneList[i].d);

   // Clip them against each other to generate the vertexes
   for (U32 i = 0; i < mFaces.mPlaneList.size(); i++)
   {
      for (U32 j = i+1; j < mFaces.mPlaneList.size(); j++)
      {
         for (U32 k = j+1; k < mFaces.mPlaneList.size(); k++)
         {
            Point3D interd;
            Point3F interf;

            bool doesSersect = intersectPlanes(mFaces.mPlaneList[i], mFaces.mPlaneList[j], mFaces.mPlaneList[k], &interd);

            interf = Point3F(interd.x, interd.y, interd.z);

            if (doesSersect)
            {
               // Check to make sure that the point is behind or on all of the
               // planes
               bool inOrOn = true;

               for (U32 l = 0; l < mFaces.mPlaneList.size(); l++)
               {
                  if (mFaces.mPlaneList[l].whichSide(interf) == PlaneF::Front)
                  {
                     inOrOn = false;
                     break;
                  }
               }

               if (inOrOn == true)
               {
                  U32 vdx = mFaces.addPoint(interf);

                  addIndex(faceIndices[i], vdx);
                  addIndex(faceIndices[j], vdx);
                  addIndex(faceIndices[k], vdx);
               }
            }
         }
      }
   }

   // Add the indices for the faces
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      // Set the starting index
      mFaces.mPolyList[i].vertexStart = mFaces.mIndexList.size();
      // Set the number of indexes
      mFaces.mPolyList[i].vertexCount = faceIndices[i].size();

      // Copy the indexes into the index list
      for (U32 j = 0; j < faceIndices[i].size(); j++)
      {
         mFaces.mIndexList.push_back(faceIndices[i][j]);
      }
   }

   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
      faceIndices[i].clear();

   delete [] faceIndices;

   return true;
}

bool ConvexBrush::intersectPlanes(const PlaneF& plane1, const PlaneF& plane2, const PlaneF& plane3, Point3D* pOutput)
{
   F64 bc  = (plane2.y * plane3.z) - (plane3.y * plane2.z);
   F64 ac  = (plane2.x * plane3.z) - (plane3.x * plane2.z);
   F64 ab  = (plane2.x * plane3.y) - (plane3.x * plane2.y);
   F64 det = (plane1.x * bc) - (plane1.y * ac) + (plane1.z * ab);

   if (mFabs(det) < 1e-7)
   {
      // Parallel planes
      return false;
   }

   F64 dc     = (plane2.d * plane3.z) - (plane3.d * plane2.z);
   F64 db     = (plane2.d * plane3.y) - (plane3.d * plane2.y);
   F64 ad     = (plane3.d * plane2.x) - (plane2.d * plane3.x);
   F64 detInv = 1.0 / det;

   pOutput->x = ((plane1.y * dc) - (plane1.d     * bc) - (plane1.z * db)) * detInv;
   pOutput->y = ((plane1.d     * ac) - (plane1.x * dc) - (plane1.z * ad)) * detInv;
   pOutput->z = ((plane1.y * ad) + (plane1.x * db) - (plane1.d     * ab)) * detInv;

   return true;
}

bool ConvexBrush::createWinding(const Vector<U32>& rPoints, const Point3F& normal, Vector<U32>& rWinding)
{
   if (rPoints.size() < 3)
      return false;

   Vector<U32> modPoints;
   Point3F centroid(0, 0, 0);
   Point3F projcent(0, 0, 0);

   modPoints = rPoints;

   // Create a centroid first...
   for (U32 i = 0; i < modPoints.size(); i++)
      centroid += mFaces.mVertexList[modPoints[i]];
   centroid /= F32(modPoints.size());

   // Create a point projected along the normal
   projcent = centroid + normal;

   // Ok, we have a centroid.  We (arbitrarily) start with the last point
   rWinding.increment();
   rWinding.last() = modPoints[modPoints.size() - 1];
   modPoints.erase(modPoints.size() - 1);

   while (modPoints.size() > 0)
   {
      // Get our last winding point
      Point3F& currPoint = mFaces.mVertexList[rWinding.last()];

      // Calculate the vector between the centroid and the current point
      VectorF currVector = currPoint - centroid;
      currVector.normalize();

      // Create a plane with the winding point, the centroid, and the projected centroid
      PlaneF testPlane(currPoint, centroid, projcent);

      F32 maxDot = -1e10;
      S32 maxIdx = -1;

      for (U32 i = 0; i < modPoints.size(); i++)
      {
         Point3F& testPoint = mFaces.mVertexList[modPoints[i]];

         if (testPlane.whichSide(testPoint) == PlaneF::Front)
         {
            // Get the vector between this point and the centroid
            VectorF testVector = testPoint - centroid;
            testVector.normalize();

            F32 dot = mDot(testVector, currVector);

            if (dot > maxDot)
            {
               maxDot = dot;
               maxIdx = i;
            }
         }
      }

      if (maxIdx == -1)
      {
         // Sometimes the last vertex doesn't make it onto the list
         if (modPoints.size() == 1)
         {
            rWinding.increment();
            rWinding.last() = modPoints.last();
            return true;
         }
         else
         {
            Con::errorf("ConvexBrush::createWinding(): was unable to find the next winding point");

            // For debugging
            U32 pt = 0;
            for (U32 i = 0; i < rWinding.size(); i++)
            {
               Point3F& point = mFaces.mVertexList[rWinding[i]];

               Con::printf("   point %d (%g, %g, %g)", pt, point.x, point.y, point.z);
            }

            for (U32 i = 0; i < modPoints.size(); i++)
            {
               Point3F& point = mFaces.mVertexList[modPoints[i]];

               Con::printf("   point %d (%g, %g, %g)", pt, point.x, point.y, point.z);
            }

            return false;
         }
      }

      rWinding.increment();
      rWinding.last() = modPoints[maxIdx];
      modPoints.erase(maxIdx);
   }

   modPoints.clear();

   return true;
}

bool ConvexBrush::render(bool genColors)
{
   // Renders it in colors only
   if (genColors)
      gRandGen.setSeed(1978);

   glEnable(GL_CULL_FACE);

   // Pick a random color for our brush
   if (!genColors)
   {
      ColorF color(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f));
      glColor3f(color.red, color.green, color.blue);
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
   }

   for (U32 j = 0; j < mFaces.mPolyList.size(); j++)
   {
      S32 tx = mFaces.mPolyList[j].material;

      if (tx == -2)
         continue;

      glBegin(GL_TRIANGLES);
         for (U32 k = 1; k < mFaces.mPolyList[j].vertexCount - 1; k++)
         {
            U32 i0 = mFaces.mIndexList[mFaces.mPolyList[j].vertexStart];
            U32 i1 = mFaces.mIndexList[mFaces.mPolyList[j].vertexStart + k];
            U32 i2 = mFaces.mIndexList[mFaces.mPolyList[j].vertexStart + k + 1];

            Point3F p0 = mFaces.mVertexList[i0];
            Point3F p1 = mFaces.mVertexList[i1];
            Point3F p2 = mFaces.mVertexList[i2];

            if (genColors)
            {
               ColorF color(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f));
               glColor3f(color.red, color.green, color.blue);
               glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
            }

            PlaneF normal = mFaces.mPlaneList[mFaces.mPolyList[k].plane];
            glNormal3f(normal.x, normal.y, normal.z);

            F32 s, t;

            PlaneF texGenX = mTexInfos[k].texGens[0];
            PlaneF texGenY = mTexInfos[k].texGens[1];

            s = texGenX.distToPlane(p0 * mBrushScale);
            t = texGenY.distToPlane(p0 * mBrushScale);

            glTexCoord2f(s, t);
            glVertex3f(p0.x, p0.y, p0.z);

            s = texGenX.distToPlane(p1 * mBrushScale);
            t = texGenY.distToPlane(p1 * mBrushScale);

            glTexCoord2f(s, t);
            glVertex3f(p1.x, p1.y, p1.z);

            s = texGenX.distToPlane(p2 * mBrushScale);
            t = texGenY.distToPlane(p2 * mBrushScale);

            glTexCoord2f(s, t);
            glVertex3f(p2.x, p2.y, p2.z);
         }
      glEnd();
   }

   return true;
}

bool ConvexBrush::renderFace(U32 face, bool renderLighting)
{
   if (face > mFaces.mPolyList.size())
   {
      Con::errorf("ConvexBrush::debugRenderFace(): face is outside range");
      return false;
   }

   if (mFaces.mPolyList[face].vertexCount < 3)
      return false;

   const OptimizedPolyList::TriangleLighting *lighting = NULL;
   Point3F tp;
   PlaneF lmGenX, lmGenY;
   if(renderLighting)
	   glActiveTextureARB(GL_TEXTURE1_ARB);

      for (U32 k = 1; k < mFaces.mPolyList[face].vertexCount - 1; k++)
      {
         U32 i0 = mFaces.mIndexList[mFaces.mPolyList[face].vertexStart];
         U32 i1 = mFaces.mIndexList[mFaces.mPolyList[face].vertexStart + k];
         U32 i2 = mFaces.mIndexList[mFaces.mPolyList[face].vertexStart + k + 1];

         Point3F p0 = mFaces.mVertexList[i0];
         Point3F p1 = mFaces.mVertexList[i1];
         Point3F p2 = mFaces.mVertexList[i2];

	   if((renderLighting) && ((lighting = mFaces.getTriangleLighting(mFaces.mPolyList[face].triangleLightingStartIndex + (k - 1))) != NULL))
	   {
		   lmGenX = lighting->lightMapEquationX;
		   lmGenY = lighting->lightMapEquationY;
		   glBindTexture(GL_TEXTURE_2D, lighting->lightMapId);
	   }

	   glBegin(GL_TRIANGLES);

       PlaneF normal = mFaces.mPlaneList[mFaces.mPolyList[face].plane];
       glNormal3f(normal.x, normal.y, normal.z);

       F32 s, t;

       PlaneF texGenX = mTexInfos[face].texGens[0];
       PlaneF texGenY = mTexInfos[face].texGens[1];

	   tp = p0;
	   s = texGenX.distToPlane(tp);
	   t = texGenY.distToPlane(tp);
	   glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t);

	   if(lighting)
	   {
		   mLightingTransform.mulP(tp);
		   s = lmGenX.distToPlane(tp);
		   t = lmGenY.distToPlane(tp);
		   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, t);
	   }

       glVertex3f(p0.x, p0.y, p0.z);

	   tp = p1;
	   s = texGenX.distToPlane(tp);
	   t = texGenY.distToPlane(tp);
	   glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t);

	   if(lighting)
	   {
		   mLightingTransform.mulP(tp);
		   s = lmGenX.distToPlane(tp);
		   t = lmGenY.distToPlane(tp);
		   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, t);
	   }

       glVertex3f(p1.x, p1.y, p1.z);

	   tp = p2;
	   s = texGenX.distToPlane(tp);
	   t = texGenY.distToPlane(tp);
	   glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t);

	   if(lighting)
	   {
		   mLightingTransform.mulP(tp);
		   s = lmGenX.distToPlane(tp);
		   t = lmGenY.distToPlane(tp);
		   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, t);
	   }

       glVertex3f(p2.x, p2.y, p2.z);

	   glEnd();
   }

   if(renderLighting)
	   glActiveTextureARB(GL_TEXTURE0_ARB);

   return true;
}

bool ConvexBrush::renderEdges(ColorF color)
{
   // Save ModelView Matrix so we can restore Canonical state at exit.
	glPushMatrix();

   // Transform by the objects' transform e.g move it.
	dglMultMatrix(&mTransform);

   // Scale
   //glScalef(mScale.x, mScale.y, mScale.z);

   glBegin(GL_LINES);

   for (U32 i = 0; i < mFaces.mEdgeList.size(); i++)
   {
      OptimizedPolyList::Edge ed = mFaces.mEdgeList[i];
      //if (mFaces.mEdgeList[i].type == OptimizedPolyList::Convex)
      //   glColor3f(0.0f, 1.0f, 0.0f);
      //else if (mFaces.mEdgeList[i].type == OptimizedPolyList::Concave)
      //   glColor3f(1.0f, 0.0f, 0.0f);
      //else
      //   glColor3f(0.0f, 0.0f, 1.0f);

      glColor4f(color.red, color.green, color.blue, color.alpha);

      U32 i0 = mFaces.mEdgeList[i].vertexes[0];
      U32 i1 = mFaces.mEdgeList[i].vertexes[1];

      Point3F p0 = mFaces.mVertexList[i0];
      Point3F p1 = mFaces.mVertexList[i1];

      glVertex3f(p0.x, p0.y, p0.z);
      glVertex3f(p1.x, p1.y, p1.z);
   }

   glEnd();

   glPopMatrix();

   return true;
}

bool ConvexBrush::calcBounds()
{
   // Rebuild our bounds
   if (mFaces.mVertexList.size() > 0)
   {
      mBounds.min.set(1e10, 1e10, 1e10);
      mBounds.max.set(-1e10, -1e10, -1e10);

      // Build our bounds
      for (U32 i = 0; i < mFaces.mVertexList.size(); i++)
      {
         mBounds.min.setMin(mFaces.mVertexList[i]);
         mBounds.max.setMax(mFaces.mVertexList[i]);
      }
   }

   return true;
}

bool ConvexBrush::setScale(F32 scale)
{
   mBrushScale = scale;

   // Scale our verts
   for (U32 i = 0; i < mFaces.mVertexList.size(); i++)
      mFaces.mVertexList[i] /= mBrushScale;

   // Scale the planes
   for (U32 i = 0; i < mFaces.mPlaneList.size(); i++)
      mFaces.mPlaneList[i].d /= mBrushScale;

   calcBounds();

   return true;
}

void ConvexBrush::addEdge(U16 zero, U16 one, U32 face)
{
   // Sort the edges
   U16 newEdge0, newEdge1;

   newEdge0 = getMin(zero, one);
   newEdge1 = getMax(zero, one);

   // Check the current edges
   bool found = false;
   U32 index = 0;

   for (index = 0; index < mFaces.mEdgeList.size(); index++)
   {
      if (mFaces.mEdgeList[index].vertexes[0] == newEdge0 && mFaces.mEdgeList[index].vertexes[1] == newEdge1)
      {
         found = true;
         break;
      }
   }

   // If we didn't find it add a new edge
   if (!found)
   {
      index = mFaces.mEdgeList.size();
      mFaces.mEdgeList.increment();
      mFaces.mEdgeList.last().vertexes[0] = newEdge0;
      mFaces.mEdgeList.last().vertexes[1] = newEdge1;
      mFaces.mEdgeList.last().faces[0] = -1;
      mFaces.mEdgeList.last().faces[1] = -1;
      mFaces.mEdgeList.last().type = OptimizedPolyList::NonShared;
   }

   // Add the face
   if (mFaces.mEdgeList[index].faces[0] == -1)
      mFaces.mEdgeList[index].faces[0] = face;
   else if (mFaces.mEdgeList[index].faces[1] == -1)
      mFaces.mEdgeList[index].faces[1] = face;
};

bool ConvexBrush::generateEdgelist()
{
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      // Add all of the edges of the polygon
      for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount - 1; j++)
      {
         U32 i0 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j];
         U32 i1 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j + 1];

         addEdge(i0, i1, i);
      }

      // Add the connecting edge from the last vertex to the first
      U32 i0 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart];
      U32 i1 = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + mFaces.mPolyList[i].vertexCount - 1];

      addEdge(i0, i1, i);
   }

   return true;
}

U32 ConvexBrush::getPolySide(S32 side)
{
   if (side == PlaneF::Back)
      return Back;
   else if (side == PlaneF::Front)
      return Front;
   else if (side == PlaneF::On)
      return On;
   else
      return Unknown;
}

bool ConvexBrush::isInsideBox(PlaneF left, PlaneF right, PlaneF top, PlaneF bottom)
{
   if (mStatus == Deleted)
      return false;

   // If the brush is in front of any of the planes then it is outside
   U32 side = whichSide(left);
   if (side == Front || side == Unknown)
      return false;
   side = whichSide(right);
   if (side == Front || side == Unknown)
      return false;
   side = whichSide(top);
   if (side == Front || side == Unknown)
      return false;
   side = whichSide(bottom);
   if (side == Front || side == Unknown)
      return false;

   return true;
}

U32 ConvexBrush::whichSide(PlaneF pln)
{
   if (mFaces.mPolyList.size() == 0)
      return Unknown;

   // Find out which side the first face is on
   U32 side = On;

   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      U32 nsd = whichSide(pln, i);

      if (nsd == Split)
         return Split;
      else if (nsd != On)
         side = nsd;
   }

   if (side == On)
      Con::errorf("ConvexBrush::whichSide(): brush has no volume");

   return side;
}

U32 ConvexBrush::whichSide(PlaneF pln, U32 faceIndex)
{
   if (faceIndex > mFaces.mPolyList.size())
      return Unknown;

   OptimizedPolyList::Poly* face = &mFaces.mPolyList[faceIndex];

   Point3F currv, nextv;
   S32 csd, nsd;

   U32 side = On;

   // Find out which side the first vert is on
   U32 idx = mFaces.mIndexList[face->vertexStart];
   currv = mFaces.mVertexList[idx];

   csd = pln.whichSide(currv);

   if (csd != PlaneF::On)
      side = getPolySide(csd);

   for (U32 k = 1; k < face->vertexCount; k++)
   {
      idx = mFaces.mIndexList[face->vertexStart + k];
      nextv = mFaces.mVertexList[idx];

      nsd = pln.whichSide(nextv);

      if ((csd == PlaneF::Back && nsd == PlaneF::Front) ||
          (csd == PlaneF::Front && nsd == PlaneF::Back))
      {
         return Split;
      }
      else if (nsd != PlaneF::On)
         side = getPolySide(nsd);

      currv = nextv;
      csd = nsd;
   }

   // Loop back to the first vert
   idx = mFaces.mIndexList[face->vertexStart];
   nextv = mFaces.mVertexList[idx];

   nsd = pln.whichSide(nextv);

   if ((csd == PlaneF::Back && nsd == PlaneF::Front) ||
         (csd == PlaneF::Front && nsd == PlaneF::Back))
   {
      return Split;
   }
   else if (nsd != PlaneF::On)
      side = getPolySide(nsd);

   return side;
}

bool ConvexBrush::getPolyList(AbstractPolyList* list)
{
   if (mFaces.mVertexList.size() == 0 || mFaces.mPolyList.size() == 0)
   {
      Con::errorf("ConvexBrush::getPolyList(): no verts or faces");
      return false;
   }

   PlaneTransformer trans;
   trans.set(mTransform, Point3F(1.0f, 1.0f, 1.0f));

   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      if (mFaces.mPolyList[i].vertexCount < 3)
      {
         Con::errorf("ConvexBrush::getPolyList(): ran into a face with less than 3 verts");
         continue;
      }

      list->begin(0, i);

      for (U32 j = 0; j < mFaces.mPolyList[i].vertexCount; j++)
      {
         U32 idx = mFaces.mIndexList[mFaces.mPolyList[i].vertexStart + j];
         Point3F pt = mFaces.mVertexList[idx];

         // Transform the point by the brush transform
         mTransform.mulP(pt);

         U32 newidx = list->addPoint(pt);
         list->vertex(newidx);
      }

      PlaneF pln = mFaces.mPlaneList[mFaces.mPolyList[i].plane];
      //pln.invert();

      PlaneF temp;
      trans.transform(pln, temp);

      U32 pdx = list->addPlane(temp);
      list->plane(pdx);

      list->end();
   }

   return true;
}

bool ConvexBrush::splitBrush(PlaneF pln, ConvexBrush* fbrush, ConvexBrush* bbrush)
{
   F32 scale = mBrushScale;

   // Scale the brush up
   setScale(1.0f / scale);
   pln.d *= scale;

   // Loop through the brush faces and sort them
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      U32 side = whichSide(pln, i);

      if (side == Front)
         fbrush->addPlane(mFaces.mPlaneList[mFaces.mPolyList[i].plane]);
      else if (side == Back)
         bbrush->addPlane(mFaces.mPlaneList[mFaces.mPolyList[i].plane]);
      else if (side == Split)
      {
         fbrush->addPlane(mFaces.mPlaneList[mFaces.mPolyList[i].plane]);
         bbrush->addPlane(mFaces.mPlaneList[mFaces.mPolyList[i].plane]);
      }
      else
      {
         Con::errorf("BspTree::splitBrush(): this brush can not be split");
         return false;
      }
   }

   // Next push the split plane into both
   bbrush->addPlane(pln);
   fbrush->addPlane(PlaneF(-pln.x, -pln.y, -pln.z, -pln.d));

   // Now generate the brushes with the planes supplied
   fbrush->processBrush();
   bbrush->processBrush();

   // Now that we've generated the new polys copy over the properties
   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      // We can match the new polys up with the old ones using their planes
      PlaneF oldpln = mFaces.mPlaneList[mFaces.mPolyList[i].plane];

      for (U32 j = 0; j < fbrush->mFaces.mPolyList.size(); j++)
      {
         PlaneF newpln = fbrush->mFaces.mPlaneList[fbrush->mFaces.mPolyList[j].plane];

         if (oldpln == newpln && oldpln.d == newpln.d)
         {
            // Copy over the properties
            fbrush->mFaces.mPolyList[j].object = mFaces.mPolyList[i].object;
            fbrush->mFaces.mPolyList[j].material = mFaces.mPolyList[i].material;
            fbrush->mFaces.mPolyList[j].material = mFaces.mPolyList[i].surfaceKey;
         }
      }

      for (U32 j = 0; j < bbrush->mFaces.mPolyList.size(); j++)
      {
         PlaneF newpln = bbrush->mFaces.mPlaneList[bbrush->mFaces.mPolyList[j].plane];

         if (oldpln == newpln && oldpln.d == newpln.d)
         {
            // Copy over the properties
            bbrush->mFaces.mPolyList[j].object = mFaces.mPolyList[i].object;
            bbrush->mFaces.mPolyList[j].material = mFaces.mPolyList[i].material;
            bbrush->mFaces.mPolyList[j].material = mFaces.mPolyList[i].surfaceKey;
         }
      }
   }

   // Now find the new poly and flag it as a non-solid face
   for (U32 i = 0; i < fbrush->mFaces.mPolyList.size(); i++)
   {
      PlaneF newpln = fbrush->mFaces.mPlaneList[fbrush->mFaces.mPolyList[i].plane];

      if (pln.x == -newpln.x && pln.y == -newpln.y && pln.z == -newpln.z && pln.d == -newpln.d)
         fbrush->mFaces.mPolyList[i].material = -2;
   }

   for (U32 i = 0; i < bbrush->mFaces.mPolyList.size(); i++)
   {
      PlaneF newpln = bbrush->mFaces.mPlaneList[bbrush->mFaces.mPolyList[i].plane];

      if (pln == newpln && pln.d == newpln.d)
         bbrush->mFaces.mPolyList[i].material = -2;
   }

   // scale everything back down
   fbrush->setScale(scale);
   bbrush->setScale(scale);

   setScale(scale);

   return true;
}

bool ConvexBrush::castRay(const Point3F& s, const Point3F& e, RayInfo* info)
{
   if (mStatus == Deleted)
      return false;

   // First transform the start and end points into the brushes space
   Point3F start, end;
   MatrixF invTrans = mTransform;
   invTrans.inverse();
   invTrans.mulP(s, &start);
   invTrans.mulP(e, &end);
   start.convolveInverse(mScale);
   end.convolveInverse(mScale);

   // Ganked from TSMesh::castRay()
   // Keep track of startTime and endTime.  They start out at just under 0 and just over 1, respectively.
   // As we check against each plane, prune start and end times back to represent current intersection of
   // line with all the planes (or rather with all the half-spaces defined by the planes).
   // But, instead of explicitly keeping track of startTime and endTime, keep track as numerator and denominator
   // so that we can avoid as many divisions as possible.

   //   F32 startTime = -0.01f;
   F32 startNum = -0.01f;
   F32 startDen =  1.00f;
   //   F32 endTime   = 1.01f;
   F32 endNum = 1.01f;
   F32 endDen = 1.00f;

   S32 curPlane = 0;
   U32 curMaterial = 0;
   bool found = false;

   // the following block of code is an optimization...
   // it isn't necessary if the longer version of the main loop is used
   bool tmpFound;
   S32 tmpPlane;
   F32 sgn = -1.0f;
   F32 * pnum = &startNum;
   F32 * pden = &startDen;
   S32 * pplane = &curPlane;
   bool * pfound = &found;

   for (U32 i = 0; i < mFaces.mPlaneList.size(); i++)
   {
      Point3F planeNormal = mFaces.mPlaneList[i];
      F32 planeConstant = mFaces.mPlaneList[i].d;

      // if start & end outside, no collision
      // if start & end inside, continue
      // if start outside, end inside, or visa versa, find intersection of line with plane
      //    then update intersection of line with hull (using startTime and endTime)
      F32 dot1 = mDot(planeNormal, start) + planeConstant;
      F32 dot2 = mDot(planeNormal, end) + planeConstant;
      if (dot1*dot2>0.0f)
      {
         // same side of the plane...which side -- dot==0 considered inside
         if (dot1>0.0f)
            // start and end outside of this plane, no collision
            return false;
         // start and end inside plane, continue
         continue;
      }

      AssertFatal(dot1/(dot1-dot2)>=0.0f && dot1/(dot1-dot2)<=1.0f,"ConvexBrush::castRay (1)");

      // find intersection (time) with this plane...
      // F32 time = dot1 / (dot1-dot2);
      F32 num = mFabs(dot1);
      F32 den = mFabs(dot1-dot2);

      // the following block of code is an optimized version...
      // this can be commented out and the following block of code used instead
      // if debugging a problem in this code, that should probably be done
      // if you want to see how this works, look at the following block of code,
      // not this one...
      // Note that this does not get optimized appropriately...it is included this way
      // as an idea for future optimization.
      if (sgn*dot1>=0)
      {
         sgn *= -1.0f;
         pnum = (F32*) ((dsize_t)pnum ^ (dsize_t)&endNum ^ (dsize_t)&startNum);
         pden = (F32*) ((dsize_t)pden ^ (dsize_t)&endDen ^ (dsize_t)&startDen);
         pplane = (S32*) ((dsize_t)pplane ^ (dsize_t)&tmpPlane ^ (dsize_t)&curPlane);
         pfound = (bool*) ((dsize_t)pfound ^ (dsize_t)&tmpFound ^ (dsize_t)&found);
      }
      bool noCollision = num*endDen*sgn<endNum*den*sgn && num*startDen*sgn<startNum*den*sgn;
      if (num * *pden * sgn < *pnum * den * sgn && !noCollision)
      {
         *pnum = num;
         *pden = den;
         *pplane = i;
         *pfound = true;
      }
      else if (noCollision)
         return false;
   }

   // setup rayInfo
   if (found && info)
   {
      info->t        = (F32)startNum/(F32)startDen; // finally divide...
      info->normal   = mFaces.mPlaneList[curPlane];
      // Find the face that uses this plane and its material
      for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
      {
         if (mFaces.mPolyList[i].plane == curPlane)
         {
            info->material = mFaces.mPolyList[i].material;
            break;
         }
      }

      return true;
   }
   else if (found)
      return true;

   // only way to get here is if start is inside hull...
   // we could return null and just plug in garbage for the material and normal...
   return false;
}

void ConvexBrush::resetBrush()
{
   mBounds.min = Point3F(0, 0, 0);
   mBounds.max = Point3F(0, 0, 0);

   mCentroid = Point3F(0.0f, 0.0f, 0.0f);
   mStatus = Unprocessed;

   // Set our transform to no translation or rotation
   mTransform.identity();

   mScale.set(1.0f, 1.0f, 1.0f);
   mRotation.set(EulerF(0.0f, 0.0f, 0.0f));

   // Data
   mFaces.clear();
   mTexInfos.clear();
}

OptimizedPolyList ConvexBrush::getIntersectingPolys(OptimizedPolyList* list)
{
   OptimizedPolyList ret;

   for (U32 i = 0; i < list->mPolyList.size(); i++)
   {
      if (isPolyInside(list, i))
         list->copyPolyToList(&ret, i);
   }

   return ret;
}

OptimizedPolyList ConvexBrush::getNonIntersectingPolys(OptimizedPolyList* list)
{
   OptimizedPolyList ret;

   for (U32 i = 0; i < list->mPolyList.size(); i++)
   {
      if (!isPolyInside(list, i))
         list->copyPolyToList(&ret, i);
   }

   return ret;
}

bool ConvexBrush::isPolyInside(OptimizedPolyList* list, U32 pdx)
{
   // This relies on the brush to be convex
   bool inside = true;

   OptimizedPolyList::Poly& poly = list->mPolyList[pdx];

   for (U32 i = 0; i < mFaces.mPolyList.size(); i++)
   {
      OptimizedPolyList::Poly& face = mFaces.mPolyList[i];
      PlaneF& pln = mFaces.mPlaneList[face.plane];

      bool behind = true;

      for (U32 j = 0; j < poly.vertexCount; j++)
      {
         U32 idx = list->mIndexList[j + poly.vertexStart];
         Point3F& pt = list->mVertexList[idx];

         U32 tside = pln.whichSide(pt);

         if (tside == PlaneF::Front)
         {
            behind = false;
            break;
         }
      }

      if (!behind)
      {
         inside = false;
         break;
      }
   }

   return inside;
}