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

#include "map2dif/morianUtil.h"
#include "map2dif/editGeometry.h"
#include "platform/platformAssert.h"
#include "math/mMath.h"
#include "map2dif/csgBrush.h"

// Global brush arena...
BrushArena gBrushArena(1024);



S32 euclidGCD(S32 one, S32 two)
{
   AssertFatal(one > 0 && two > 0, "Error");

   S32 m = one >= two ? one : two;
   S32 n = one >= two ? two : one;

   while (true) {
      S32 test = m % n;
      if (test == 0)
         return n;

      m = n;
      n = test;
   }
}

S32 planeGCD(S32 x, S32 y, S32 z, S32 d)
{
   S32* pNonZero = NULL;
   if (x != 0)      pNonZero = &x;
   else if (y != 0) pNonZero = &y;
   else if (z != 0) pNonZero = &z;
   else if (d != 0) pNonZero = &d;
   else {
      AssertFatal(false, "Cannot pass all zero to GCD!");
      return 1;
   }

   if (x == 0) return planeGCD(*pNonZero, y, z, d);
   if (y == 0) return planeGCD(x, *pNonZero, z, d);
   if (z == 0) return planeGCD(x, y, *pNonZero, d);
   if (d == 0) return planeGCD(x, y, z, *pNonZero);

   if (x < 0) x = -x;
   if (y < 0) y = -y;
   if (z < 0) z = -z;
   if (d < 0) d = -d;

   // Do a Euclid on these...
   return euclidGCD(euclidGCD(euclidGCD(x, y), z), d);
}

void dumpWinding(const Winding& winding, const char* prefixString)
{
   for (U32 i = 0; i < winding.numIndices; i++) {
      const Point3D& rPoint = gWorkingGeometry->getPoint(winding.indices[i]);
      dPrintf("%s(%d) %lg, %lg, %lg\n", prefixString, winding.indices[i], rPoint.x, rPoint.y, rPoint.z);
   }
}

//------------------------------------------------------------------------------
F64 getWindingSurfaceArea(const Winding& rWinding, U32 planeEQIndex)
{
   // DMMTODO poly area...
   Point3D areaNormal(0, 0, 0);
   for (U32 i = 0; i < rWinding.numIndices; i++) {
      U32 j = (i + 1) % rWinding.numIndices;

      Point3D temp;
      mCross(gWorkingGeometry->getPoint(rWinding.indices[i]),
             gWorkingGeometry->getPoint(rWinding.indices[j]), &temp);

      areaNormal += temp;
   }

   F64 area = mDot(gWorkingGeometry->getPlaneEQ(planeEQIndex).normal, areaNormal);
   if (area < 0.0)
      area *= -0.5;
   else
      area *= 0.5;

   return area;
}

//------------------------------------------------------------------------------
// Simple in-place clipper.  Should be all that's required...
//
bool clipWindingToPlaneFront(Winding* winding, const PlaneEQ& rClipPlane)
{
   U32 start;
   for (start = 0; start < winding->numIndices; start++) {
      const Point3D& rPoint = gWorkingGeometry->getPoint(winding->indices[start]);
      if (rClipPlane.whichSide(rPoint) == PlaneFront)
         break;
   }

   if (start == winding->numIndices) {
      winding->numIndices = 0;
      return true;
   }

   U32 finalIndices[MaxWindingPoints];
   U32 numFinalIndices = 0;

   U32 baseStart = start;
   U32 end       = (start + 1) % winding->numIndices;

   bool modified = false;
   while (end != baseStart) {
      const Point3D& rStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
      const Point3D& rEndPoint   = gWorkingGeometry->getPoint(winding->indices[end]);

      PlaneSide fSide = rClipPlane.whichSide(rStartPoint);
      PlaneSide eSide = rClipPlane.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
         finalIndices[numFinalIndices++] = winding->indices[start];
         start = end;
         end   = (end + 1) % winding->numIndices;
         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...
            //
            finalIndices[numFinalIndices++] = winding->indices[start];

            Point3D vector = rEndPoint - rStartPoint;
            F64 t = -(rClipPlane.distanceToPlane(rStartPoint) / mDot(rClipPlane.normal, vector));
            
            Point3D intersection = rStartPoint + (vector * t);
            AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn,
                        "CSGPlane::clipWindingToPlaneFront: error in computing intersection");
            finalIndices[numFinalIndices++] = gWorkingGeometry->insertPoint(intersection);

            U32 endSeek = (end + 1) % winding->numIndices;
            while (rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack)
               endSeek = (endSeek + 1) % winding->numIndices;

            PlaneSide esSide = rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek]));
            if (esSide == PlaneFront) {
               end   = endSeek;
               start = (end + (winding->numIndices - 1)) % winding->numIndices;

               const Point3D& rNewStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
               const Point3D& rNewEndPoint   = gWorkingGeometry->getPoint(winding->indices[end]);

               vector = rNewEndPoint - rNewStartPoint;
               t = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector));
            
               intersection = rNewStartPoint + (vector * t);
               AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn,
                           "CSGPlane::clipWindingToPlaneFront: error in computing intersection");
            
               winding->indices[start] = gWorkingGeometry->insertPoint(intersection);
               AssertFatal(winding->indices[start] != winding->indices[end], "Error");
            } else {
               start = endSeek;
               end   = (endSeek + 1) % winding->numIndices;
            }
            modified = true;
         }
         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...
            //
            finalIndices[numFinalIndices++] = winding->indices[start];

            U32 endSeek = (end + 1) % winding->numIndices;
            while (rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack)
               endSeek = (endSeek + 1) % winding->numIndices;

            PlaneSide esSide = rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek]));
            if (esSide == PlaneFront) {
               end   = endSeek;
               start = (end + (winding->numIndices - 1)) % winding->numIndices;

               const Point3D& rNewStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
               const Point3D& rNewEndPoint   = gWorkingGeometry->getPoint(winding->indices[end]);

               Point3D vector = rNewEndPoint - rNewStartPoint;
               F64  t      = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector));
            
               Point3D intersection = rNewStartPoint + (vector * t);
               AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn,
                           "CSGPlane::clipWindingToPlaneFront: error in computing intersection");
            
               winding->indices[start] = gWorkingGeometry->insertPoint(intersection);
               AssertFatal(winding->indices[start] != winding->indices[end], "Error");
            } else {
               start = endSeek;
               end   = (endSeek + 1) % winding->numIndices;
            }
            modified = true;
         }
         break;

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

        default:
         AssertFatal(false, "CSGPlane::clipWindingToPlaneFront: bad outcode");
         break;
      }

   }
   
   // Emit the last point.
   AssertFatal(rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[start])) != PlaneBack,
               "CSGPlane::clipWindingToPlaneFront: bad final point in clipper");
   finalIndices[numFinalIndices++] = winding->indices[start];
   AssertFatal(numFinalIndices >= 3, "Error, line out of clip!");

   // Copy the new rWinding, and we're set!
   //
   dMemcpy(winding->indices, finalIndices, numFinalIndices * sizeof(U32));
   winding->numIndices = numFinalIndices;
   AssertISV(winding->numIndices <= MaxWindingPoints,
             avar("Increase maxWindingPoints.  Talk to DMoore (%d, %d)",
                  MaxWindingPoints, numFinalIndices));

   // DEBUG POINT: No colinear points in the winding
   // DEBUG POINT: all points unique in the winding

   return modified;
}


bool clipWindingToPlaneFront(Winding* winding, U32 planeEQIndex)
{
   // DEBUG POINT: winding contains no colinear points...

   const PlaneEQ& rClipPlane = gWorkingGeometry->getPlaneEQ(planeEQIndex);
   return clipWindingToPlaneFront(winding, rClipPlane);
}


U32 windingWhichSide(const Winding& rWinding, U32 windingPlaneIndex, U32 planeIndex)
{
   AssertFatal(rWinding.numIndices > 2, "Error, winding with invalid number of indices...");

   if (gWorkingGeometry->isCoplanar(windingPlaneIndex, planeIndex))
      return PlaneSpecialOn;

   const PlaneEQ& rPlane = gWorkingGeometry->getPlaneEQ(planeIndex);

   U32 retSide = 0;
   for (U32 i = 0; i < rWinding.numIndices; i++) {
      switch (rPlane.whichSide(gWorkingGeometry->getPoint(rWinding.indices[i]))) {
        case PlaneOn:
         retSide |= PlaneSpecialOn;
         break;
        case PlaneFront:
         retSide |= PlaneSpecialFront;
         break;
        case PlaneBack:
         retSide |= PlaneSpecialBack;
         break;
      }
   }

   return retSide;
}

struct EdgeConnection {
   U32 start;
   U32 end;
} boxEdges[] = {
   { 0, 1 },
   { 1, 2 },
   { 2, 3 },
   { 3, 0 },
   { 4, 5 },
   { 5, 6 },
   { 6, 7 },
   { 7, 4 },
   { 3, 7 },
   { 0, 4 },
   { 1, 5 },
   { 2, 6 }
};

void createBoundedWinding(const Point3D& minBound,
                          const Point3D& maxBound,
                          U32         planeEQIndex,
                          Winding*       finalWinding)
{
   Point3D boxPoints[8];
   boxPoints[0].set(minBound.x, minBound.y, minBound.z);
   boxPoints[1].set(maxBound.x, minBound.y, minBound.z);
   boxPoints[2].set(maxBound.x, minBound.y, maxBound.z);
   boxPoints[3].set(minBound.x, minBound.y, maxBound.z);
   boxPoints[4].set(minBound.x, maxBound.y, minBound.z);
   boxPoints[5].set(maxBound.x, maxBound.y, minBound.z);
   boxPoints[6].set(maxBound.x, maxBound.y, maxBound.z);
   boxPoints[7].set(minBound.x, maxBound.y, maxBound.z);

   const PlaneEQ& rPlaneEQ = gWorkingGeometry->getPlaneEQ(planeEQIndex);

   PlaneSide sides[8];
   for (U32 i = 0; i < 8; i++)
      sides[i] = rPlaneEQ.whichSide(boxPoints[i]);

   UniqueVector unorderedWinding;
   for (U32 i = 0; i < 8; i++)
      if (sides[i] == PlaneOn)
         unorderedWinding.pushBackUnique(gWorkingGeometry->insertPoint(boxPoints[i]));

   for (U32 i = 0; i < sizeof(boxEdges) / sizeof(EdgeConnection); i++) {
      if ((sides[boxEdges[i].start] == PlaneBack && sides[boxEdges[i].end]   == PlaneFront) ||
          (sides[boxEdges[i].end]   == PlaneBack && sides[boxEdges[i].start] == PlaneFront)) {
         // Emit the intersection...
         //
         Point3D vector = boxPoints[boxEdges[i].end] - boxPoints[boxEdges[i].start];
         F64 t = -(rPlaneEQ.distanceToPlane(boxPoints[boxEdges[i].start]) / mDot(rPlaneEQ.normal, vector));
         
         Point3D intersection = boxPoints[boxEdges[i].start] + (vector * t);
         unorderedWinding.pushBackUnique(gWorkingGeometry->insertPoint(intersection));
      }
   }

   if (unorderedWinding.size() <= 2) {
      AssertISV(false, "createBoundedWinding: Bad face on brush.  < 3 points.  DMMNOTE: Handle better?");
      finalWinding->numIndices = 0;
      return;
   }

   Vector<U32> modPoints = unorderedWinding;

   // Create a centroid first...
   Point3D centroid(0, 0, 0);
   for (U32 i = 0; i < modPoints.size(); i++)
      centroid += gWorkingGeometry->getPoint(modPoints[i]);
   centroid /= F64(modPoints.size());

   // Ok, we have a centroid.  We (arbitrarily) take the last point, and start from
   //  there...
   finalWinding->indices[0] = modPoints[modPoints.size() - 1];
   finalWinding->numIndices = 1;
   modPoints.erase(modPoints.size() - 1);

   while (modPoints.size() != 0) {
      // Find the largest dot product of a point with a positive facing
      //  cross product.
      //
      F64 maxDot  = -1e10;
      S32 maxIndex = -1;

      const Point3D& currPoint = gWorkingGeometry->getPoint(finalWinding->indices[finalWinding->numIndices - 1]);
      Point3D currVector       = currPoint - centroid;
      currVector.normalize();

      for (U32 i = 0; i < modPoints.size(); i++) {
         const Point3D& testPoint = gWorkingGeometry->getPoint(modPoints[i]);
         Point3D testVector       = testPoint - centroid;
         testVector.normalize();

         Point3D crossVector;
         mCross(currVector, testVector, &crossVector);

         if (mDot(crossVector, rPlaneEQ.normal) < 0.0) {
            // Candidate
            F32 val = mDot(testVector, currVector);
            if (val > maxDot) {
               maxDot   = val;
               maxIndex = i;
            }
         }
      }
      AssertISV(maxIndex != -1, "createBoundedWinding: no point found for winding?");

      finalWinding->indices[finalWinding->numIndices++] = modPoints[maxIndex];
      modPoints.erase(maxIndex);
   }
}


struct FooeyDebug {
   Point3D p;
   F64     d;
};

void splitBrush(CSGBrush*& inBrush,
                U32        planeEQIndex,
                CSGBrush*& outFront,
                CSGBrush*& outBack)
{
   AssertFatal(outFront == NULL && outBack == NULL, "Must be null on entry");

   CSGBrush* pFront = gBrushArena.allocateBrush();
   CSGBrush* pBack  = gBrushArena.allocateBrush();

   pFront->copyBrush(inBrush);
   pBack->copyBrush(inBrush);

   bool regen = false;
   for (S32 i = S32(pFront->mPlanes.size()) - 1; i >= 0; i--) {
      CSGPlane& rPlane = pFront->mPlanes[i];
      
      if (rPlane.planeEQIndex == planeEQIndex ||
          rPlane.planeEQIndex == gWorkingGeometry->getPlaneInverse(planeEQIndex))
         continue;

      if (rPlane.clipWindingToPlaneFront(planeEQIndex) == true) {
         // Winding was modified
         regen = true;

         if (rPlane.winding.numIndices == 0) {
            // Plane is completely gone.  nuke it.
            pFront->mPlanes.erase(i);
         }
      }
   }
   if (pFront->mPlanes.size() <= 1) {
      // Nuke it.
      gBrushArena.freeBrush(pFront);
      pFront = NULL;
   } else {
      // Otherwise, we have a good brush still.  Check to see if we need to add
      //  a plane, and regen, or can just return it...
      if (regen == true) {
         pFront->mPlanes.increment();
         CSGPlane& rPlane = pFront->mPlanes.last();

         rPlane.planeEQIndex = gWorkingGeometry->getPlaneInverse(planeEQIndex);
         rPlane.pTextureName = NULL;
         rPlane.flags        = 0;

         for (U32 i = 0; i < pFront->mPlanes.size(); i++)
            pFront->mPlanes[i].winding.numIndices = 0;

         pFront->selfClip();
      }

      outFront = pFront;
   }

   //-------------------------------------- now do the back...
   regen = false;
   for (S32 i = S32(pBack->mPlanes.size()) - 1; i >= 0; i--) {
      CSGPlane& rPlane = pBack->mPlanes[i];
      
      if (rPlane.planeEQIndex == planeEQIndex ||
          rPlane.planeEQIndex == gWorkingGeometry->getPlaneInverse(planeEQIndex))
         continue;

      if (rPlane.clipWindingToPlaneFront(gWorkingGeometry->getPlaneInverse(planeEQIndex)) == true) {
         // Winding was modified
         regen = true;

         if (rPlane.winding.numIndices == 0) {
            // Plane is completely gone.  nuke it.
            pBack->mPlanes.erase(i);
         }
      }
   }
   if (pBack->mPlanes.size() <= 1) {
      // Nuke it.
      gBrushArena.freeBrush(pBack);
      pBack = NULL;
   } else {
      // Otherwise, we have a good brush still.  Check to see if we need to add
      //  a plane, and regen, or can just return it...
      if (regen == true) {
         pBack->mPlanes.increment();
         CSGPlane& rPlane = pBack->mPlanes.last();

         rPlane.planeEQIndex = planeEQIndex;
         rPlane.pTextureName = NULL;
         rPlane.flags        = 0;

         for (U32 i = 0; i < pBack->mPlanes.size(); i++)
            pBack->mPlanes[i].winding.numIndices = 0;

         pBack->selfClip();
      }

      outBack = pBack;
   }
}


void assessPlane(const U32    testPlane,
                 const CSGBrush& rTestBrush,
                 S32*          numCoplanar,
                 S32*          numTinyWindings,
                 S32*          numSplits,
                 S32*          numFront,
                 S32*          numBack)
{
   for (U32 i = 0; i < rTestBrush.mPlanes.size(); i++) {
      if (gWorkingGeometry->isCoplanar(testPlane, rTestBrush.mPlanes[i].planeEQIndex)) {
         // Easy.  Brush abuts the plane.
         (*numCoplanar)++;
         if (testPlane == rTestBrush.mPlanes[i].planeEQIndex)
            (*numBack)++;
         else
            (*numFront)++;
         return;
      }
   }

   // Ah well, more work...
   const PlaneEQ& rPlane = gWorkingGeometry->getPlaneEQ(testPlane);

   static UniqueVector uniquePoints(64);
   for (U32 i = 0; i < rTestBrush.mPlanes.size(); i++) {
      for (U32 j = 0; j < rTestBrush.mPlanes[i].winding.numIndices; j++)
         uniquePoints.pushBackUnique(rTestBrush.mPlanes[i].winding.indices[j]);
   }

   F64 maxFront = 0.0;
   F64 minBack  = 0.0;

   for (U32 i = 0; i < uniquePoints.size(); i++) {
      const Point3D& rPoint = gWorkingGeometry->getPoint(uniquePoints[i]);

      F64 dist = rPlane.distanceToPlane(rPoint);
      if (dist > maxFront)
         maxFront = dist;
      if (dist < minBack)
         minBack = dist;
   }   

   //AssertFatal(maxFront > gcPlaneDistanceEpsilon || minBack < -gcPlaneDistanceEpsilon,
   //            "This should only happen for coplanar windings...");

   if (maxFront >  gcPlaneDistanceEpsilon)
      (*numFront)++;

   if (minBack  < -gcPlaneDistanceEpsilon)
      (*numBack)++;

   if (maxFront >  gcPlaneDistanceEpsilon &&
       minBack  < -gcPlaneDistanceEpsilon)
      (*numSplits)++;


   if ((maxFront > 0.0 && maxFront < 1.0) ||
       (minBack  < 0.0 && minBack  > -1.0))
      (*numTinyWindings)++;

   // done.
   uniquePoints.clear();
}


//------------------------------------------------------------------------------
bool windingsEquivalent(const Winding& winding1, const Winding& winding2)
{
   if (winding1.numIndices != winding2.numIndices)
      return false;

   U32 oneCurr = 0;
   S32 twoCurr  = -1;
   for (U32 i = 0; i < winding2.numIndices; i++) {
      if (winding2.indices[i] == winding1.indices[0]) {
         twoCurr = i;
         break;
      }
   }
   if (twoCurr == -1)
      return false;

   for (U32 i = 0; i < winding1.numIndices; i++) {
      if (winding1.indices[oneCurr] != winding2.indices[twoCurr])
         return false;

      oneCurr = (oneCurr + 1) % winding1.numIndices;
      twoCurr = (twoCurr + 1) % winding2.numIndices;
   }

   return true;
}


//------------------------------------------------------------------------------
bool canCollapseWinding(const Winding& winding1, const Winding& winding2)
{
   // We are looking for an edge that is backwards in winding2 that is present
   //  in winding1.  Note that this is not a general purpose algorithm, it's
   //  very tailored to the break*Winding function from the BSP.
   //
   AssertFatal(winding1.numIndices >= 3 && winding2.numIndices >= 3, "improper windings");

   // First, lets check the ambient light condition...
   if (winding1.numZoneIds == 0 || winding2.numZoneIds == 0)
      return false;

   if (gWorkingGeometry->mZones[winding1.zoneIds[0]]->ambientLit != 
       gWorkingGeometry->mZones[winding2.zoneIds[0]]->ambientLit)
      return false;

   for (U32 i = 0; i < winding1.numIndices; i++) {
      U32 edgeStart = winding1.indices[(i + 1) % winding1.numIndices];
      U32 edgeEnd   = winding1.indices[i];

      for (U32 j = 0; j < winding2.numIndices; j++) {
         if (winding2.indices[j] == edgeStart &&
             winding2.indices[(j + 1) % winding2.numIndices] == edgeEnd) {
            // Yay!
            return true;
         }
      }
   }

   return false;
}

//------------------------------------------------------------------------------
bool pointsAreColinear(U32 p1, U32 p2, U32 p3)
{
   const Point3D& rP1 = gWorkingGeometry->getPoint(p1);
   const Point3D& rP2 = gWorkingGeometry->getPoint(p2);
   const Point3D& rP3 = gWorkingGeometry->getPoint(p3);

   // We are testing the distance from p3 to the line defined
   //  by p1 and p2.
   //
   F64 f = rP2.x - rP1.x;
   F64 g = rP2.y - rP1.y;
   F64 h = rP2.z - rP1.z;

   // P0 in our case is rP1.  Note the the following is taken from
   //  "A Programmer's Geometry"
   //
   F64 denom = f*f + g*g + h*h;
   if (denom < gcPlaneDistanceEpsilon * gcPlaneDistanceEpsilon) {
      // If two points are the same, then all three are colinear...
      return true;
   }

   F64 xj0 = rP3.x - rP1.x;
   F64 yj0 = rP3.y - rP1.y;
   F64 zj0 = rP3.z - rP1.z;

   F64 fygx = f * yj0 - g * xj0;
   F64 fzhx = f * zj0 - h * xj0;
   F64 gzhy = g * zj0 - h * yj0;

   F64 v1 =  g * fygx + h * fzhx;
   F64 v2 =  h * gzhy - f * fygx;
   F64 v3 = -f * fzhx - g * gzhy;

   F64 dist = mSqrt(v1*v1 + v2*v2 + v3*v3) / denom;

   if (dist < gcPlaneDistanceEpsilon)
      return true;
   else
      return false;
}


//------------------------------------------------------------------------------
bool collapseWindings(Winding& into, const Winding& from)
{
   if (canCollapseWinding(into, from) == false)
      return false;

   // DEBUG POINT: Check that into contains no colinear
   // DEBUG POINT: Check that from contains no colinear

   U32 oneStart;
   U32 twoStart;

   bool found = false;
   for (U32 i = 0; i < into.numIndices && !found; i++) {
      U32 edgeStart = into.indices[(i + 1) % into.numIndices];
      U32 edgeEnd   = into.indices[i];

      for (U32 j = 0; j < from.numIndices && !found; j++) {
         if (from.indices[j] == edgeStart &&
             from.indices[(j + 1) % from.numIndices] == edgeEnd) {
            found = true;
            oneStart = i;
            twoStart = j;
         }
      }
   }
   AssertFatal(found == true, "error, something missing in dodge city, no common edge!");

   AssertFatal((into.numIndices + from.numIndices - 2) < MaxWindingPoints,
               "Uh, need to increase MaxWindingPoints.  Talk to DMoore");
   U32 newIndices[64];
   U32 newCount = 0;

   for (U32 i = 0; i <= oneStart; i++) {
      newIndices[newCount++] = into.indices[i];
   }
   for (U32 i = (twoStart + 2) % from.numIndices; i != twoStart;) {
      newIndices[newCount++] = from.indices[i];

      i = (i+1) % from.numIndices;
   }
   for (U32 i = (oneStart + 1) % into.numIndices; i > 0;) {
      newIndices[newCount++] = into.indices[i];

      i = (i+1) % into.numIndices;
   }

   // Remove any colinear edges...
   for (U32 i = 0; i < newCount; i++) {
      U32 index0 = i;
      U32 index1 = (i + 1) % newCount;
      U32 index2 = (i + 2) % newCount;

      if (pointsAreColinear(newIndices[index0], newIndices[index1], newIndices[index2])) {
         // Remove the edge...
         U32 size = (newCount - index1 - 1) * sizeof(U32);
         dMemmove(&newIndices[index1], &newIndices[index1+1], size);
         newCount--;
      }
   }
   AssertFatal(newCount >= 3 && newCount <= MaxWindingPoints, "Ah, crap, something goofed in collapseWindings.  Talk to DMoore");

   // See if the new winding is convex
   Point3D reference;
   for (U32 i = 0; i < newCount; i++) {
      U32 j = (i + 1) % newCount;
      U32 k = (i + 2) % newCount;

      Point3D vec1 = gWorkingGeometry->getPoint(newIndices[i]) - gWorkingGeometry->getPoint(newIndices[j]);
      Point3D vec2 = gWorkingGeometry->getPoint(newIndices[k]) - gWorkingGeometry->getPoint(newIndices[j]);

      Point3D result;
      mCross(vec1, vec2, &result);
      if (i == 0) {
         reference = result;
      } else {
         F64 dot = mDot(reference, result);
         if (dot < 0.0) {
            return false;
         }
      }
   }

   // Copy the new rWinding, and we're set!
   //
   dMemcpy(into.indices, newIndices, newCount * sizeof(U32));
   into.numIndices = newCount;

   // Make sure the nodes are merged
   for (U32 i = 0; i < from.numNodes; i++) {
      bool found = false;
      for (U32 j = 0; j < into.numNodes; j++) {
         if (into.solidNodes[j] == from.solidNodes[i]) {
            found = true;
            break;
         }
      }
      if (found == false) {
         AssertFatal(into.numNodes < MaxWindingNodes, "Error, too many solid nodes affecting poly.  Plase ask DMoore to increase the allowed number!");
         into.solidNodes[into.numNodes++] = from.solidNodes[i];
      }
   }
   for (U32 i = 0; i < from.numZoneIds; i++)
      into.insertZone(from.zoneIds[i]);

   // DEBUG POINT: into must contain no colinear

   return true;
}


//------------------------------------------------------------------------------
void extendName(char* pName, const char* pExt)
{
   AssertFatal(pName != NULL && dStrlen(pName) != 0 && pExt != NULL && pExt[0] == '.',
               "Error, bad inputs to reextendName");

   dStrcat(pName, pExt);
}


void reextendName(char* pName, const char* pExt)
{
   AssertFatal(pName != NULL && dStrlen(pName) != 0 && pExt != NULL && pExt[0] == '.',
               "Error, bad inputs to reextendName");

   char* pDot = dStrrchr(pName, '.');
   if (pDot != NULL) {
      if (dStricmp(pDot, pExt) != 0) {
         dStrcpy(pDot, pExt);
      }
   } else {
      dStrcat(pName, pExt);
   }
}

bool createBaseWinding(const Vector<U32>& rPoints, const Point3D& normal, Winding* pWinding)
{
   if (rPoints.size() <= 2) {
      dPrintf("::createBaseWinding: Bad face on brush.  < 3 points.  DMMNOTE: Handle better?\n");
      return false;
   }
   if (rPoints.size() > 32) {
      dPrintf("::createBaseWinding: Bad face on brush.  > 32 points.  DMMNOTE: Increase max winding points?\n");
      return false;
   }


   Vector<U32> modPoints = rPoints;

   // Create a centroid first...
   Point3D centroid(0, 0, 0);
   for (U32 i = 0; i < modPoints.size(); i++)
      centroid += gWorkingGeometry->getPoint(modPoints[i]);
   centroid /= F64(modPoints.size());

   // Ok, we have a centroid.  We (arbitrarily) take the last point, and start from
   //  there...
   pWinding->indices[0] = modPoints[modPoints.size() - 1];
   pWinding->numIndices = 1;
   modPoints.erase(modPoints.size() - 1);

   while (modPoints.size() != 0) {
      // Find the largest dot product of a point with a positive facing
      //  cross product.
      //
      F64 maxDot  = -1e10;
      S32 maxIndex = -1;

      const Point3D& currPoint = gWorkingGeometry->getPoint(pWinding->indices[pWinding->numIndices - 1]);
      Point3D currVector       = currPoint - centroid;
      currVector.normalize();

      for (U32 i = 0; i < modPoints.size(); i++) {

         const Point3D& testPoint = gWorkingGeometry->getPoint(modPoints[i]);
         Point3D testVector       = testPoint - centroid;
         testVector.normalize();

         Point3D crossVector;
         mCross(currVector, testVector, &crossVector);
         crossVector.normalize();

         if (mDot(crossVector, normal) < 0.0) {
            // Candidate
            F64 val  = mDot(testVector, currVector);
            F64 dVal = mDot(testVector, currVector);

            if (val > maxDot) {
               maxDot   = val;
               maxIndex = i;
            }
         }
      }
      //AssertISV(maxIndex != -1, "::createBaseWinding: no point found for winding?");
      if(maxIndex == -1)
         return false;

      pWinding->indices[pWinding->numIndices++] = modPoints[maxIndex];
      modPoints.erase(maxIndex);
   }

   pWinding->numNodes = 0;
   pWinding->numZoneIds = 0;

   return true;
}


BrushArena::BrushArena(U32 _arenaSize)
{
   arenaValid    = true;
   arenaSize     = _arenaSize;
   mFreeListHead = NULL;
}

BrushArena::~BrushArena()
{
   arenaValid = false;
   for (U32 i = 0; i < mBuffers.size(); i++) {
      delete [] mBuffers[i];
      mBuffers[i] = NULL;
   }
   mFreeListHead = NULL;
   arenaSize     = 0;
}

void BrushArena::newBuffer()
{
   // Otherwise, we set up some more free windings...
   CSGBrush* pNewBuffer = new CSGBrush[arenaSize];
   mBuffers.push_back(pNewBuffer);
   for (U32 i = 0; i < arenaSize - 1; i++) {
      pNewBuffer[i].pNext = &pNewBuffer[i+1];
   }
   pNewBuffer[arenaSize - 1].pNext = NULL;

   mFreeListHead = pNewBuffer;
}

CSGBrush* BrushArena::allocateBrush()
{
   AssertFatal(arenaValid == true, "Arena invalid!");

   if (mFreeListHead == NULL)
      newBuffer();
   AssertFatal(mFreeListHead != NULL, "error, that shouldn't happen!");

   CSGBrush* pRet     = mFreeListHead;
   mFreeListHead      = pRet->pNext;
   pRet->pNext        = NULL;
   pRet->mIsAmbiguous = false;
   pRet->mPlanes.clear();
   return pRet;
}

void BrushArena::freeBrush(CSGBrush* junk)
{
   AssertFatal(arenaValid == true, "Arena invalid!");
   AssertFatal(junk->pNext == NULL, "Error, this is still on the free list!");

   junk->pNext   = mFreeListHead;
   mFreeListHead = junk;
}


bool intersectWGPlanes(U32 i, U32 j, U32 k,
                       Point3D* pOutput)
{
   const PlaneEQ& rPlaneEQ1 = gWorkingGeometry->getPlaneEQ(i);
   const PlaneEQ& rPlaneEQ2 = gWorkingGeometry->getPlaneEQ(j);
   const PlaneEQ& rPlaneEQ3 = gWorkingGeometry->getPlaneEQ(k);

   F64 bc  = (rPlaneEQ2.normal.y * rPlaneEQ3.normal.z) - (rPlaneEQ3.normal.y * rPlaneEQ2.normal.z);
   F64 ac  = (rPlaneEQ2.normal.x * rPlaneEQ3.normal.z) - (rPlaneEQ3.normal.x * rPlaneEQ2.normal.z);
   F64 ab  = (rPlaneEQ2.normal.x * rPlaneEQ3.normal.y) - (rPlaneEQ3.normal.x * rPlaneEQ2.normal.y);
   F64 det = (rPlaneEQ1.normal.x * bc) - (rPlaneEQ1.normal.y * ac) + (rPlaneEQ1.normal.z * ab);

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

   F64 dc     = (rPlaneEQ2.dist * rPlaneEQ3.normal.z) - (rPlaneEQ3.dist * rPlaneEQ2.normal.z);
   F64 db     = (rPlaneEQ2.dist * rPlaneEQ3.normal.y) - (rPlaneEQ3.dist * rPlaneEQ2.normal.y);
   F64 ad     = (rPlaneEQ3.dist * rPlaneEQ2.normal.x) - (rPlaneEQ2.dist * rPlaneEQ3.normal.x);
   F64 detInv = 1.0 / det;

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

   return true;
}
