//
import java.awt.*;

public class VolumeApplet extends PixApplet
{
   public double light[] = {1,1,1}, hilite[] = {.5,.5,1};
   double T[][], M[][], D[][];
   double X, Y, Z, R, C = 1, S = 0;
   double xx, yy, zz;
   double normal[] = new double[3];
   double rgb[] = new double[3];
   double zRate = 1;
   int skip = 1;
   int animationFrame = 0;

   public void setBg(double r, double g, double b) {
      bg[0] = r; bg[1] = g; bg[2] = b;
   }
   public void setAmbient(double r, double g, double b) {
      ambient[0] = r; ambient[1] = g; ambient[2] = b;
   }
   public void setDiffuse(double r, double g, double b) {
      diffuse[0] = r; diffuse[1] = g; diffuse[2] = b;
   }
   public void setSpecular(double r, double g, double b, double p) {
      specular[0] = r; specular[1] = g; specular[2] = b;
      power = p;
   }

   public void initialize() { }
   public void restart() { }
   public void animate(int f) { }
   public double density(double x, double y, double z) { return 0; }

   void lighting(double normal[], double rgb[]) {
      double t = Math.max(0, .5 + .3 * (normal[0] + normal[1] + normal[2]));
      double s = Math.pow(t, power);
      rgb[0] = t * diffuse[0] + s * specular[0];
      rgb[1] = t * diffuse[1] + s * specular[1];
      rgb[2] = t * diffuse[2] + s * specular[2];
   }

   public void init() {
      super.init();
      D = new double[W][H];
      M = new double[W][H];
      T = new double[W][H];
      R = W/2;

      initialize();
      setToBackground();
   }

   void setToBackground() {
      int rgb = pack(f2i(bg[0]),f2i(bg[1]),f2i(bg[2]));
      int i = 0;
      for (int y = 0 ; y < H ; y++)
      for (int x = 0 ; x < W ; x++)
	    pix[i++] = rgb;
   }

   void freshStart() {
      frame0 = frame;
      initialize();
      setToBackground();
      restart();
      clearRender = true;
   }

   int frame, frame0 = 0;
   public void setPix(int frame) {
      this.frame = frame;
      if (skip == 3 && frame-frame0 == W/skip) {
         skip = 1;
	 specular[0] *= .5;
	 specular[1] *= .5;
	 specular[2] *= .5;
	 frame0 = frame;
	 restart();
	 clearRender = true;
      }
      if (frame-frame0 == W/skip) {
         outputImage();
	 animate(animationFrame++);
	 freshStart();
      }

      if (clearRender) {
	 for (int x = 0 ; x < W ; x++)
	 for (int y = 0 ; y < H ; y++) {
	    D[x][y] = 0;
	    M[x][y] = 1;
	    T[x][y] = 1;
         }
	 clearRender = false;
      }

      damage = true;
      for (int x = skip ; x < W ; x += skip)
      for (int y = skip ; y < H ; y += skip) {

	 M[x-skip][y-skip] = M[x][y];

	 if (isSlice) {
	    D[x][y] = 0;
	    M[x][y] = 1; // SHADOW MASKING
	    T[x][y] = 1; // REMAINING FRONT-TO-BACK TRANSPARENCY
	    Z = sliceZ;
         }
	 else {
	    if (T[x][y] < .01)
	       continue;
	    Z = (R - skip*zRate*(frame-frame0)) / R;
         }

	 X =  (x - R) / R;
	 Y =  (y - R) / R;

	 double p = 1 + .2*Z;
	 X /= p;
	 Y /= p;

	 xx = X;
	 //yy =  Y*C+Z*S;
	 //zz = -Y*S+Z*C;
	 yy = Y;
	 zz = Z;

	 double d = Math.max(0, Math.min(.99,
	            skip*zRate * density(xx, yy, zz)));

	 if (d == .99)
	    D[x][y] = 1;
	 else if (d > .01 && d < .99) {
	    if (isSlice) {
	       int c = (int)(255 * d);
	       pix[xy2i(x,H-y)] = pack(c,c,c);
	       continue;
            }

	    double dx = D[x-skip][y];
	    double dy = D[x][y-skip];
	    double dz = D[x][y];
	    if (isSlice)
	       dz = 1;

	    if (dx > 0 && dx < 1 && dy > 0 && dy < 1 && dz > 0 && dz < 1) {

	       double transparency = Math.pow(2.71828, -d);
	       double contrib = T[x][y] * (1 - transparency);

	       double red = ambient[0];
	       double grn = ambient[1];
	       double blu = ambient[2];

	       if (M[x][y] >= .01) {
	          normal[0] = dx - d;
	          normal[1] = dy - d;
	          normal[2] = d - dz;
		  if (normal[0]!=0 && normal[1]!=0 && normal[2]!=0)
	             normalize(normal);
	          lighting(normal, rgb);
	          red += M[x][y] * rgb[0];
	          grn += M[x][y] * rgb[1];
	          blu += M[x][y] * rgb[2];
               }

	       int i = xy2i(x,H-y);
	       int rgb = pack(f2i(lerp(contrib, i2f(unpack(pix[i],0)), red)),
	                      f2i(lerp(contrib, i2f(unpack(pix[i],1)), grn)),
	                      f2i(lerp(contrib, i2f(unpack(pix[i],2)), blu)));
               pix[i] = rgb;
	       if (skip > 1)
	          for (int I = 0 ; I < skip ; I++)
	          for (int J = 0 ; J < skip ; J++)
		     pix[i + W*I + J] = rgb;

	       T[x][y] *= transparency;
	       M[x-skip][y-skip] *= transparency;
            }
	    D[x][y] = d;
         }
      }
      blur(M);
   }

   double tmpX[], tmpY[];

   void blur(double M[][]) {

      if (tmpY == null)
         tmpY = new double[H];
      for (int x = 0 ; x < W ; x++) {
         for (int y = 1 ; y < H-1 ; y++)
            tmpY[y] = M[x][y-1]/6 + 2*M[x][y]/3 + M[x][y+1]/6;
         for (int y = 1 ; y < H-1 ; y++)
            M[x][y] = tmpY[y];
      }

      if (tmpX == null)
         tmpX = new double[W];
      for (int y = 0 ; y < H ; y++) {
         for (int x = 1 ; x < W-1 ; x++)
            tmpX[x] = M[x-1][y]/6 + 2*M[x][y]/3 + M[x+1][y]/6;
         for (int x = 1 ; x < W-1 ; x++)
            M[x][y] = tmpX[x];
      }
   }

   void normalize(double v[]) {
      double t = Math.sqrt(dot(v,v));
      v[0] /= t;
      v[1] /= t;
      v[2] /= t;
   }

//////////////// MATH PRIMITIVES //////////////////////////

   double dot(double a[], double b[]) { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }
   double scurve(double t) { return t < 0 ? 0 : t > 1 ? 1 : t*t*(3-t-t); }
   double lerp(double t,double a,double b) { return a + t * (b - a); }
   double i2f(int i) { return i / 255.; }
   int f2i(double f) { return Math.max(0,Math.min(255,(int)(255 * f))); }
   double boundary(double t) { return 10 * scurve(t); }
   double clip(double lo,double hi,double t) {
      return Math.max(lo, Math.min(hi, t));
   }
   static final double LOG_HALF = Math.log(0.5);
   static double gain(double a, double b) {
      double p;

      if (a<.001)
        return 0.;
      else if (a>.999)
         return 1;

      b = (b<.001) ? .0001 : (b>.999) ? .999 : b;
      p = Math.log(1. - b) / LOG_HALF;
      if (a < 0.5)
         return Math.pow(2 * a, p) / 2;
      else
         return 1. - Math.pow(2 * (1. - a), p) / 2;
   }
   static double bias(double a, double b) {
      if (a < .001)
         return 0.;
      else if (a > .999)
         return 1.;
      else if (b < .001)
         return 0.;
      else if (b > .999)
         return 1.;
      else
         return Math.pow(a, Math.log(b) / LOG_HALF);
   }

//////////////// COLORS //////////////////////////

   private double ambient[] = {.2,.2,.2};
   private double diffuse[] = {.8,.8,.8};
   private double specular[] = {0,0,0}, power = 2;
   private double bg[] = {.5,.5,.8};
   public  double fg[] = {1,1,1};

//////////////// GEOMETRIC PRIMITIVES //////////////////////////

   public double ball(double cx, double cy, double cz, double r) {
      double x = xx - cx, y = yy - cy, z = zz - cz;
      double xyz = x*x + y*y + z*z;
      return transition(.04 * R * (r*r - xyz) / r);
   }
   public double superquadric(double cx, double cy, double cz, double r, double p) {
      double x = Math.abs(xx - cx), y = Math.abs(yy - cy), z = Math.abs(zz - cz);
      if (x > r || y > r || z > r)
	 return 0;
      double xyz = Math.pow(x,p) + Math.pow(y,p) + Math.pow(z,p);
      double rr = Math.pow(r,p);
      return transition(.1 * R * (rr - xyz) / (p * rr/r));
   }
   public double transition(double t) {
      return clip(0,1,t);
   }

///////////////// USER INTERACTION FUNCTIONS //////////////////////

   boolean clearRender = true;
   boolean isSlice = false;
   double sliceZ = 0.0;

   public boolean keyUp(Event e, int key) {
      switch (key) {
      case 's':
	 isSlice = ! isSlice;
	 clearRender = true;
	 break;
      default:
	 break;
      }
      return true;
   }

   double theta = 0;
   int mx = 0;

   public boolean mouseDown(Event e, int x, int y) {
      mx = x;
      return true;
   }
   public boolean mouseDrag(Event e, int x, int y) {
      if (isSlice) {
         sliceZ = 2. * y / H - 1;
         clearRender = true;
      }
      theta += (double)(x - mx) / W;
      mx = x;
      return true;
   }
   public boolean mouseUp(Event e, int x, int y) {
      if (isSlice) {
         sliceZ = 2. * y / H - 1;
         clearRender = true;
      }
      else {
         System.out.println(theta);
         C = Math.cos(theta);
         S = Math.sin(theta);
         freshStart();
      }
      return true;
   }

   void outputImage() {
      System.out.println(animationFrame + " " + W + " " + H);
      int i = 0;
      for (int y = 0 ; y < H ; y++)
      for (int x = 0 ; x < W ; x++) {
         System.out.print(int2hex(pix[i++]));
	 if (i % 10 == 0)
            System.out.println();
      }
      System.out.println();
   }
   String int2hex(int n) {
      return i2h(n >> 16) + i2h(n >> 8) + i2h(n);
   }
   String i2h(int n) {
      n &= 255;
      return nibble(n >> 4) + nibble(n);
   }
   String nibble(int n) {
      n &= 15;
      return "" + (char)(n < 10 ? '0' + n : 'a' + (n-10));
   }

   void inputImage() {
   }
}