/*
 * Plane.C - methods for plane manipulations.
 *
 * Copyright (C) 1992, Christoph Streit (streit@iam.unibe.ch)
 *                     University of Berne, Switzerland
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

#include "Plane.h"

//___________________________________________________________ Plane

Plane::Plane(const Vector& position, const Vector& normal)
:  pos(position), n(normal)
{
  if (n.normalize() == 0)
  n.normalize();
  D = pos^n;
}

GeoObject* Plane::create(const Vector& position, const Vector& normal)
{
  if (equal(normal.sqr(), 0))
    return NULL;

  return new Plane(position, normal);
}

/*
 * intersect plane with ray.
 */

int Plane::intersect(const Ray& ray, real minDist, real& maxDist)
{
  intersectionTests++;

  real t = n^ray.getDir();
  if (fabs(t) < minDist)
    return FALSE;

  t = (D - (ray.getOrig()^n))/t;
  
  if (t > minDist && t < maxDist) {
    maxDist = t;
    intersections++;
    return TRUE;
  }
  return FALSE;
}

/*
 * Compute plane normal
 */

Vector Plane::normal(const Vector&) const
{
  return n;
}

/*
 * tesselate splits a plane into a polygon (I know it's impossible).
 * It is done by intersecting the plane with the bounding volume. The 
 * resulting polygon is the visible part of the plane within the bounding
 * volume.
 */

PolygonList* Plane::tesselate(const BoundingBox& bbox)
{
  int i;
  BoundingBox tbbox(bbox);
  
  if (trans != NULL) 
    tbbox = bbox.transform(*itrans);

  /*
   * compute the points on the bounding volume
   */
  Vector bottom[4];
  bottom[0] = Vector(tbbox.xmin(), tbbox.ymin(), tbbox.zmin());
  bottom[1] = Vector(tbbox.xmax(), tbbox.ymin(), tbbox.zmin());
  bottom[2] = Vector(tbbox.xmax(), tbbox.ymax(), tbbox.zmin());
  bottom[3] = Vector(tbbox.xmin(), tbbox.ymax(), tbbox.zmin());

  Vector top[4];
  top[0] = Vector(tbbox.xmin(), tbbox.ymin(), tbbox.zmax());
  top[1] = Vector(tbbox.xmax(), tbbox.ymin(), tbbox.zmax());
  top[2] = Vector(tbbox.xmax(), tbbox.ymax(), tbbox.zmax());
  top[3] = Vector(tbbox.xmin(), tbbox.ymax(), tbbox.zmax());
  
  Polygon* p = new Polygon(4);
  PolygonList* polys = NULL;
  Vector iPoint;
  int pointsOnPlane;

  for (i=0; i<4; i++) {
    if (pointsOnPlane=intersectPlaneWithSegment(bottom[i], top[i], iPoint)) {
      if (pointsOnPlane == 2) {
        p->addVertex(bottom[i]); 
        p->addVertex(top[i]);
      }
      else
        p->addVertex(iPoint);
    }
  }
  for (i=0; i<4 && p->numVertices() < 4; i++) {
    if (pointsOnPlane=intersectPlaneWithSegment(top[i], top[(i+1)%4], iPoint)) {
      if (pointsOnPlane == 2) {
        p->addVertex(top[i]); 
        p->addVertex(top[(i+1)%4]);
      }
      else
        p->addVertex(iPoint);
    }
  }
  for (i=0; i<4 && p->numVertices() < 4; i++) {
    if (pointsOnPlane=intersectPlaneWithSegment(bottom[i], bottom[(i+1)%4], iPoint)) {
      if (pointsOnPlane == 2) {
        p->addVertex(bottom[i]); 
        p->addVertex(bottom[(i+1)%4]);
      }
      else
        p->addVertex(iPoint);
    }
  }

  if (p->numVertices() < 3) {
    delete p; 
    p = NULL;
  }
  else if (p->numVertices() == 4) {
    Vector p1 = p->vertex(0), p2 = p->vertex(1),
           p3 = p->vertex(2), p4 = p->vertex(3);

    /*
     * check if any two edges intersects 
     *  -> if yes, swap the corresponding points
     */
    if (!equal(((p2-p1)*(p4-p3)).sqr(),0)) {
      Polygon* swapedP = new Polygon(p1, p2, p4, p3);
      delete p;
      p = swapedP;
    }
    else if (!equal(((p4-p1)*(p2-p3)).sqr(),0)) {
      Polygon* swapedP = new Polygon(p2, p1, p3, p4);
      delete p;
      p = swapedP;
    }
  }

  if (p != NULL) {
    if (trans != NULL)
      p->transform(*trans);
    polys = new PolygonList(1);
    polys->append(p);
  }
  
  return polys;
}


/*
 * Intersect segment p1,p2 with the plane. The return values are
 * 0 (no intersection), 1 (one intersection) or 2 (segment is on the plane).
 */

int Plane::intersectPlaneWithSegment(const Vector& p1, const Vector& p2, Vector& h)
{
  /*
   * calculate distance to plane for points p1, p2
   */
  real d1 = (p1 - pos)^n; 
  real d2 = (p2 - pos)^n;

  /* 
   * Test if one (both) endpoint(s) of the segment is one the plane.
   */
  if (equal(d1, 0)) {
    if (equal(d2,0))
      return 2;
    else {
      h = p1;
      return 1;
    }
  } 
  else if (equal(d2, 0)) {
    h = p2;
    return 1;
  }

  /*
   * If the point p1 and p2 are on the same side of the plane,
   * no intersection is possible.
   */
  if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
    return 0;

  Vector dir = p2-p1; dir.normalize();
  real t = n^dir;

  h = p1 + ((D-(p1^n))/t)*dir;

  return 1;
}


