#ifdef _WINDOWS
#include <windows.h>
#endif
#include "indexed_polygon.h"

#include "globals.h"
#include "ray.h"
#include "vertexlist.h"


Indexed_Polygon::Indexed_Polygon() {

	cerr << "Error: Indexed_Polygon default constuctor called" << endl;
	exit(-1);
}

Indexed_Polygon::Indexed_Polygon(Vertex *v1, Vertex *v2, Vertex *v3, Vertex *v4) : _v1 (v1), _v2 (v2), _v3 (v3), _v4 (v4), _obj_to_bbox_area (-1.) {

#define V1 (*_v1)
#define V2 (*_v2)
#define V3 (*_v3)
#define V4 (*_v4)
#define V1PT (V1.Point())
#define V2PT (V2.Point())
#define V3PT (V3.Point())
#define V4PT (V4.Point())

        _bounds.SetToPoint (V1PT);
        _bounds.IncludePoint (V2PT);
        _bounds.IncludePoint (V3PT);
        if (v4) _bounds.IncludePoint (V4PT);

	// calculate _normal:

	Vec4 tmp1(V2PT, V1PT);
	Vec4 tmp2(V3PT, V2PT);
	Vec4FastCross3(_normal, tmp1, tmp2);
	_normal.MakeUnit();


	// calculate _d:

	// _v1 is being used as "any point on the plane"
	_d = -V1PT.Dot3(_normal);


	// determine max_norm:

	float mx = _normal.x();
	float my = _normal.y();
	float mz = _normal.z();

	if (mx < 0) mx = -mx;
	if (my < 0) my = -my;
	if (mz < 0) mz = -mz;

	if (mz > my)
		if (mz > mx)
			_max_norm = 2;
		else
			_max_norm = 0;
	else
		if (my > mx)
			_max_norm = 1;
		else
			_max_norm = 0;

}

Indexed_Polygon::~Indexed_Polygon() {

}

Indexed_Polygon& Indexed_Polygon::CopyFrom(const Indexed_Polygon &S) {

	Prim::CopyFrom(S);

	_v1 = S._v1;
	_v2 = S._v2;
	_v3 = S._v3;
	_v4 = S._v4;

	_normal = S._normal;
	_d = S._d;
	_max_norm = S._max_norm;

        _bounds = S._bounds;

	return *this;
}

ostream& operator<<(ostream &co, const Indexed_Polygon&) {

	co << "Indexed_Polygon (no output function yet)" << endl;

	return co;
}

/*
istream& operator>>(istream &ci, Indexed_Polygon &S) {

	return ci;
}
*/

//XXX todo
void Indexed_Polygon::IntersectCertain(const Ray &ray, Hit &hit) {
  IntersectProbable (ray, hit);
}

void Indexed_Polygon::Intersect(const Ray &ray, Hit &hit) {
  //quick-reject
  real bboxt;
  int face, dir;
  int bboxtest;
  bboxtest = _bounds.Intersect(ray, bboxt, face, dir);

  hit.num_polygon_bbox_intersections++;
  if (bboxtest < 0)
    hit.num_bbox_quick_rejects++;
  if (bboxtest <= 0) 
    {
      hit.num_bbox_successes++;
      return;//if it missed our bounding box, leave
    }

  //if it hit our bounding box coming in and that point was further
  //than the best point so far, leave
  if ((bboxtest == 1) && (bboxt > hit.t))
    return;

  IntersectProbable(ray, hit);
}

void Indexed_Polygon::IntersectProbable(const Ray &ray, Hit &hit) {
  hit.num_polygon_intersections++;
  Prim::_intersect_count++;

  RCFloat denom, t;
  RCFloat x, y;
  RCFloat x1, y1, x2, y2, x3, y3, x4, y4;

  if ((denom = _normal.Dot3(ray.D())) &&
      ((t = -(_normal.Dot3(ray.R())+_d)/denom) >= 0)) {

    int sides = (_v4) ? 4 : 3;

    switch (_max_norm) {

    case 0:
      x = ray.R().y()+ray.D().y()*t;
      y = ray.R().z()+ray.D().z()*t;
      x1 = V1PT.y() - x;
      y1 = V1PT.z() - y;
      x2 = V2PT.y() - x;
      y2 = V2PT.z() - y;
      x3 = V3PT.y() - x;
      y3 = V3PT.z() - y;
      if (sides == 4) {
	x4 = V4PT.y() - x;
	y4 = V4PT.z() - y;
      }
      break;

    case 1:
      x = ray.R().x()+ray.D().x()*t;
      y = ray.R().z()+ray.D().z()*t;
      x1 = V1PT.x() - x;
      y1 = V1PT.z() - y;
      x2 = V2PT.x() - x;
      y2 = V2PT.z() - y;
      x3 = V3PT.x() - x;
      y3 = V3PT.z() - y;
      if (sides == 4) {
	x4 = V4PT.x() - x;
	y4 = V4PT.z() - y;
      }
      break;

    case 2:
      x = ray.R().x()+ray.D().x()*t;
      y = ray.R().y()+ray.D().y()*t;
      x1 = V1PT.x() - x;
      y1 = V1PT.y() - y;
      x2 = V2PT.x() - x;
      y2 = V2PT.y() - y;
      x3 = V3PT.x() - x;
      y3 = V3PT.y() - y;
      if (sides == 4) {
	x4 = V4PT.x() - x;
	y4 = V4PT.y() - y;
      }
      break;

    default:
      cerr << "Error, bad _max_norm: " << _max_norm << endl;
      exit(-1);
    }


    int count_up = 0;
    int count_down = 0;
    RCFloat cross;

    cross = (x1 * y2) - (y1 * x2);
    if (cross < 0) count_down = 1;
    else if (cross > 0) count_up = 1;

    cross = (x2 * y3) - (y2 * x3);
    if (cross < 0) {
      if (count_up) return;
      else count_down = 1;
    }
    else if (cross > 0) {
      if (count_down) return;
      else count_up = 1;
    }

    if (sides == 3) {
      cross = (x3 * y1) - (y3 * x1);
      if (cross < 0) {
	if (count_up) return;
	else count_down = 1;
      }
      else if (cross > 0) {
	if (count_down) return;
	else count_up = 1;
      }
    }
    else {
      cross = (x3 * y4) - (y3 * x4);
      if (cross < 0) {
	if (count_up) return;
	else count_down = 1;
      }
      else if (cross > 0) {
	if (count_down) return;
	else count_up = 1;
      }

      cross = (x4 * y1) - (y4 * x1);
      if (cross < 0) {
	if (count_up) return;
	else count_down = 1;
      }
      else if (cross > 0) {
	if (count_down) return;
	else count_up = 1;
      }
    }

    hit.num_intersect_successes++;
    if (t < hit.t)
      {
	hit.t = t;

	// cache texture coords for lookup
	hit.texs = x;
	hit.text = y;
	hit.texcalced = 1;
	hit.normalcalced = 0;
	hit.obj = this;
      }
  }
}

int Indexed_Polygon::IntersectLinespace(const Ray&, const Ray&,
										const Ray&, const Ray&,
										const Vec4&, const Vec4&,
										const Vec4&, const Vec4&) {

	// implement this

	return NO_INTERSECTION;
}

#if 0
int Indexed_Polygon::TestPlaneX(float x) {

	float x1, x2, tmp;

	x1 = x2 = V1PT.x();

	tmp = V2PT.x();
	if (tmp < x1) x1 = tmp;
	else if (tmp > x2) x2 = tmp;

	tmp = V3PT.x();
	if (tmp < x1) x1 = tmp;
	else if (tmp > x2) x2 = tmp;

	if (_v4) {
		tmp = V4PT.x();
		if (tmp < x1) x1 = tmp;
		else if (tmp > x2) x2 = tmp;
	}

	return (x<x1) ? 1 : ((x>x2) ? -1 : 0);
}

int Indexed_Polygon::TestPlaneY(float y) {

	float y1, y2, tmp;

	y1 = y2 = V1PT.y();

	tmp = V2PT.y();
	if (tmp < y1) y1 = tmp;
	else if (tmp > y2) y2 = tmp;

	tmp = V3PT.y();
	if (tmp < y1) y1 = tmp;
	else if (tmp > y2) y2 = tmp;

	if (_v4) {
		tmp = V4PT.y();
		if (tmp < y1) y1 = tmp;
		else if (tmp > y2) y2 = tmp;
	}

	return (y<y1) ? 1 : ((y>y2) ? -1 : 0);
}

int Indexed_Polygon::TestPlaneZ(float y) {

	float y1, y2, tmp;

	y1 = y2 = V1PT.y();

	tmp = V2PT.y();
	if (tmp < y1) y1 = tmp;
	else if (tmp > y2) y2 = tmp;

	tmp = V3PT.y();
	if (tmp < y1) y1 = tmp;
	else if (tmp > y2) y2 = tmp;

	if (_v4) {
		tmp = V4PT.y();
		if (tmp < y1) y1 = tmp;
		else if (tmp > y2) y2 = tmp;
	}

	return (y<y1) ? 1 : ((y>y2) ? -1 : 0);
}
#endif

void Indexed_Polygon::calcST (const Vec4 &pt, float &s, float &t) const
{
  //XXX should work as expected; but should never get called! test.
  //XXX needs to be written
#if 0
        _bounds.SetToPoint (V1PT);
        _bounds.IncludePoint (V2PT);
        _bounds.IncludePoint (V3PT);
        if (_v4) _bounds.IncludePoint (V4PT);
#endif

#if STCACHE
  // SJT use values computed by Intersect()
  s = texS;  t = texT;
#else
  s = t= 0.0;
#endif
}

float Indexed_Polygon::ObjAreaToBboxArea (void)
{
  if (_obj_to_bbox_area < 0.)
    {
      Vec4 boundsdiff = _bounds[1] - _bounds[0];
      float boundsarea = 2.0 * (boundsdiff.x() * boundsdiff.y() + 
	boundsdiff.x() * boundsdiff.z()+ 
	boundsdiff.z() * boundsdiff.y());
      
      float objarea;
      Vec4 v1cross = (V2PT - V1PT).Cross3 (V3PT - V1PT);
      if (_v4)
	{
	  //first make sure the four points are going clockwise.
	  Vec4 v2cross = (V3PT - V2PT).Cross3 (V4PT - V2PT);
	  if (v2cross.Dot3 (v1cross) < 0.)
	    {
	      //no!
	      objarea = ((V2PT - V1PT).Cross3 (V4PT - V1PT)).Length() + ((V4PT - V1PT).Cross3 (V3PT - V1PT)).Length();
	    }
	  else
	    {
	      //yes!
	      objarea = v1cross.Length() + ((V4PT - V1PT).Cross3 (V3PT - V1PT)).Length();
	    }
	  objarea /= 2.;
	}
      else
	{
	  objarea = v1cross.Length() / 2.;
	}
      _obj_to_bbox_area = objarea / boundsarea;
    }
  
  return _obj_to_bbox_area;
}

void Indexed_Polygon::DrawFilled (void) const 
{
  if (_v4)
    {
      glBegin (GL_QUADS);
      glVertex3fv (V1PT.ArrayForGL());
      glVertex3fv (V2PT.ArrayForGL());
      glVertex3fv (V3PT.ArrayForGL());
      glVertex3fv (V4PT.ArrayForGL());
      glEnd();
    }
  else
    {
      glBegin (GL_TRIANGLES);
      glVertex3fv (V1PT.ArrayForGL());
      glVertex3fv (V2PT.ArrayForGL());
      glVertex3fv (V3PT.ArrayForGL());
      glEnd();
    }
}

void Indexed_Polygon::DrawWF (void) const 
{
  glBegin (GL_LINE_LOOP);

  glVertex3fv (V1PT.ArrayForGL());
  glVertex3fv (V2PT.ArrayForGL());
  glVertex3fv (V3PT.ArrayForGL());
  
  if (_v4)
      glVertex3fv (V4PT.ArrayForGL());
  glEnd();
}
