#include "stdafx.h"

#include <math.h>

#include "Frustum.h"

#include "Tile.h"
#include "TArc.h"

// MFC - Assists in finding memory leaks:
#ifdef MFC_DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

/******************************************************************************/
/** \brief Destruktor
*******************************************************************************/
Frustum::~Frustum()
{
delete [] (m_sidePlanes);
}

/******************************************************************************/
/** \brief Konstruktor
*******************************************************************************/
Frustum::Frustum()
  {
  viewAngle       = 60;   // Blickwinkel eines Menschen
  NearPlaneHeight = 200;  // entspricht der Bildschirmaufloseung
  NearPlaneWidth  = 320;  // entspricht der Bildschirmaufloesung
  NearFarDist     = 4000; // 200; // Abstand Near- zur FarPlane
  OriginDist      = 1;    // NearPlaneDist/2;

  init();
  }

/******************************************************************************/
/** \brief Konstruktor
    \param width  Breite der Nearplane, bzw. des Bildschirmfensters
    \param height Hoehe  der Nearplane, bzw. des Bildschirmfensters
*******************************************************************************/
Frustum::Frustum(int width, int height)
   {
   viewAngle       = 60;     // Blickwinkel eines Menschen
   NearPlaneHeight = height; // entspricht der Bildschirmaufloseung
   NearPlaneWidth  = width;  // entspricht der Bildschirmaufloesung
   NearFarDist     = 4000;   // 200; // Abstand Near- zur FarPlane
   OriginDist      = 1;      // NearPlaneDist/2;

   init();
   }
  
int Frustum::SIDE_PLANES = 10;
  
/******************************************************************************/
/** \brief Initialisiert das Frustum
*******************************************************************************/
void Frustum::init()
  {
  m_usedSidePlanes = 4;
  m_sidePlanes =  new Plane[SIDE_PLANES];
  
  vOrigin.clear();       // Ursprung in 0,0,0
  vEnd.set(0,0,300); 
  
  NearPlaneDist   = 0;   // Wird aus viewAngle und NearPlaneWidth berechnet
  FarPlaneDist    = 0;   // Meditativer Abstand zur Cameraposition

  Origin.clear(); 

  PlaneDir.clear(); 

  OriginPlaneLR.clear(); 
  OriginPlaneUR.clear(); 
  OriginPlaneLL.clear(); 
  OriginPlaneUL.clear(); 

  NearPlaneUR.clear(); 
  NearPlaneLR.clear(); 
  NearPlaneUL.clear(); 
  NearPlaneLL.clear(); 

  FarPlaneUR.clear(); 
  FarPlaneLR.clear(); 
  FarPlaneUL.clear(); 
  FarPlaneLL.clear(); 

  OriginPlane.clear(); 
  NearPlane.clear(); 
  FarPlane.clear(); 
  
  for (int i = 0; i < SIDE_PLANES; i++)
    m_sidePlanes[i].clear();
  
  float npw2 = (float)NearPlaneWidth  / 2.0f;
  float nph2 = (float)NearPlaneHeight / 2.0f;

  TArc* arc = TArc::getInstance();
  NearPlaneDist = (int) (npw2 / arc->getTan(viewAngle/2));
  FarPlaneDist = NearPlaneDist + NearFarDist;

  float fpw2 =  (npw2 / (float)NearPlaneDist) * (float)FarPlaneDist;
  float fph2 =  (nph2 / (float)NearPlaneDist) * (float)FarPlaneDist;

  Origin.set(0,0,0);
  PlaneDir.set(0,0, (float)FarPlaneDist);

  OriginPlaneLR.clear(); 
  OriginPlaneUR.clear(); 
  OriginPlaneLL.clear(); 
  OriginPlaneUL.clear(); 
  
  OriginPlaneUR.set( npw2,  nph2, OriginDist);
  OriginPlaneLR.set( npw2, -nph2, OriginDist);
  OriginPlaneUL.set(-npw2,  nph2, OriginDist);
  OriginPlaneLL.set(-npw2, -nph2, OriginDist);

  float NearDist = (float)NearPlaneDist; 
  NearPlaneUR.set( npw2,  nph2, NearDist);
  NearPlaneLR.set( npw2, -nph2, NearDist);
  NearPlaneUL.set(-npw2,  nph2, NearDist);
  NearPlaneLL.set(-npw2, -nph2, NearDist);

  float FarDist = (float)FarPlaneDist;
  FarPlaneUR.set( fpw2,  fph2, FarDist);
  FarPlaneLR.set( fpw2, -fph2, FarDist);
  FarPlaneUL.set(-fpw2,  fph2, FarDist);
  FarPlaneLL.set(-fpw2, -fph2, FarDist);

  // Achtung: Die Wahl der Parameter und die Reihenfolge ist entscheidend
  // damit der Normalenvektor nach 'aussen' zeigt
  OriginPlane.set(OriginPlaneLL, OriginPlaneUL, OriginPlaneLR);

  NearPlane.set(NearPlaneLL, NearPlaneUL, NearPlaneLR);
  FarPlane.set(FarPlaneLR, FarPlaneUR, FarPlaneLL);
  
  m_sidePlanes[PLANE_TOP].set(NearPlaneUR,    NearPlaneUL, FarPlaneUR);
  m_sidePlanes[PLANE_BOTTOM].set(NearPlaneLL, NearPlaneLR, FarPlaneLL);
  m_sidePlanes[PLANE_LEFT].set(FarPlaneLL,    FarPlaneUL,  NearPlaneLL);
  m_sidePlanes[PLANE_RIGHT].set(NearPlaneLR,  NearPlaneUR, FarPlaneLR);
  }

/******************************************************************************/
/** \brief Erzeugt aus den Geraden einer geclippten Tile (typ Tile.PORTAL) die Planes 
    \param tile Referenz auf die Tile
*******************************************************************************/
void Frustum::set(Tile& tile)
   {
   int i;  
   float dx, dy, dz, x, y, z, len;
   Vec nvec; // = new Vec(0,0,0);

   // Wenn das Portalfrustum zu klein ist, treten Rundungsfehler
   // und damit Renderingfehler auf. Deshalb muss der Mindestabstand 
   // zwischen zwei Vertices >= 1.0 sein.
   for (i=0; i < tile.m_maxFC; i++)
      {
      dx = tile.m_vFC[i+1].m_x - tile.m_vFC[i].m_x;
      dy = tile.m_vFC[i+1].m_y - tile.m_vFC[i].m_y;
      dz = tile.m_vFC[i+1].m_z - tile.m_vFC[i].m_z;
      len = (float)sqrt(dx*dx + dy*dy + dz*dz);
      if (len < 1.0f)
         {
         printf("Portalfrustum skipped len=%lf", len);
         return;
         }
      }
     
// Achtung: Der Normalenvektor der Planes muss nach 'aussen' zeigen!!!
// Das bedeutet, das die Vertices der PORTAL-Tile im Uhrzeigersinn 
// angeordnet sein muessen (betrachtet aus der jeweiligen Cameraposition)
// Weil die Portaltile bereits gegen das Camerafrustum geclipped wurde,
// kann es aus mehr als 4 Vertices bestehen!
     
   m_usedSidePlanes = tile.m_maxFC;
   for (i = 0; i < m_usedSidePlanes; i++)
      m_sidePlanes[i].set (nvec, tile.m_vFC[i],tile.m_vFC[i+1]);
   m_sidePlanes[i].set (nvec, tile.m_vFC[i],tile.m_vFC[0]);

   dx = tile.m_vFC[2].m_x - tile.m_vFC[0].m_x;
   dy = tile.m_vFC[2].m_y - tile.m_vFC[0].m_y;
   dz = tile.m_vFC[2].m_z - tile.m_vFC[0].m_z;
   x = dx / 2.0f;
   y = dy / 2.0f;
   z = dz / 2.0f;
   
   x = x + tile.m_vFC[0].m_x;
   y = y + tile.m_vFC[0].m_y;
   z = z + tile.m_vFC[0].m_z;
     
   vEnd.set(x, y, z);
   vEnd.mul(100);
   }

/******************************************************************************/
/** \brief Getter fuer die Distanz zum Ursprung 
    \return Distanz
*******************************************************************************/
float Frustum::getOriginDist()
 {
 return (OriginDist);
 }

/******************************************************************************/
/** \brief Die Tile wird gegen das Frustum geclipped.
           Zuerst werden die orginal Vertices in die Clipp-Liste uebertragen.
           Anschliessend wird gegen alle Planes des Frustums geclippt, sodass
           zum Schluss das 'eingepasste' Polygon in der Clipp-Liste uebrig bleibt 
    \param tile Referenz auf die Tile
*******************************************************************************/
void Frustum::clip(Tile& tile)
   {
   tile.resetClipList();
   tile.clip(OriginPlane);
   tile.clip(FarPlane);
     
   tile.clip(m_sidePlanes[PLANE_LEFT]);
   tile.clip(m_sidePlanes[PLANE_RIGHT]);
   tile.clip(m_sidePlanes[PLANE_TOP]);
   tile.clip(m_sidePlanes[PLANE_BOTTOM]);
   }
       
/******************************************************************************/
/** \brief Testet, ob eine Tile im Frustum liegt oder schneidet
     \param tile Die Tile
     \return true-->Tile liegt innerhalb des Frustum, oder schneidet es
             false-->Tile liegt ausserhalb des Frustums
*******************************************************************************/
bool Frustum::inside(Tile& tile)
   {
   int i =0;
   int insideNearPlane[5]; // = new int[5];

   insideNearPlane[0] = OriginPlane.check(tile.m_vertex[0]);
   insideNearPlane[1] = OriginPlane.check(tile.m_vertex[1]);
   insideNearPlane[2] = OriginPlane.check(tile.m_vertex[2]);
   insideNearPlane[3] = OriginPlane.check(tile.m_vertex[3]);

   // Alles was hinter der Camera liegt, ist nicht sichtbar
   if ((insideNearPlane[0]   != Plane::INSIDE) && 
       (insideNearPlane[1]   != Plane::INSIDE) &&
       (insideNearPlane[2]   != Plane::INSIDE) &&
       (insideNearPlane[3]   != Plane::INSIDE))
        {
        return (false);
        }
    
   // Liegt mind. ein Punkt im Frustum, muss die Tile gerendert werden
   for (i=0; i < tile.m_numVertices; i++)
      if (inside(tile.m_vertex[i]) == true)
         return (true);
    
   // Schneidet eine Gerade das Frustum muss die Tile gerendert werden
   Line line; // = new Line();
   for (i=0; i < tile.m_numVertices; i++)
      {
      line.set(tile.m_vertex[i], tile.m_vertex[i+1]);
      if (intersects(line) == true)        
         return (true);
      }

   // Wenn die Camera sehr nah an einer Tile steht, wird diese perspektivisch 
   // sehr 'gross'. Dann kann es sein, das kein Punkt im Frustum liegt und keine
   // Gerade das Frustum schneidet. In diesem Fall wird hier mit einem Strahl
   // eine Kollistionsabfrage getestet. Die Laenge kann anhand der Tilegroesse
   // und der Frustumgeometrie exakt ermittel werden. Ich habe den Wert aber 
   // meditativ festgelegt. 
   if (tile.intersect(vOrigin, vEnd) == true)
       return (true);
    
   return (false);
   }

/******************************************************************************/
/** \brief Testet, ob ein Partikel im Frustum liegt
    \param partikel Das Partikel
    \return true--> Partikel liegt innerhalb  des Frustum
            false-->Partikel liegt ausserhalb des Frustums
*******************************************************************************/
bool Frustum::inside(Partikel& partikel)
{
bool a = inside(partikel.m_pos);
return (a);
}
      
/******************************************************************************/
/** \brief Testet, ob ein Punkt im Frustum Liegt
    \param v der Punkt
    \return true-->Punkt liegt innerhalb des Frustums
            false-->Punkt liegt ausserhalb des Frustums
*******************************************************************************/
bool Frustum::inside (Vec& v)
 {
 // Diese Variante ist einen Tick schneller
 
 for (int i = 0; i < this->m_usedSidePlanes; i++)
    if (m_sidePlanes[i].check(v) != 0)
        return(false);

 if (OriginPlane.check(v) != 0)
   return (false);

 if (FarPlane.check(v) != 0)
   return (false);
 
 return (true);
 }
 
/******************************************************************************/
/** \brief Testet, ob eine Line das Frustum schneidet          
    \param line Die Linie
    \return  true Linie schneidet das Frustum
             false Kein Schnittpunkt
*******************************************************************************/
bool Frustum::intersects(Line& line)
   {
   int isInside;

   Vec nv; nv.set(line.m_V); 
   nv.normalize();
   float len = nv.length();
   float t = 0;
   bool NaN;     

   // Intersects gegen die RightPlane:    
   NaN = m_sidePlanes[PLANE_RIGHT].intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vr = line.getEndPoint2(t);
         isInside = 0;
         isInside += OriginPlane.check(vr);       
         isInside += FarPlane.check(vr);
         isInside += m_sidePlanes[PLANE_TOP].check(vr);
         isInside += m_sidePlanes[PLANE_BOTTOM].check(vr);
         if (isInside == 0)
            return (true);
         }
      }
    
   // Intersects gegen die LeftPlane:
   NaN = m_sidePlanes[PLANE_LEFT].intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vl = line.getEndPoint2(t);
         isInside = 0;
         isInside += OriginPlane.check(vl);       
         isInside += FarPlane.check(vl);
         isInside += m_sidePlanes[PLANE_TOP].check(vl);
         isInside += m_sidePlanes[PLANE_BOTTOM].check(vl);
         if (isInside == 0) 
            return (true);
         }
      }
    
   // Intersects gegen die TopPlane:    
   NaN = m_sidePlanes[PLANE_TOP].intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vt = line.getEndPoint2(t);
         isInside = 0;
         isInside += OriginPlane.check(vt);       
         isInside += FarPlane.check(vt);
         isInside += m_sidePlanes[PLANE_LEFT].check(vt);
         isInside += m_sidePlanes[PLANE_RIGHT].check(vt);
         if (isInside == 0) 
            return (true);
         }
      }

   // Intersects gegen die BottomPlane:    
   NaN = m_sidePlanes[PLANE_BOTTOM].intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vb = line.getEndPoint2(t);
         isInside = 0;
         isInside += OriginPlane.check(vb);       
         isInside += FarPlane.check(vb);
         isInside += m_sidePlanes[PLANE_LEFT].check(vb);
         isInside += m_sidePlanes[PLANE_RIGHT].check(vb);
         if (isInside == 0)
            return (true);
         }
      }

   // Intersects gegen die OriginPlane:    
   NaN = OriginPlane.intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vo = line.getEndPoint2(t);
         isInside = 0;
         isInside += m_sidePlanes[PLANE_TOP].check(vo);          
         isInside += m_sidePlanes[PLANE_BOTTOM].check(vo);
         isInside += m_sidePlanes[PLANE_LEFT].check(vo);
         isInside += m_sidePlanes[PLANE_RIGHT].check(vo);
         if (isInside == 0)
            return (true);
         }
      }
    
   // Intersects gegen die FarPlane:    
   NaN = FarPlane.intersect(line, &t);
   if (NaN == true)
      {
      if ((t >= 0) && (t <= len))
         {
         Vec vf = line.getEndPoint2(t);
         isInside = 0;
         isInside += m_sidePlanes[PLANE_TOP].check(vf);
         isInside += m_sidePlanes[PLANE_BOTTOM].check(vf);
         isInside += m_sidePlanes[PLANE_LEFT].check(vf);
         isInside += m_sidePlanes[PLANE_RIGHT].check(vf);
         if (isInside == 0)
            return (true);
         }
      }
    return (false);
 }
 
/******************************************************************************/
/** \brief Obligatorische Dump-Methode fuer Debugzwecke
*******************************************************************************/
void Frustum::dump()
  {
  NearPlane.dump("NearPlane");
  FarPlane.dump("FarPlane");

  if ( m_usedSidePlanes == 4)
     {
     m_sidePlanes[PLANE_TOP].dump("PLANE_TOP");
     m_sidePlanes[PLANE_BOTTOM].dump("PLANE_BOTTOM");
     m_sidePlanes[PLANE_LEFT].dump("PLANE_LEFT");
     m_sidePlanes[PLANE_RIGHT].dump("PLANE_RIGHT");
     }
  else
     { 
     char buf[256];
     for (int i = 0; i < m_usedSidePlanes; i++)
        {
        sprintf(buf, "Plane=%d", i);
        m_sidePlanes[i].dump(buf);
        } 
     }
  }
