//<pre>
// Copyright 2001 Ken Perlin

package render;

// A VERY SIMPLE 3D RENDERER BUILT IN JAVA 1.0 - KEN PERLIN

/**
   Deals with aspects of color and material properties of objects.<p>
   Stores color properties of material: diffuse light, specular light,
   and ambient light.<br> 
   Holds information about transparency and whether the material is 
   double sided.
   @author Ken Perlin 2001
*/
public class Material implements Runnable {
   private String notice = "Copyright 2001 Ken Perlin. All rights reserved.";

   /**
      Flag determining wheter to precompute and store color tables for 
      direct look up intead of on the fly computation.
   */
   public boolean tableMode = true;

   /**
      Indicator whether background caching started.
   */
   private boolean startedBackgroundCaching = false;

   /**
      Bit depth of the resolution.
   */
   public int resP;

   /**
      Resolution of the material.
   */
   public int res;

   /**
      Stores the precomputed normal map for quick lookup later.
   */
   protected int table[] = new int[2 * res * res];

   /** 
   Indicates whether the material is double sided.
   */
   public boolean isDoubleSided = false;

   /**
      Indicates whether the material is anisotropic (light reflection  
      varies with respect to direction).
   */
   public boolean anisotropic = false;

   /**
      Noise frequency.
   */
   public double noiseF = 1;

   /**
      Noise amplitude.
   */
   public double noiseA = 0;

   /**
      Looks up the appropriate color value from the table at x, y, z.
      @param ix x
      @param iy y
      @param iz z
      @return the packed color value
   */
   public int getTable(int ix, int iy, int iz) {
      if (!tableMode || iz < 0 || iz > 1 || ix < 0 || ix >= res || iy < 0 || iy >= res)
         return 0;
      return table[tableIndex(ix, iy, iz)];
   }

   /**
      Sets the x, y, z, value in the lookup table to p.
      @param ix x
      @param iy y
      @param iz z
      @param p the packed color value
   */
   public void setTable(int ix, int iy, int iz, int p) {
      if (!tableMode)
         return;
      startedBackgroundCaching = true;
      if (iz < 0 || iz > 1 || ix < 0 || ix >= res || iy < 0 || iy >= res)
         return;
      table[tableIndex(ix, iy, iz)] = p;
   }

   /**
      Creates and initializes the lookup table to all black.
      @param p bit depth resolution
   */
   public void initTable(int p) {
      if (!tableMode)
         return;
      startedBackgroundCaching = false;
      resP = p;
      res = 1 << resP;
      table = new int[2 * res * res];
      for (int iz = 0; iz < 2; iz++)
         for (int ix = 0; ix < res; ix++)
            for (int iy = 0; iy < res; iy++)
               table[tableIndex(ix, iy, iz)] = 0;
   }

   /** 
   Counts the non-zero entries in the lookup table.
   @return the number of non-zero entries.
   */
   public int countTable() {
      int n = 0;
      for (int iz = 0; iz < 2; iz++)
         for (int ix = 0; ix < res; ix++)
            for (int iy = 0; iy < res; iy++)
               if (table[tableIndex(ix, iy, iz)] != 0)
                  n++;
      return n;
   }

   private int tableIndex(int ix, int iy, int iz) {
      return (((iz << resP) | ix) << resP) | iy;
   }

   /** 
   Holds the color information ( Diffuse color R,G,B, Specular 
   Color R,G,B, Specular exponent). 
   */
   protected double[] color = { 1, 1, 1, 0, 0, 0, 1 };

   /**
      Transparency of the object (0-invisible, 1-opaque).
   */
   protected double transparency = 0;
   /**
      Ambient lighting color values in RGB (glow) (range [0,1]).
   */
   protected double glow[] = { 0, 0, 0 };

   protected Texture texture;

   public boolean hasTexture() {
      return texture != null;
   }

   public Material setTexture(Texture texel) {
      texture = texel;
      return this;
   }

   private double getw(int pz, int NB) {
      double p = 1. * pz / (1 << NB);
      double ret = 1. * p / (1 << 31 - NB);
      return ret;
   }

   double FL = 10;
   private double getuv(int pz, int NB) {
      double ret = 1. * pz / (1 << 31 - NB);
      return ret;
   }

   /** Returns the packed integer of a particular pixel
    *  To do extra pixel computation, overload this method
    *  @param data array representing the pixel 
    *  indices of data are:
    *  0,1,2 are the x,y,z of the pixel
    *  3,4,5 are the r,g,b values
    *  6,7 are the u,v coordinates 
    *  @param dx size of the pixel in x
    *  @param dy size of the pixel in y
    *  @param NB precision value
    */
   public int computePixel(int[] data, int dx, int dy, int NB) {
      //0,1,2 are the x,y,z of the point
      //3,4,5 are the rbg of the point
      //6, 7 are the u, v
      double dw = getw(data[2], NB);

      double u = (double) (getuv(data[6], NB)) / dw;
      double v = (double) (getuv(data[7], NB)) / dw;

      int ret;
      int NBPower = 1 << NB;

      ret = texture.getTexel(u, v, dx, dy, NBPower);
      return ret;
   }

   /**
      Sets diffuse and specular color values.
      @param c color values corresponding to [dred, dgreen, dblue, sred,
      sgreen, sblue, s exponent].
      @return the material
      @deprecated Use specific color-setting functions, see {@link #setDS}.
   */
   public Material setColor(double c[]) {

      for (int i = 0; i < c.length; i++)
         color[i] = c[i];
      recache();
      return this;
   }

   /**
      Sets the diffuse components of light (range 0..1).
      @param r red
      @param g green
      @param b blue
   */
   public Material setDiffuse(double r, double g, double b) {
      color[0] = r;
      color[1] = g;
      color[2] = b;
      recache();
      return this;
   }

   /**
      Gets the diffuse color components in RGB (range 0 to 1).
      @return an array of doubles corresponding to r g b color components
   */
   public double[] getDiffuse() {
      return new double[] { color[0], color[1], color[2] };
   }

   /**
      Sets the specular color components (r, g, b, exp).
      @param r red
      @param g green
      @param b blue
      @return the material
   */
   public Material setSpecular(double r, double g, double b, double p) {

      color[3] = r;
      color[4] = g;
      color[5] = b;
      color[6] = p;
      recache();
      return this;
   }

   /**
      Gets the specular components of color (r, g, b, exponent).
      @return array of doubles containing 4 specular light components
   */
   public double[] getSpecular() {
      return new double[] { color[3], color[4], color[5], color[6] };
   }

   /**
      Sets the ambient lighting color values (range 0..1).
      @param r red
      @param g green
      @param b blue
      @return the material
   */
   public Material setAmbient(double r, double g, double b) {
      glow[0] = r;
      glow[1] = g;
      glow[2] = b;
      recache();
      return this;
   }

   /**
      Gets the glow light components (r, g, b).
      @return the array of the ambient lighting components (r, g, b)
   */
   public double[] getAmbient() {
      return new double[] { glow[0], glow[1], glow[2] };
   }

   /**
      Sets the diffuse and specular values of color.
      @param dr diffuse red
      @param dg diffuse green
      @param db diffuse blue
      @param sr specular red
      @param sg specular green
      @param sb specular blue
      @param se specular exponent
      @return the material
    */
   public Material setDS(double dr, double dg, double db, double sr, double sg, double sb, double p) {
      color[0] = dr;
      color[1] = dg;
      color[2] = db;
      color[3] = sr;
      color[4] = sg;
      color[5] = sb;
      color[6] = p;
      recache();
      return this;
   }

   /**
      Returns the diffuse and specular light components.
      @return array of values corresponding to diffuse r, g, b, and 
      specular r, g, b, exponent. In that order.
   */
   public double[] getDS() {
      double[] k = new double[7];
      for (int i = 0; i < 7; i++)
         k[i] = color[i];
      return k;
   }

   /**
      Sets the diffuse, specular, and ambient values of color.
      @param dr diffuse red
      @param dg diffuse green
      @param db diffuse blue
      @param sr specular red
      @param sg specular green
      @param sb specular blue
      @param se specular exponent
      @param ar ambient red
      @param ag ambient green
      @param ab ambient blue
      @return the material
   */
   public Material setDSA(double dr, double dg, double db, double sr, double sg, double sb, double p, double ar, double ag, double ab) {
      color[0] = dr;
      color[1] = dg;
      color[2] = db;
      color[3] = sr;
      color[4] = sg;
      color[5] = sb;
      color[6] = p;
      glow[0] = ar;
      glow[1] = ag;
      glow[2] = ab;
      recache();
      return this;
   }

   /**
      Gets the diffuse, specular, and ambient light components.
      @return An array containing the diffuse r, g, b, specular r, g, b, exp,
      ambient r, g, b.
   */
   public double[] getDSA() {
      double[] k = new double[10];
      for (int i = 0; i < 7; i++)
         k[i] = color[i];
      k[7] = glow[0];
      k[8] = glow[1];
      k[9] = glow[2];
      return k;
   }

   /**
      Sets the diffuse color of an object.
      @param dr diffuse red
      @param dg diffuse green
      @param db diffuse blue
      @return the material
      @deprecated Use specific color-setting functions, see 
      {@link #setDiffuse}.
   */
   public Material setColor(double dr, double dg, double db) {
      setDiffuse(dr, dg, db);
      return this;
   }

   /**
      Sets the diffuse and specular values of color.
      @param dr diffuse red
      @param dg diffuse green
      @param db diffuse blue
      @param sr specular red
      @param sg specular green
      @param sb specular blue
      @param sp specular exponent
      @return the material
      @see #setDS
      @deprecated Use specific color-setting functions, see {@link #setDS}.
   */
   public Material setColor(double dr, double dg, double db, double sr, double sg, double sb, double sp) {
      setDS(dr, dg, db, sr, sg, sb, sp);
      return this;
   }

   /**
      Sets the diffuse, specular and ambient values of color.
      @param dr diffuse red
      @param dg diffuse green
      @param db diffuse blue
      @param sr specular red
      @param sg specular green
      @param sb specular blue
      @param se specular exponent
      @param ar ambient red
      @param ag ambient green
      @param ab ambient blue
      @return the material
      @deprecated Use specific color-setting functions, see {@link #setDSA}.
   */
   public Material setColor(double dr, double dg, double db, double sr, double sg, double sb, double sp, double ar, double ag, double ab) {
      setDSA(dr, dg, db, sr, sg, sb, sp, ar, ag, ab);
      return this;
   }

   /**
      Sets the double sided flag true to indicate whether the object is
      double sided.
      @param t new value of isDoubleSided
      @return the material
   */
   public Material setDoubleSided(boolean t) {
      isDoubleSided = t;
      return this;
   }

   /**
      Sets the transparency of the material (0 transparent to 1 opaque).
      @param t new transparency value
      @return the material
   */
   public Material setTransparency(double t) {
      transparency = t;
      return this;
   }

   /**
      Returns the transparency of the material (0 transparent to 1 opaque).
      @return the actual transparency of the material
   */
   public double getTransparency() {
      return transparency;
   }

   /**
      Sets the color value of the ambient lighting.
      @param g array of [red, green, blue] values in range 0..1
      @return the material.
      @see #setAmbient
      @deprecated Use specific color setting function, {@link #setAmbient}
   */
   public Material setGlow(double g[]) {
      for (int i = 0; i < 3; i++)
         glow[i] = g[i];
      recache();
      return this;
   }
   /**
      Sets the color value of the ambient lighting.
      @param r red 
      @param g green
      @param b blue
      @return the material
      @see  #setAmbient
      @deprecated Use specific color setting function, {@link #setAmbient}
   */
   public Material setGlow(double r, double g, double b) {
      setAmbient(r, g, b);
      return this;
   }

   // THE REST OF THIS CLASS IS DEDICATED TO BACKGROUND CACHING - FILLING
   // UP A NORMAL-MAP TABLE IN THE BACKGROUND DURING THE FIRST FEW SECONDS
   // THAT THE APPLET IS RUNNING.

   private Thread t = null;

   /**
      Start background caching thread.
   */
   private void start() {
      if (t == null) {
         t = new Thread(this);
         t.start();
      }
   }
   /**
      Stop background caching thread.
   */
   private void stop() {
      if (t != null)
         finishedBackgroundCaching = true;
   }

   public void recache() {
      stop();
      initTable(7);
      finishedBackgroundCaching = false;
      if (tableMode)
         start();
   }

   protected double v[] = new double[6];
   private boolean finishedBackgroundCaching = false;

   /**
      Thread that runs in the background ( provided the resources are
      available - no mouse dragging for example) and computes the normal
      map table of values for quick look up later.
   */
   public void run() {
      int i = 0, chunk = 0;

      while (!finishedBackgroundCaching) {

         if (tableMode && startedBackgroundCaching && !Renderer.isDragging()) {

            for (chunk = 0; chunk < 500; chunk++) {

               if (table[i] == 0)
                  Renderer.renderVertex(i, this);

               i++;
               if (i == table.length) {
                  finishedBackgroundCaching = true;
                  break;
               }
            }
         }

         try {
            Thread.sleep(20);
         } catch (InterruptedException e) {}

      }
   }
}

