/*  
    Crystal Shooter, a first person shooter game engine.
    Homepage: http://members.xoom.com/thieber/cs

    Copyright (C) 1999 Thomas Hieber (thieber@gmx.net)

    Game specific collision detection, based on some code by 
    Alex Pfaffe. (alexpf@microsoft.com)
 
    This program is free software; you can redistribute it and/or modify 
    it under the terms of the GNU General Public License as published by 
    the Free Software Foundation; either version 2 of the License, or 
    (at your option) any later version. 
 
    This program is distributed in the hope that it will be useful, 
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    GNU General Public License for more details. 
 
    You should have received a copy of the GNU General Public License 
    along with this program; if not, write to the Free Software 
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
*/

#include "sysdef.h"
#include "csgame/gstd.h"
#include "csgame/gcollide.h"

geCollider::geCollider(geWorld* pWorld, csPolygonSet *p)
 : inherited(p)
{
  m_pWorld  = pWorld;
  m_pSprite = NULL;
}

geCollider::geCollider(geWorld* pWorld, geSprite3D *pSprite)
 : inherited(pSprite->GetCsSprite())
{
  ASSERT(pWorld);
  ASSERT(pSprite);
  
  m_pWorld  = pWorld;
  m_pSprite = pSprite;

  csColliderPointerObject::SetCollider(*(pSprite->GetCsSprite()), this, false);
  gePhysObjectPointer::    SetPointer (*(pSprite->GetCsSprite()), pSprite->GetMaster());
}

/*
Being* Being::playerSpawn(char *name)
{
  // Define the player bounding box.
  // The camera's lens or person's eye is assumed to be
  // at 0,0,0.  The height (DY), width (DX) and depth (DZ).
  // Is the size of the camera/person and the origin
  // coordinates (OX,OY,OZ) locate the bbox w.r.t. the eye.
  // This player is 1.8 metres tall (assuming 1cs unit = 1m) (6feet)
  #define DX    0.5
  #define DY    1.8
  #define DZ    0.3
  #define OX   -0.25
  #define OY   -1.1
  #define OZ    0.0
  
  Polygon3D *p;
  CHK (PolygonSet* playerps = new PolygonSet(name,CS_THING));

  playerps->add_vertex(OX,    OY,    OZ); 
  playerps->add_vertex(OX,    OY,    OZ+DZ); 
  playerps->add_vertex(OX,    OY+DY, OZ+DZ); 
  playerps->add_vertex(OX,    OY+DY, OZ); 
  playerps->add_vertex(OX+DX, OY,    OZ); 
  playerps->add_vertex(OX+DX, OY,    OZ+DZ); 
  playerps->add_vertex(OX+DX, OY+DY, OZ+DZ); 
  playerps->add_vertex(OX+DX, OY+DY, OZ); 

  // Left
  p = playerps->new_polygon ("",0);

  p->add_vertex(0); p->add_vertex(1); 
  p->add_vertex(2); p->add_vertex(3); 

  // Right
  p = playerps->new_polygon ("",0);
  p->add_vertex(4); p->add_vertex(5); 
  p->add_vertex(6); p->add_vertex(7); 

  // Front
  p = playerps->new_polygon ("",0);
  p->add_vertex(0); p->add_vertex(1); 
  p->add_vertex(5); p->add_vertex(4); 

  // Back
  p = playerps->new_polygon ("",0);
  p->add_vertex(3); p->add_vertex(2); 
  p->add_vertex(6); p->add_vertex(7); 

  // Top
  p = playerps->new_polygon ("",0);
  p->add_vertex(1); p->add_vertex(5); 
  p->add_vertex(6); p->add_vertex(2); 

  // Bottom
  p = playerps->new_polygon ("",0);
  p->add_vertex(0); p->add_vertex(4); 
  p->add_vertex(7); p->add_vertex(3); 

  CHK (Being* b = new Being(playerps));

  return b;
}
*/

int geCollider::CheckCollisionInSector(csSector* sp, gePhysicalObject*& pOther, csTransform *cdt)
{
  int hit = 0;
  pOther  = NULL;

  // Check collision with this sector.
  
  hit += CollidePair(this,csColliderPointerObject::GetCollider(*sp),cdt);

  // Check collision of with the things in this sector.
  csThing* tp = sp->GetFirstThing();
  while (tp)
  {
    // TODO, if and when Things can move, their transform must be passed in.
    hit += CollidePair(this,csColliderPointerObject::GetCollider(*tp),cdt);
    tp = (csThing*)(tp->GetNext ());
    // TODO, should test which one is the closest.
  }

  // Check collision of with the sprites in this sector.
  csSprite3D* sp3d = 0;
  csTransform cds;
  int i;
  for (i = 0 ; i < m_pWorld->GetCsWorld()->sprites.Length () ; i++)
  {
    sp3d = (csSprite3D*)m_pWorld->GetCsWorld()->sprites[i];
    cds.SetO2T (sp3d->GetW2T ());
    cds.SetOrigin (sp3d->GetW2TTranslation ());

    csCollider* pCollider = csColliderPointerObject::GetCollider(*sp3d);

    if (pCollider && pCollider!= this)
    {
      //if that sprite has got a collider, and that collidier is not
      //us, then we check for collision 
      int collisions = CollidePair(this, pCollider, cdt, &cds);
      if (collisions)
      { 
        if (pCollider->_type == csCollider::SPRITE3D)
        {
          pOther = gePhysObjectPointer::GetPointer(*(pCollider->_sp));
        }
      }
      hit+=collisions;
    }
  }

  return hit;
}

#define MAXSECTORSOCCUPIED  10      // Being can occupy no more than 10 sectors at one time.
// Find all sectors within distance d from point v.
// Currently only includes immediate neighbour sectors, should possibly
// recurse into 2ndary sectors as well.
int geCollider::FindSectors( csVector3 v, csVector3 d, csSector *s, csSector **sa )
{
  sa[0] = s;
  int i, c = 1;
  float size = d.x * d.x + d.y * d.y + d.z * d.z;
  for (i = 0 ; i < s->GetNumPolygons () && c < MAXSECTORSOCCUPIED; i++)
  {
    // Get the polygon of for this sector.
    csPolygon3D* p = (csPolygon3D*) s->GetPolygon (i);
    csPortal* portal = p->GetPortal ();
    // Handle only portals.
    if (portal != NULL)
    {
        if (p->GetPlane ()->SquaredDistance (v) < size)
        {
          sa[c] = portal->GetSector ();
          c++;
        }
    }
  }
  return c;
}

// This is the main logic for collision detection 
int geCollider::CollisionDetect(gePhysicalObject*& pOther)
{
  ASSERT(m_pSprite);
  gePosition Pos = m_pSprite->GetPosition();

  csTransform transform(csMatrix3(1,0,0, 0,1,0, 0,0,1), Pos.GetPosition());

  return CollisionDetect(&transform, Pos.GetSector(), pOther);
}

int geCollider::CollisionDetect(csTransform* pTransform, csSector* pSector, gePhysicalObject*& pOther)
{
  int hit = 0;
  
  // Find all sectors which our being occupies.
  csSector* OccupiedSectors[MAXSECTORSOCCUPIED];
  int NumSectors = FindSectors(pTransform->GetO2TTranslation (),GetBbox()->d,pSector, OccupiedSectors);

  // If we were to fall from our new position, which sector would our feet end up in?
  //csTransform cdtnew(pTransform->GetO2T (),pTransform->GetO2TTranslation ());

  CollideReset();

  hit = 0;
  for (int SectorIndex = 0; SectorIndex < NumSectors; SectorIndex++)
  {
    hit = CheckCollisionInSector(OccupiedSectors[SectorIndex], pOther, pTransform);

    if (hit) break;
  }

  return hit;
}

int geCollider::FindFirstCollision(csVector3 MoveVector, float MaxMoveDistance, 
                                   csVector3& Moved,     gePhysicalObject*& pOther)
{
  ASSERT(m_pSprite);
  gePosition Pos           = m_pSprite->GetPosition();
  //csVector3 StartPos       = Pos.GetPosition();

  Moved           = csVector3(0,0,0);
  csVector3 Move  = MoveVector;
  float   Norm    = Move.Norm();
  int     Steps   = 1;
  int     hits    = 0;
  int     curhits = 0;

  if (Norm > MaxMoveDistance)
  {
    Steps = (int)(Norm / MaxMoveDistance) + 1;
    Move  = Move / (Steps);
  }

  for (int i=0; i<Steps; i++)
  {
    Pos.Move(Move);
    Moved += Move;

    csTransform transform(csMatrix3(1,0,0, 0,1,0, 0,0,1), Pos.GetPosition());
    gePhysicalObject* pCurrentOther = NULL;
    
    curhits = CollisionDetect(&transform, Pos.GetSector(), pCurrentOther);
    if (curhits)
    {
      hits = curhits;

      //There has been a collision. Now, we will do 10 interations, to
      //find the exact position.
      const int MaxIter = 10;

      pOther = pCurrentOther;

      //Move back half way
      Move /= 2;
      Pos.Move(-Move);
      Moved -= Move;
      
      for (int j=0; j<MaxIter; j++)
      {
        //reduce Move step
        Move /= 2;

        csTransform transform(csMatrix3(1,0,0, 0,1,0, 0,0,1), Pos.GetPosition());
        gePhysicalObject* pCurrentOther = NULL;
        curhits = CollisionDetect(&transform, Pos.GetSector(), pCurrentOther);
        if (curhits)
        {
          hits = curhits;

          pOther = pCurrentOther;
          if (j == (MaxIter-1))
          {
            //on the last iteration we will move back to a position, where, 
            //there is no collision
            Pos.Move(-2*Move);
            Moved -= 2*Move;
          }
          else
          {
            Pos.Move(-Move);
            Moved -= Move;
          }
        }
        else
        {
          if (j != (MaxIter-1))
          {
            Pos.Move(Move);
            Moved += Move;
          }
        }
      } //for (Iterations)
      break;
    } //if (Collision detect)
  } //for (Major steps)

  return hits;
}
