/*
  Maze editor: 3D model class
  Copyright (C) 1998 by Jorrit Tyberghein
  Written by Andrew Zabolotny <bit@eltech.ru>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <limits.h>
#include "sysdef.h"
#include "me_model.h"
#include "me_text.h"
#include "csutil/util.h"
#include "cssys/common/system.h"
#include "csobject/nameobj.h"

//--------------------------------------------------// 3D polygon class //----//

mz3DPolygon::mz3DPolygon (mz3DModel *iParent) : V (8, 8)
{
  parent = iParent;
  Color = -1;
  selected = false;
  Name = NULL;
  Texture = NULL;
  SpaceWarp =NULL;
  portal = NULL;
  alpha = 0;
  mirror = false;
  // Generate a name for current polygon
  char tmp [16];
  sprintf (tmp, "%s_p%d", iParent->Name, iParent->Polygons()+1);
  SetName (tmp);
  csNameObject::AddName(*this, tmp);

}

mz3DPolygon::mz3DPolygon (mz3DPolygon &iCopy) : V (8, 8)
{
  parent = iCopy.parent;
  Color = iCopy.Color;
  selected = iCopy.selected;
  alpha = iCopy.alpha;
  mirror = iCopy.mirror;
  Name = NULL;
  Texture = NULL;
  SpaceWarp = NULL;

  // Generate a name for current polygon
  char tmp [16];
  sprintf (tmp, "%s_cp1", iCopy.Name);
  SetName (tmp);
  csNameObject::AddName(*this, tmp);

  for (int i = 0; i < iCopy.Vertices (); i++)
    V [i] = iCopy.V [i];
}

mz3DPolygon::~mz3DPolygon ()
{
  if (Name)
    delete [] Name;

}

void mz3DPolygon::SetName (const char *iName)
{
  if (Name)
    delete [] Name;
  Name = strnew (iName);
}

bool mz3DPolygon::NotifyVertexDeleted (int iVertex)
{
  for (int i = 0; i < Vertices (); i++)
    if ((*this) [i] > iVertex)
      (*this) [i]--;
    else if ((*this) [i] == iVertex)
      return false;
  return true;
}

bool mz3DPolygon::operator == (const mz3DPolygon &p) const
{
  if (p.Vertices () != Vertices ())
    return false;
  int i, v1 = 0, v2 = 0, m1 = INT_MAX, m2 = INT_MAX;
  int s = Vertices ();
  mz3DPolygon &thisv = (mz3DPolygon &)(*this);
  mz3DPolygon &pv = (mz3DPolygon &)p;
  for (i = 0; i < s; i++)
  {
    if (thisv [i] < m1)
    {
      m1 = thisv [i];
      v1 = i;
    } /* endif */
    if (pv [i] < m2)
    {
      m2 = pv [i];
      v2 = i;
    } /* endif */
  } /* endfor */

  for (i = 0; i < s; i++)
    if (thisv [(v1 + i) % s] != pv [(v2 + i) % s])
      return false;
  return true;
}

csPolyError mz3DPolygon::Check ()
{
  if (Vertices () < 3)
    return cspeUnderflow;
  if (Vertices () > MAX_POLYGON_VERTICES)
    return cspeOverflow;

  // Check if some vertex is mentioned twice
  int i, j;
  for (i = 0; i < Vertices (); i++)
    for (j = i + 1; j < Vertices (); j++)
    {
      if ((*this) [i] == (*this) [j])
        return cspeDuplicateVertex;
    } /* endfor */

  // Check if we have a valid parent model
  if (!parent)
    return cspeNoParent;

  // Check if all vertices lies on the same plane
  mz3DVertex v1 = Vertex (1) - Vertex (0);
  mz3DVertex v2 = Vertex (2) - Vertex (0);
  float epsilon1 = EPSILON, epsilon2 = -EPSILON;
  for (i = 3; i < Vertices (); i++)
  {
    mz3DVertex v3 = Vertex (i) - Vertex (0);
    float val = v1.x * v2.y * v3.z
              + v1.y * v2.z * v3.x
              + v1.z * v2.x * v3.y
              - v1.x * v2.z * v3.y
              - v1.z * v2.y * v3.x
              - v1.y * v2.x * v3.z;
    if ((val > epsilon1)
     || (val < epsilon2))
      return cspeNonCoplanar;
    v1 = v2;
    v2 = v3;
  } /* endfor */

  // And finally, check if polygon is convex
  // For this, check for all polygon edges that all vertices lies
  // on one side of the plane that is perpendicular to polygon's plane
  // and crosses it through the line that contains that edge
  for (i = 0; i < Vertices (); i++)
  {
    // First compute the plane that is perpendicular to polygon plane
    // and crosses it through the line that contains current edge
    int v = i, vn = (i + 1) % Vertices (), vp = (i + Vertices () - 1) % Vertices ();
    csVector3 pp = Vertex (v) + ((Vertex (vp) - Vertex (v))
                             % (Vertex (vn) - Vertex (v)));
    csVector3 normal;
    float D;

    // Normalize pp so that we will avoid precision lose
    if (pp.x > 0) pp.x = sqrt (pp.x); else pp.x = -sqrt (-pp.x);
    if (pp.y > 0) pp.y = sqrt (pp.y); else pp.y = -sqrt (-pp.y);
    if (pp.z > 0) pp.z = sqrt (pp.z); else pp.z = -sqrt (-pp.z);

    csMath3::CalcPlane (Vertex (v), Vertex (vn), pp, normal, D);
    csPlane plane (normal, D);

    // Compute maximum allowable bias
    epsilon1 = fabs (plane.Classify (Vertex (v )));
    epsilon2 = fabs (plane.Classify (Vertex (vn)));
    if (epsilon1 > epsilon2)
      epsilon2 = -epsilon1;
    else
      epsilon1 = -epsilon2;

    // Now look if all polygon vertices are on one side of plane
    // defined by three vectors e1, e2 and e3 (or normal n)
    bool oneside = false, otherside = false;
    for (j = 0; j < Vertices (); j++)
    {
      if ((j == v) || (j == vn))
        continue;
      float c = plane.Classify (Vertex (j));
      if (c > epsilon1)
        oneside = true;
      if (c < epsilon2)
        otherside = true;
    } /* endfor */
    if (oneside && otherside)
      return cspeNonConvex;
  } /* endfor */

  // Everything looks okay
  return cspeOK;
}

mz3DVertex& mz3DPolygon::Vertex (int iVertex)
{
  return parent->Vertex ((*this) [iVertex]);
}

char *mz3DPolygon::ErrorText (csPolyError iCode)
{
  switch (iCode)
  {
    case cspeUnderflow:
      return TEXT_POLYERRUNDERFLOW;
    case cspeOverflow:
      return TEXT_POLYERROVERFLOW;
    case cspeDuplicateVertex:
      return TEXT_POLYERRDUPVERTEX;
    case cspeNoParent:
      return TEXT_POLYERRNOPARENT;
    case cspeNonCoplanar:
      return TEXT_POLYERRNONCOPLANAR;
    case cspeNonConvex:
      return TEXT_POLYERRNONCONVEX;
    default:
      return NULL;
  } /* endswitch */
}

void mz3DPolygon::Reverse ()
{
  for (int i = Vertices () / 2; i > 0; i--)
  {
    int idx = (*this) [i - 1];
    (*this) [i - 1] = (*this) [Vertices () - i];
    (*this) [Vertices () - i] = idx;
  } /* endif */
}
