/*
  Simple examples of procedural bump textures. - Ken Perlin
*/

public class Sphere2 extends MISApplet {

   // PARAMETERS

   int surfaceType = 0;                      // SURFACE TYPE (0,1 OR 2)

   double surfaces[][][] = {                 // SURFACES:
      // AMBIENT   DIFFUSE    SPECULAR
      {{.2,.2,.2},{.6,.2,.1},{.3,.3,.3, 4}}, //    BROWN
      {{.2,.2,.2},{.2,.4,.8},{.3,.3,.3,20}}, //    BLUISH
      {{.2,.2,.2},{.6,.4,.3},{.3,.3,.3, 5}}, //    PAPERY
   };

   double lights[][][] = {                   // LIGHTS
     // DIRECTION      // COLOR  // SCATTER
     {{.577, .577, .577},{1,1,1} ,{0,0,0}},
     {{-.707,   0,-.707},{0,0,0} ,{.5,.4,.5}},
   };

   // WORKING GLOBAL VARIABLES

   int type;                                 // CURRENT SURFACE TYPE
   double normal[] = {0,0,0};                // SURFACE NORMAL VECTOR
   double color[] = {0,0,0};                 // SURFACE COLOR VECTOR

   // INITIALIZE ONE RENDERED FRAME

   public void initFrame(double time) {
      type = surfaceType;                     // SET THE SURFACE TYPE
   }

   // SET THE COLOR OF A SINGLE PIXEL

   public void setPixel(int x, int y, int rgb[]) {

      // SEE WHETHER PIXEL IS WITHIN THE ANTIALIASED DISK

      double X = x-W/2;    // X RELATIVE TO DISK CENTER
      double Y = H/2-y;    // Y RELATIVE TO DISK CENTER
      double R = W/2-W/10; // RADIUS OF DISK

      // IF PIXEL IS IN THE DISK, THEN

      double t = disk(X*X+Y*Y, R);
      if (t > 0) {

         // COMPUTE POINT ON UNIT SPHERE (WHICH IS ALSO THE SURFACE NORMAL)

         normal[0] = X / R;
         normal[1] = Y / R;
         normal[2] = Math.sqrt(1 - normal[0]*normal[0] - normal[1]*normal[1]);

         // SAMPLE THE FUNCTION FOUR TIMES TO GET GRADIENT INFO

         double f0 = f(normal[0]      ,normal[1]      ,normal[2]      ),
                fx = f(normal[0]+.0001,normal[1]      ,normal[2]      ),
                fy = f(normal[0]      ,normal[1]+.0001,normal[2]      ),
                fz = f(normal[0]      ,normal[1]      ,normal[2]+.0001);

         // SUBTRACT THE FUNCTION'S GRADIENT FROM THE SURFACE NORMAL

         normal[0] -= (fx - f0) / .0001;
         normal[1] -= (fy - f0) / .0001;
         normal[2] -= (fz - f0) / .0001;
	 normalize(normal);

         double s[][] = surfaces[type];

	 for (int i = 0 ; i < 3 ; i++)               // START WITH JUST AMBIENT COLOR
            color[i] = s[0][i];

         for (int L = 0 ; L < lights.length ; L++) { // ITERATE OVER ALL LIGHTS

            // COMPUTE DIFFUSE AND SPECULAR REFLECTANCE FACTORS

            double spec = specular(normal, lights[L][0], s[2][3]);
            double diff = diffuse (normal, lights[L][0]);

            // SHADE THE PIXEL WITH PHONG ALGORITHM

	    for (int i = 0 ; i < 3 ; i++)
               color[i] += lights[L][1][i] * (s[1][i]*diff + s[2][i]*spec) + lights[L][2][i]*diff;
         }

	 // CHANGE COLOR TO FIXED POINT AND SEND IT TO THE FRAME BUFFER

	 for (int i = 0 ; i < 3 ; i++)
	    rgb[i] = (int)(255 * t * color[i]);
      }

      // IF BACKGROUND, THEN PIXEL IS BLACK

      else
         rgb[0] = rgb[1] = rgb[2] = 0;
   }

   // CHOOSE A TYPE OF SPACE FILLING TEXTURE

   double f(double x,double y,double z) {
      switch (type) {
      case 0:  return  .03 * noise(x,y,z, 8);
      case 1:  return  .01 * stripes(x + 2*turbulence(x,y,z,1), 1.6);
      default: return -.10 * turbulence(x,y,z, 1);
      }
   }

   // STRIPES TEXTURE (GOOD FOR MAKING MARBLE)

   double stripes(double x, double f) {
      double t = .5 + .5 * Math.sin(f * 2*Math.PI * x);
      return t * t - .5;
   }

   // TURBULENCE TEXTURE

   double turbulence(double x, double y, double z, double freq) {
      double t = -.5;
      for ( ; freq <= W/12 ; freq *= 2)
         t += Math.abs(noise(x,y,z,freq) / freq);
      return t;
   }

   // NOISE TEXTURE

   double noise(double x, double y, double z, double freq) {
      double x1, y1, z1;
      x1 = .707*x-.707*z;
      z1 = .707*x+.707*z;
      y1 = .707*x1+.707*y;
      x1 = .707*x1-.707*y;
      return ImprovedNoise.noise(freq*x1 + 100, freq*y1, freq*z1);
   }

   // DIFFUSE REFLECTION

   double diffuse(double normal[], double light[]) {
      return Math.max(0, normal[0]*light[0] + normal[1]*light[1] + normal[2]*light[2]);
   }

   // SPECULAR REFLECTION (SPECIAL CASE, WHERE CAMERA IS ALWAYS IN (0,0,1) DIRECTION)

   double r[] = {0,0,0};
   double specular(double normal[], double light[], double power) {
      r[0] = 2*normal[2]*normal[0];
      r[1] = 2*normal[2]*normal[1];
      r[2] = 2*normal[2]*normal[2]-normal[2];
      return Math.pow(diffuse(r,light),power);
   }

   // COVERAGE OF ONE PIXEL BY A SMOOTH-EDGED DISK

   double disk(double rr, double radius) {
      double dd = rr - radius*radius;
      return dd >= 2*radius ? 0 : dd <= -2*radius ? 1 : (2*radius - dd) / (4*radius);
   }

   // NORMALIZE THE LENGTH OF A VECTOR

   void normalize(double v[]) {
      double norm = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
      v[0] /= norm;
      v[1] /= norm;
      v[2] /= norm;
   }

   // ALLOW AN EXTERNAL CALLER TO SET THE SURFACE TYPE

   public void setType(int t) { surfaceType = t; }
}