import java.applet.Applet;
import java.awt.Image;
import java.awt.image.IndexColorModel;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;
import java.lang.*;
import java.io.StreamTokenizer;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;

class FileFormatException extends Exception {
  public FileFormatException (String s) {
    super(s);
  }
}

class Face {
  public int nverts;
  public int index[];
  public int cr, cg, cb;
  public int zdepth;

  Face () {
    nverts = 0;
    index = null;
    cr = 255; cg = 255; cb = 255;
  }

  Face (int nv) {
    nverts = nv;
    index = new int[nv];
    cr = 255; cg = 255; cb = 255;
  }

  public void numVerts(int nv) {
    nverts = nv;
    index = new int[nv];
  }
}

/* A class for parsing and storing OOGL OFF 3D models */

public class OOGL_OFF {
  float vert[];
  Face face[];
  int findex[];
  boolean transformed, gotsection;
  Matrix3D mat;
  public float xmin, xmax, ymin, ymax, zmin, zmax;
  int nverts, nfaces, nedges, num, vx[], vy[], tvert[];
  static Color gr[];
  
  OOGL_OFF () {
    mat = new Matrix3D();
    vx = new int[1000];
    vy = new int[1000];
    mat.xrot(0);
    mat.yrot(0);
    nverts = 0; nedges = 0; nfaces = 0; vert = null; gr = null;
  }

  OOGL_OFF (URL loc) {
    this();
    try {
      readObject(loc.openStream());
    } catch (Exception e) {
    }
  }
  
  OOGL_OFF (InputStream is) {
    this();
    try {
      readObject(is);
    } catch (Exception e) {
    }
  }

  void readObject(InputStream is) throws IOException, FileFormatException {
    StreamTokenizer stream = new StreamTokenizer (is);
    stream.eolIsSignificant(true);
    stream.commentChar('#');
    gotsection= false;

scanhead:
    while (!gotsection) {
      switch (stream.nextToken()) {
      default:
	break scanhead;
      case StreamTokenizer.TT_EOL:
	break;
      case StreamTokenizer.TT_WORD:
	if ("OFF".equals(stream.sval)) {
	  nverts = 0; nfaces = 0; nedges = 0;
	  while (stream.nextToken() == StreamTokenizer.TT_EOL) {};
	  if (stream.ttype == StreamTokenizer.TT_NUMBER) {
	    nverts = (int)stream.nval;
	    if (stream.nextToken() == StreamTokenizer.TT_NUMBER) {
	      nfaces = (int)stream.nval;
	      if (stream.nextToken() == StreamTokenizer.TT_NUMBER) {
		nedges = (int)stream.nval;
		gotsection = true;
	      } else throw new FileFormatException("Bad OFF file");
	    } else throw new FileFormatException("Bad OFF file");
	  } else throw new FileFormatException("Bad OFF file");
	}
	break;
      case StreamTokenizer.TT_NUMBER:
	break;
      }
    }
    vert = new float[nverts * 3];
    face = new Face[nfaces]; findex = new int[nfaces];
    for (int i = 0; i < nfaces; i++) { findex[i] = i; }
    num = 0; 
    int coordnum = 0;

scanverts:
    while (num < nverts) {
      switch (stream.nextToken()) {
        default:
	  break;
	case StreamTokenizer.TT_EOL:
	  if (coordnum > 2) {
	    coordnum = 0; num++;
	  }
	  break;
	case StreamTokenizer.TT_NUMBER:
	  if (coordnum < 3) {
	    vert[num*3 + coordnum] = (float)stream.nval;
	    coordnum++;
	  }
	}
    }

    num = 0; coordnum = 0;
    boolean gotnum = false;
scanfaces:
    while (num < nfaces) {
      switch (stream.nextToken()) {
        default:
	  break;
	case StreamTokenizer.TT_EOL:
	  if (gotnum) { num++; }
	  gotnum = false;
	  break;
	case StreamTokenizer.TT_NUMBER:
	  if (!gotnum) {
	    face[num] = new Face();
	    face[num].numVerts((int)stream.nval);
	    gotnum = true; coordnum = 0;
	  } else if (coordnum < face[num].nverts) {
	    face[num].index[coordnum] = 3 * (int)stream.nval;
	    coordnum++;
	  } else {
	    face[num].cr = 255; face[num].cg = 255; face[num].cb = 255;
	    float val = (float)stream.nval;
	    if (val <= 1) { val *= 255; }
	    face[num].cr = (int)val;
	    if (stream.nextToken() != StreamTokenizer.TT_EOL) {
	      val = (float)stream.nval;
	      if (val <= 1) { val *= 255; }
	      face[num].cg = (int)val;
	      if (stream.nextToken() != StreamTokenizer.TT_EOL) {
		val = (float)stream.nval;
	        if (val <= 1) { val *= 255; }
		face[num].cb = (int)val;
	      } else {
		face[num].cr = 255; face[num].cg = 255; face[num].cb = 255;
		num++; gotnum = false;
	      }
	    } else {
		face[num].cr = 255; face[num].cg = 255; face[num].cb = 255;
		num++; gotnum = false;
	      }
	  }
	  break;
	}
    }

  }

 /* Transform all points in model */
 void transform() {
   if (transformed || nverts <= 0)
     return;
   if (tvert == null)
     tvert = new int[nverts*3];
   mat.transform(vert, tvert, nverts);
   transformed = true;
 }

  void qs(int left, int right) {
    int i,j;
    int x,y;

    i = left; j = right;
    x = face[findex[(left+right)/2]].zdepth;

    do {
      while (face[findex[i]].zdepth > x && i < right) i++;
      while (x > face[findex[j]].zdepth && j > left) j--;

      if (i <= j) {
	y = findex[i];
	findex[i] = findex[j];
	findex[j] = y;
	i++; j--;
      }
    } while (i <= j);

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

  void paint(Graphics g) {
    if (vert == null || nverts <= 0)
      return;
    transform();
    if (gr == null) {
      gr = new Color[nfaces+1];
      for (int i = 0; i < nfaces; i++) {
	gr[i] = new Color(face[i].cr, face[i].cg, face[i].cb);
      } gr[nfaces] = new Color(0, 0, 0);
    }

    /* This doesn't always work, but here goes. Calculate the average z depth
	of faces and use it to sort them. */

    for (int i = 0; i < nfaces; i++) {
      face[i].zdepth = 0;
      for (int c = 0; c < face[i].nverts; c++) {
	if (face[i].zdepth < tvert[face[i].index[c]+2]);
	  face[i].zdepth = tvert[face[i].index[c]+2];
      }
    }

    qs(0, nfaces-1);
/*
    for (int i = 0; i < nfaces; i++) {
      for (int c = i+1; c < nfaces; c++) {
	if (face[findex[c]].zdepth > face[findex[i]].zdepth) {
	  int tmp = findex[i];
	  findex[i] = findex[c];
	  findex[c] = tmp;
	}
      }

      System.out.print(Integer.toString(findex[i]) + " " +
			Integer.toString(face[findex[i]].zdepth) + ":");

    }

    System.out.println("");
    System.out.println("-----------");
*/

    for (int f = 0; f < nfaces; f++) {
      int i = findex[f];
      int v = 0;
      for (int c = 0; c < face[i].nverts; c++) {
	vx[c] = tvert[face[i].index[c]]; vy[c] = tvert[face[i].index[c]+1];
      }
      g.setColor(gr[i]);
      g.fillPolygon(vx, vy, face[i].nverts);
      g.setColor(gr[nfaces]);
      for (v = 0; v < (face[i].nverts-1); v++) {
	g.drawLine(vx[v], vy[v], vx[v+1], vy[v+1]);
      }
      g.drawLine(vx[v], vy[v], vx[0], vy[0]);
//    g.drawPolygon(vx, vy, face[i].nverts);
    }
  }

  void findBB() {
    if (nverts <= 0)
      return;
    float v[] = vert;
    float xmin = v[0], xmax = xmin;
    float ymin = v[1], ymax = ymin;
    float zmin = v[2], zmax = zmin;
    for (int i = nverts * 3; (i -= 3) > 0;) {
      float x = v[i];
      if (x < xmin)
	xmin = x;
      if (x > xmax)
	xmax = x;
      float y = v[i + 1];
      if (y < ymin)
	ymin = y;
      if (y > ymax)
	ymax = y;
      float z = v[i + 2];
      if (z < zmin)
	zmin = z;
      if (z > zmax)
	zmax = z;
    }
    this.xmax = xmax;
    this.xmin = xmin;
    this.ymax = ymax;
    this.ymin = ymin;
    this.zmax = zmax;
    this.zmin = zmin;
  }
}
