/* Model3D.java
 * Author: David Wanqian Liu
 * Date: Dec 20, 1995
 */


import java.awt.Color;
import java.awt.Graphics;


/** Abstract class used to represent a 3D model.
 */
abstract class Model3D {
  /** vertices contains a COPY of original vertices, this is used to
      avoid object distortion caused by matrix transformation.
      tvertices points to all transformable vertices.
   */
  Vertex   vertices[];
  Vertex   tvertices[];
  int      nvertices;

  /** edges are defined by reference to tvertices.
   */
  Edge     edges[];
  int      nedges;

  /** faces are defined by reference to tvertices.
   */
  Face     faces[];
  int      nfaces;
  Matrix3D tmatrix = new Matrix3D();


  /** Create a 3D model. We generally use polygon meshes to represent
      a 3D model. Other representation, though, can be converted into
      a polygon mesh 3D model if necessary.
   */
  public Model3D() {
    vertices = null;
    tvertices = null;
    edges = null;
    faces = null;
    nvertices = 0;
    nedges = 0;
    nfaces = 0;
    tmatrix.unit();
  }


  /** Transform all the vertices in the model using 3D matrix mat,
      and put result into tvertices. vertices is a "permenant" copy
      of the model, which is used to avoid model distortion caused
      by inaccuracy of floating point calculation.
   */
  void transform(Matrix3D mat) {
    if (nvertices <= 0) return;

    /* Apply mat to the model transformation matrix tmat */
    tmatrix.mult(mat);
    tmatrix.transform(vertices, tvertices, nvertices);
  }



  /** Sort all the faces according to their z depth.
      This can be used in "depth-sort algorithm" or "painter's algorithm".
   */
  void sortFaces() {
    if (nfaces < 1) return;

    /* Calculate z depth of faces */
    for (int i=0; i<nfaces; i++)
      faces[i].calcParam();

    qsort(0, nfaces-1);
  }


  /* Array of X and Y points used to fill faces */
  int vx[] = new int[64];
  int vy[] = new int[64];

  public synchronized void paint(Graphics g) {
    if (nvertices <= 0)
      return;

    /* Draw edges */
    for (int i=0; i<nedges; i++) {
      g.setColor(edges[i].color);
      g.drawLine((int)edges[i].v1.x, (int)edges[i].v1.y,
		           (int)edges[i].v2.x, (int)edges[i].v2.y);
    }

    /* Draw faces. Simple "painter's algorithm" is used to paint faces.
       This algorith can only handle "2+1/2D" model correctly. It can also
       be applied to a scene in which each polygon is not embedded in a
       plane of constant z by sorting the polygons by there minimum z
       depth or z coordinate of their centroid. Although scenes can be
       constructed for which this approach works, it does not in general
       produce a correct ordering.
     */
    sortFaces();

    for (int i=0; i<nfaces; i++) {
      for (int j=0; j<faces[i].nvertices; j++) {
	vx[j] = (int) faces[i].vertices[j].x;
	vy[j] = (int) faces[i].vertices[j].y;
      }

      g.setColor(faces[i].color);
      g.fillPolygon(vx, vy, faces[i].nvertices);
      
      // g.setColor(Color.black);
      // g.drawPolygon(vx, vy, faces[i].nvertices);
    }
  }


  /** Given the bounding box of this model (0,0) and (width, height),
      scale the model to the size of the bounding box,
      and return the origin of the model */
  Vertex defineBB(int width, int height) {
    if (nvertices <= 0)
      return new Vertex(0.0f, 0.0f, 0.0f);

    /* Calculate the dimension of the model */
    float xmin = vertices[0].x, xmax = xmin;
    float ymin = vertices[0].y, ymax = ymin;
    float zmin = vertices[0].z, zmax = zmin;

    for (int i=1; i<nvertices; i++) {
      xmin = Math.min(xmin, vertices[i].x);
      ymin = Math.min(ymin, vertices[i].y);
      zmin = Math.min(zmin, vertices[i].z);
      xmax = Math.max(xmax, vertices[i].x);
      ymax = Math.max(ymax, vertices[i].y);
      zmax = Math.max(zmax, vertices[i].z);
    }


    /* Calculate the scaling ratio */
    float ratio = 0.7f * Math.min(width, height) /
      Math.max(Math.max(xmax - xmin, ymax - ymin), zmax - zmin);

    Matrix3D tmat = new Matrix3D();
    tmat.unit();
    tmat.translate(-(xmin + xmax) / 2,
                   -(ymin + ymax) / 2,
		   -(zmin + zmax) / 2);
    /* Scale the model by ratio, y axis is reversed */
    tmat.scale(ratio, -ratio, ratio);
    tmat.translate(width / 2.0f, height / 2.0f, 0.0f);

    transform(tmat);

    return new Vertex(width / 2.0f, height / 2.0f, 0.0f);
  }


  /* Quick sort for face sorting algorithm */
  private void qsort(int left, int right) {
    int	i, j;
    int mid;
    Face  tmp;

    i = left; j = right;
    mid = (left+right)/2;

    do {

      while (faces[mid].notObscuredBy(faces[i]) && i < right) i++;
      while (faces[j].notObscuredBy(faces[mid]) && j > left) j--;

      /* switch faces[i] and faces[j] */
      if (i <= j) {
	tmp = faces[i];
	faces[i] = faces[j];
	faces[j] = tmp;
	i++; j--;
      }

    } while (i <= j);

    if (left < j) qsort(left, j);
    if (i < right) qsort(i, right);
  }


}
