// Filename: eggPolygon.cxx
// Created by:  drose (16Jan99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "eggPolygon.h"
#include "eggGroupNode.h"
#include "plane.h"

#include "indent.h"

#include <algorithm>

TypeHandle EggPolygon::_type_handle;


////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::cleanup
//       Access: Published, Virtual
//  Description: Cleans up modeling errors in whatever context this
//               makes sense.  For instance, for a polygon, this calls
//               remove_doubled_verts(true).  For a point, it calls
//               remove_nonunique_verts().  Returns true if the
//               primitive is valid, or false if it is degenerate.
////////////////////////////////////////////////////////////////////
bool EggPolygon::
cleanup() {
  remove_doubled_verts(true);

  // Use calculate_normal() to determine if the polygon is degenerate.
  Normald normal;
  return calculate_normal(normal);
}

////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::calculate_normal
//       Access: Published
//  Description: Calculates the true polygon normal--the vector
//               pointing out of the front of the polygon--based on
//               the vertices.  This does not return or change the
//               polygon's normal as set via set_normal().
//
//               The return value is true if the normal is computed
//               correctly, or false if the polygon is degenerate and
//               does not have at least three noncollinear vertices.
////////////////////////////////////////////////////////////////////
bool EggPolygon::
calculate_normal(Normald &result, CoordinateSystem cs) const {
  result = Normald::zero();

  // Project the polygon into each of the three major planes and
  // calculate the area of each 2-d projection.  This becomes the
  // polygon normal.  This works because the ratio between these
  // different areas corresponds to the angle at which the polygon is
  // tilted toward each plane.
  size_t num_verts = size();
  for (size_t i = 0; i < num_verts; i++) {
    Vertexd p0 = get_vertex(i)->get_pos3();
    Vertexd p1 = get_vertex((i + 1) % num_verts)->get_pos3();
    result[0] += p0[1] * p1[2] - p0[2] * p1[1];
    result[1] += p0[2] * p1[0] - p0[0] * p1[2];
    result[2] += p0[0] * p1[1] - p0[1] * p1[0];
  }

  if (!result.normalize()) {
    // The polygon is degenerate: it has zero area in each plane.
    return false;
  }

  if (cs == CS_default) {
    cs = get_default_coordinate_system();
  }
  if (cs == CS_zup_left || cs == CS_yup_left) {
    // In a left-handed coordinate system, we must flip the result.
    result = -result;
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::is_planar
//       Access: Published
//  Description: Returns true if all of the polygon's vertices lie
//               within the same plane, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggPolygon::
is_planar() const {
  if (size() <= 3) {
    // If we don't have more than three vertices, we can't be
    // non-planar.
    return true;
  }

  Normald normal;
  if (!calculate_normal(normal)) {
    // A degenerate polygon--all of the vertices are within one line,
    // or all in the same point--is technically planar.  Not sure if
    // this is a useful return value or not.
    return true;
  }

  // There should be at least one vertex (actually, at least three)
  // since we have already shown that the polygon is nondegenerate.
  nassertr(!empty(), false);

  // Create a plane perpendicular to the polygon's normal, containing
  // the first vertex.
  const_iterator vi = begin();
  LVecBase3d first_point = (*vi)->get_pos3();
  Planed plane(normal, first_point);

  // And check that all of the remaining vertices are sufficiently
  // close to the plane.
  ++vi;
  while (vi != end()) {
    LVecBase3d this_point = (*vi)->get_pos3();
    if (!this_point.almost_equal(first_point)) {
      double dist = plane.dist_to_plane(this_point);
      double tol = dist / length(this_point - first_point);
      if (!IS_THRESHOLD_ZERO(tol, 0.0001)) {
        // Nope, too far away--the polygon is nonplanar.
        return false;
      }
    }
    ++vi;
  }

  // All vertices are close enough to pass.
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::triangulate_in_place
//       Access: Published
//  Description: Subdivides the polygon into triangles and adds those
//               triangles to the parent group node in place of the
//               original polygon.  Returns a pointer to the original
//               polygon, which is likely about to be destructed.
//
//               If convex_also is true, both concave and convex
//               polygons will be subdivided into triangles;
//               otherwise, only concave polygons will be subdivided,
//               and convex polygons will be copied unchanged into the
//               container.
////////////////////////////////////////////////////////////////////
PT(EggPolygon) EggPolygon::
triangulate_in_place(bool convex_also) {
  EggGroupNode *parent = get_parent();
  nassertr(parent != (EggGroupNode *)NULL, this);

  PT(EggPolygon) save_me = this;
  parent->remove_child(this);
  triangulate_poly(parent, convex_also);

  return save_me;
}

////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::write
//       Access: Published, Virtual
//  Description: Writes the polygon to the indicated output stream in
//               Egg format.
////////////////////////////////////////////////////////////////////
void EggPolygon::
write(ostream &out, int indent_level) const {
  write_header(out, indent_level, "<Polygon>");
  write_body(out, indent_level+2);
  indent(out, indent_level) << "}\n";
}

////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::decomp_concave
//       Access: Private
//  Description: Decomposes a concave polygon into triangles.  Returns
//               true if successful, false if the polygon is
//               self-intersecting.
////////////////////////////////////////////////////////////////////
bool EggPolygon::
decomp_concave(EggGroupNode *container, int asum, int x, int y) const {
#define VX(p, c)    p->coord[c]

  struct DecompVtx {
    int index;
    LPoint3d coord;
    struct DecompVtx *next;
  };

  DecompVtx *p0, *p1, *p2, *t0, *vert;
  DecompVtx *m[3];
  double xmin, xmax, ymin, ymax;
  int i, init, csum, chek;
  double a[3], b[3], c[3], s[3];

  int num_verts = size();
  nassertr(num_verts >= 3, false);

  /* Make linked list of verts */
  vert = (DecompVtx *) alloca(sizeof(DecompVtx));
  vert->index = 0;
  vert->coord = get_vertex(0)->get_pos3();
  p1 = vert;

  for (i = 1; i < num_verts; i++) {
    p0 = (DecompVtx *) alloca(sizeof(DecompVtx));
    p0->index = i;
    p0->coord = get_vertex(i)->get_pos3();
    // There shouldn't be two consecutive identical vertices.  If
    // there are, skip one.
    if (!(p0->coord == p1->coord)) {
      p1->next = p0;
      p1 = p0;
    }
  }
  p1->next = vert;

  p0 = vert;
  p1 = p0->next;
  p2 = p1->next;
  m[0] = p0;
  m[1] = p1;
  m[2] = p2;
  chek = 0;

  while (p0 != p2->next) {
    /* Polygon is self-intersecting so punt */
    if (chek &&
        m[0] == p0 &&
        m[1] == p1 &&
        m[2] == p2) {

      return false;
    }

    chek = 1;

    a[0] = VX(p1, y) - VX(p2, y);
    b[0] = VX(p2, x) - VX(p1, x);
    a[2] = VX(p0, y) - VX(p1, y);
    b[2] = VX(p1, x) - VX(p0, x);

    csum = ((b[0] * a[2] - b[2] * a[0] >= 0.0) ? 1 : 0);

    if (csum ^ asum) {
      /* current angle is concave */
      p0 = p1;
      p1 = p2;
      p2 = p2->next;

    } else {
      /* current angle is convex */
      xmin = (VX(p0, x) < VX(p1, x)) ? VX(p0, x) : VX(p1, x);
      if (xmin > VX(p2, x))
        xmin = VX(p2, x);

      xmax = (VX(p0, x) > VX(p1, x)) ? VX(p0, x) : VX(p1, x);
      if (xmax < VX(p2, x))
        xmax = VX(p2, x);

      ymin = (VX(p0, y) < VX(p1, y)) ? VX(p0, y) : VX(p1, y);
      if (ymin > VX(p2, y))
        ymin = VX(p2, y);

      ymax = (VX(p0, y) > VX(p1, y)) ? VX(p0, y) : VX(p1, y);
      if (ymax < VX(p2, y))
        ymax = VX(p2, y);

      for (init = 1, t0 = p2->next; t0 != p0; t0 = t0->next) {
        if (VX(t0, x) >= xmin && VX(t0, x) <= xmax &&
            VX(t0, y) >= ymin && VX(t0, y) <= ymax) {
          if (init) {
            a[1] = VX(p2, y) - VX(p0, y);
            b[1] = VX(p0, x) - VX(p2, x);
            init = 0;
            c[0] = VX(p1, x) * VX(p2, y) - VX(p2, x) * VX(p1, y);
            c[1] = VX(p2, x) * VX(p0, y) - VX(p0, x) * VX(p2, y);
            c[2] = VX(p0, x) * VX(p1, y) - VX(p1, x) * VX(p0, y);
          }

          s[0] = a[0] * VX(t0, x) + b[0] * VX(t0, y) + c[0];
          s[1] = a[1] * VX(t0, x) + b[1] * VX(t0, y) + c[1];
          s[2] = a[2] * VX(t0, x) + b[2] * VX(t0, y) + c[2];

          if (asum) {
            if (s[0] >= 0.0 && s[1] >= 0.0 && s[2] >= 0.0)
              break;
          } else {
            if (s[0] <= 0.0 && s[1] <= 0.0 && s[2] <= 0.0)
              break;
          }
        }
      }

      if (t0 != p0) {
        p0 = p1;
        p1 = p2;
        p2 = p2->next;
      } else {

        // Here's a triangle to output.
        PT(EggPolygon) triangle = new EggPolygon(*this);
        triangle->clear();
        triangle->add_vertex(get_vertex(p0->index));
        triangle->add_vertex(get_vertex(p1->index));
        triangle->add_vertex(get_vertex(p2->index));

        if (triangle->cleanup()) {
          container->add_child(triangle.p());
        }

        p0->next = p1->next;
        p1 = p2;
        p2 = p2->next;

        m[0] = p0;
        m[1] = p1;
        m[2] = p2;
        chek = 0;
      }
    }
  }

  // One more triangle to output.
  PT(EggPolygon) triangle = new EggPolygon(*this);
  triangle->clear();
  triangle->add_vertex(get_vertex(p0->index));
  triangle->add_vertex(get_vertex(p1->index));
  triangle->add_vertex(get_vertex(p2->index));

  if (triangle->cleanup()) {
    container->add_child(triangle.p());
  }

  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: EggPolygon::triangulate_poly
//       Access: Private
//  Description: Breaks a (possibly concave) higher-order polygon into
//               a series of constituent triangles.  Fills the
//               container up with EggPolygons that represent the
//               triangles.  Returns true if successful, false on
//               failure.
//
//               If convex_also is true, both concave and convex
//               polygons will be subdivided into triangles;
//               otherwise, only concave polygons will be subdivided,
//               and convex polygons will be copied unchanged into the
//               container.
//
//               It is assumed that the EggPolygon is not already a
//               child of any other group when this function is
//               called.
////////////////////////////////////////////////////////////////////
bool EggPolygon::
triangulate_poly(EggGroupNode *container, bool convex_also) {
  LPoint3d p0, p1, as;
  double dx1, dy1, dx2, dy2, max;
  int i, flag, asum, csum, index, x, y, v0, v1, v, even;

  if (!cleanup()) {
    return false;
  }

  // First see if the polygon is already a triangle
  int num_verts = size();
  if (num_verts == 3) {
    container->add_child(this);

    return true;

  } else if (num_verts < 3) {
    // Or if it's a degenerate polygon.
    return false;
  }

  // calculate signed areas
  as[0] = 0.0;
  as[1] = 0.0;
  as[2] = 0.0;

  for (i = 0; i < num_verts; i++) {
    p0 = get_vertex(i)->get_pos3();
    p1 = get_vertex((i + 1) % num_verts)->get_pos3();
    as[0] += p0[0] * p1[1] - p0[1] * p1[0];
    as[1] += p0[0] * p1[2] - p0[2] * p1[0];
    as[2] += p0[1] * p1[2] - p0[2] * p1[1];
  }

  /* select largest signed area */
  max = 0.0;
  index = 0;
  flag = 0;
  for (i = 0; i < 3; i++) {
    if (as[i] >= 0.0) {
      if (as[i] > max) {
        max = as[i];
        index = i;
        flag = 1;
      }
    } else {
      as[i] = -as[i];
      if (as[i] > max) {
        max = as[i];
        index = i;
        flag = 0;
      }
    }
  }

  /* pointer offsets */
  switch (index) {
  case 0:
    x = 0;
    y = 1;
    break;

  case 1:
    x = 0;
    y = 2;
    break;

  default: // case 2
    x = 1;
    y = 2;
    break;
  }

  /* concave check */
  p0 = get_vertex(0)->get_pos3();
  p1 = get_vertex(1)->get_pos3();
  dx1 = p1[x] - p0[x];
  dy1 = p1[y] - p0[y];
  p0 = p1;
  p1 = get_vertex(2)->get_pos3();

  dx2 = p1[x] - p0[x];
  dy2 = p1[y] - p0[y];
  asum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0);

  for (i = 0; i < num_verts - 1; i++) {
    p0 = p1;
    p1 = get_vertex((i+3) % num_verts)->get_pos3();

    dx1 = dx2;
    dy1 = dy2;
    dx2 = p1[x] - p0[x];
    dy2 = p1[y] - p0[y];
    csum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0);

    if (csum ^ asum) {
      // It's a concave polygon.  This is a little harder.
      return decomp_concave(container, flag, x, y);
    }
  }

  // It's a convex polygon.
  if (!convex_also) {
    // Make sure that it's also coplanar.  If it's not, we should
    // triangulate it anyway.
    if (is_planar()) {
      container->add_child(this);
      return true;
    }
  }

  v0 = 0;
  v1 = 1;
  v = num_verts - 1;

  even = 1;

  /*
   * Convert to triangles only. Do not fan out from a single vertex
   * but zigzag into triangle strip.
   */
  for (i = 0; i < num_verts - 2; i++) {
    if (even) {
      PT(EggPolygon) triangle = new EggPolygon(*this);
      triangle->clear();
      triangle->add_vertex(get_vertex(v0));
      triangle->add_vertex(get_vertex(v1));
      triangle->add_vertex(get_vertex(v));

      if (triangle->cleanup()) {
        container->add_child(triangle.p());
      }

      v0 = v1;
      v1 = v;
      v = v0 + 1;
    } else {
      PT(EggPolygon) triangle = new EggPolygon(*this);
      triangle->clear();
      triangle->add_vertex(get_vertex(v1));
      triangle->add_vertex(get_vertex(v0));
      triangle->add_vertex(get_vertex(v));

      if (triangle->cleanup()) {
        container->add_child(triangle.p());
      }

      v0 = v1;
      v1 = v;
      v = v0 - 1;
    }

    even = !even;
  }

  return true;
}
