//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

// The core algorithms in this file are inspired by public papers written
// by G. van den Bergen for his interference detection library,
// "SOLID 2.0"

#include "core/dataChunker.h"
#include "collision/collision.h"
#include "scene/sceneObject.h"
#include "collision/convex.h"
#include "collision/gjk.h"


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

static F32 rel_error = 1E-5f;     // relative error in the computed distance
static F32 sTolerance = 1E-3f;    // Distance tolerance
static F32 sEpsilon2 = 1E-20f;    // Zero length vector
static U32 sIteration = 15;       // Stuck in a loop?

S32 num_iterations = 0;
S32 num_irregularities = 0;


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

GjkCollisionState::GjkCollisionState()
{
   mBits = 0;
   mAll_bits = 0;
   U32 x, y;
   for (x = 0; x < 16; x++)
      for (y = 0; y < 4; y++)
         mDet[x][y] = 0.0f;

   for (x = 0; x < 4; x++)
      for (y = 0; y < 4; y++)
         mDP[x][y] = 0.0f;

   mLast = 0;
   mLast_bit = 0;
   mA = mB = 0;
}

GjkCollisionState::~GjkCollisionState()
{
}


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

void GjkCollisionState::swap()
{
   Convex* t = mA; mA = mB; mB = t;
   CollisionStateList* l = mLista; mLista = mListb; mListb = l;
   mDistvec.neg();
}


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

void GjkCollisionState::compute_det()
{
   // Dot new point with current set
   for (S32 i = 0, bit = 1; i < 4; ++i, bit <<=1)
      if (mBits & bit)
		  mDP[i][mLast] = mDP[mLast][i] = mDot(mY[i], mY[mLast]);
   mDP[mLast][mLast] = mDot(mY[mLast], mY[mLast]);

   // Calulate the determinent
   mDet[mLast_bit][mLast] = 1;
   for (S32 j = 0, sj = 1; j < 4; ++j, sj <<= 1) {
      if (mBits & sj) {
         S32 s2 = sj | mLast_bit;
		 mDet[s2][j] = mDP[mLast][mLast] - mDP[mLast][j];
		 mDet[s2][mLast] = mDP[j][j] - mDP[j][mLast];
         for (S32 k = 0, sk = 1; k < j; ++k, sk <<= 1) {
            if (mBits & sk) {
              S32 s3 = sk | s2;
			  mDet[s3][k] = mDet[s2][j] * (mDP[j][j] - mDP[j][k]) +
                            mDet[s2][mLast] * (mDP[mLast][j] - mDP[mLast][k]);
			  mDet[s3][j] = mDet[sk | mLast_bit][k] * (mDP[k][k] - mDP[k][j]) +
                            mDet[sk | mLast_bit][mLast] * (mDP[mLast][k] - mDP[mLast][j]);
			  mDet[s3][mLast] = mDet[sk | sj][k] * (mDP[k][k] - mDP[k][mLast]) +
                                mDet[sk | sj][j] * (mDP[j][k] - mDP[j][mLast]);
            }
         }
      }
   }

   if (mAll_bits == 15) {
     mDet[15][0] = mDet[14][1] * (mDP[1][1] - mDP[1][0]) +
                   mDet[14][2] * (mDP[2][1] - mDP[2][0]) +
                   mDet[14][3] * (mDP[3][1] - mDP[3][0]);
	 mDet[15][1] = mDet[13][0] * (mDP[0][0] - mDP[0][1]) +
                   mDet[13][2] * (mDP[2][0] - mDP[2][1]) +
                   mDet[13][3] * (mDP[3][0] - mDP[3][1]);
	 mDet[15][2] = mDet[11][0] * (mDP[0][0] - mDP[0][2]) +
                   mDet[11][1] * (mDP[1][0] - mDP[1][2]) +
                   mDet[11][3] * (mDP[3][0] - mDP[3][2]);
	 mDet[15][3] = mDet[7][0] * (mDP[0][0] - mDP[0][3]) +
                   mDet[7][1] * (mDP[1][0] - mDP[1][3]) +
                   mDet[7][2] * (mDP[2][0] - mDP[2][3]);
   }
}


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

inline void GjkCollisionState::compute_vector(S32 bits, VectorF& v)
{
   F32 sum = 0;
   v.set(0, 0, 0);
   for (S32 i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
      if (bits & bit) {
         sum += mDet[bits][i];
         v += mY[i] * mDet[bits][i];
      }
   }
   v *= 1 / sum;
}


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

inline bool GjkCollisionState::valid(S32 s)
{
   for (S32 i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
      if (mAll_bits & bit) {
         if (s & bit) {
            if (mDet[s][i] <= 0)
               return false;
         }
         else
            if (mDet[s | bit][i] > 0)
               return false;
      }
   }
   return true;
}


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

inline bool GjkCollisionState::closest(VectorF& v)
{
   compute_det();
   for (S32 s = mBits; s; --s) {
      if ((s & mBits) == s) {
         if (valid(s | mLast_bit)) {
			 mBits = s | mLast_bit;
            if (mBits != 15)
    	         compute_vector(mBits, v);
	         return true;
         }
      }
   }
   if (valid(mLast_bit)) {
      mBits = mLast_bit;
      v = mY[mLast];
      return true;
   }
   return false;
}


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

inline bool GjkCollisionState::degenerate(const VectorF& w)
{
  for (S32 i = 0, bit = 1; i < 4; ++i, bit <<= 1)
   if ((mAll_bits & bit) && mY[i] == w)
      return true;
  return false;
}


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

inline void GjkCollisionState::nextBit()
{
   mLast = 0;
   mLast_bit = 1;
   while (mBits & mLast_bit) {
      ++mLast;
	  mLast_bit <<= 1;
   }
}


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

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

void GjkCollisionState::set(Convex* aa, Convex* bb,
   const MatrixF& a2w, const MatrixF& b2w)
{
   mA = aa;
   mB = bb;

   mBits = 0;
   mAll_bits = 0;
   reset(a2w,b2w);

   // link
   mLista = CollisionStateList::alloc();
   mLista->mState = this;
   mListb = CollisionStateList::alloc();
   mListb->mState = this;
}


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

void GjkCollisionState::reset(const MatrixF& a2w, const MatrixF& b2w)
{
   VectorF zero(0,0,0),sa,sb;
   a2w.mulP(mA->support(zero),&sa);
   b2w.mulP(mB->support(zero),&sb);
   mDistvec = sa - sb;
   mDist = mDistvec.len();
}


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

void GjkCollisionState::getCollisionInfo(const MatrixF& mat, Collision* info)
{
   AssertFatal(false, "GjkCollisionState::getCollisionInfo() - There remain scaling problems here.");
   // This assumes that the shapes do not intersect
   Point3F pa,pb;
   if (mBits) {
      getClosestPoints(pa,pb);
      mat.mulP(pa,&info->point);
	  mB->getTransform().mulP(pb,&pa);
      info->normal = info->point - pa;
   }
   else {
      mat.mulP(mP[mLast],&info->point);
      info->normal = mDistvec;
   }
   info->normal.normalize();
   info->object = mB->getObject();
}

void GjkCollisionState::getClosestPoints(Point3F& p1, Point3F& p2)
{
   F32 sum = 0;
   p1.set(0, 0, 0);
   p2.set(0, 0, 0);
   for (S32 i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
      if (mBits & bit) {
         sum += mDet[mBits][i];
         p1 += mP[i] * mDet[mBits][i];
         p2 += mQ[i] * mDet[mBits][i];
      }
   }
   F32 s = 1 / sum;
   p1 *= s;
   p2 *= s;
}


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

bool GjkCollisionState::intersect(const MatrixF& a2w, const MatrixF& b2w)
{
   num_iterations = 0;
   MatrixF w2a,w2b;

   w2a = a2w;
   w2b = b2w;
   w2a.inverse();
   w2b.inverse();
   reset(a2w,b2w);

   mBits = 0;
   mAll_bits = 0;

   do {
      nextBit();

      VectorF va,sa;
      w2a.mulV(-mDistvec,&va);
	  mP[mLast] = mA->support(va);
      a2w.mulP(mP[mLast],&sa);

      VectorF vb,sb;
      w2b.mulV(mDistvec,&vb);
	  mQ[mLast] = mB->support(vb);
      b2w.mulP(mQ[mLast],&sb);

      VectorF w = sa - sb;
      if (mDot(mDistvec,w) > 0)
         return false;
      if (degenerate(w)) {
         ++num_irregularities;
         return false;
      }

	  mY[mLast] = w;
	  mAll_bits = mBits | mLast_bit;

      ++num_iterations;
      if (!closest(mDistvec) || num_iterations > sIteration) {
         ++num_irregularities;
         return false;
      }
   }
   while (mBits < 15 && mDistvec.lenSquared() > sEpsilon2);
   return true;
}

F32 GjkCollisionState::distance(const MatrixF& a2w, const MatrixF& b2w,
   const F32 dontCareDist, const MatrixF* _w2a, const MatrixF* _w2b)
{
   num_iterations = 0;
   MatrixF w2a,w2b;

   if (_w2a == NULL || _w2b == NULL) {
      w2a = a2w;
      w2b = b2w;
      w2a.inverse();
      w2b.inverse();
   }
   else {
      w2a = *_w2a;
      w2b = *_w2b;
   }

   reset(a2w,b2w);
   mBits = 0;
   mAll_bits = 0;
   F32 mu = 0;

   do {
      nextBit();

      VectorF va,sa;
      w2a.mulV(-mDistvec,&va);
	  mP[mLast] = mA->support(va);
      a2w.mulP(mP[mLast],&sa);

      VectorF vb,sb;
      w2b.mulV(mDistvec,&vb);
	  mQ[mLast] = mB->support(vb);
      b2w.mulP(mQ[mLast],&sb);

      VectorF w = sa - sb;
      F32 nm = mDot(mDistvec, w) / mDist;
      if (nm > mu)
         mu = nm;
      if (mu > dontCareDist)
         return mu;
      if (mFabs(mDist - mu) <= mDist * rel_error)
         return mDist;

      ++num_iterations;
      if (degenerate(w) || num_iterations > sIteration) {
         ++num_irregularities;
         return mDist;
      }

	  mY[mLast] = w;
	  mAll_bits = mBits | mLast_bit;

      if (!closest(mDistvec)) {
         ++num_irregularities;
         return mDist;
      }

	  mDist = mDistvec.len();
   }
   while (mBits < 15 && mDist > sTolerance) ;

   if (mBits == 15 && mu <= 0)
	   mDist = 0;
   return mDist;
}
