//
import java.util.*;

public class puff extends PixApplet
{
   Random R = new Random(0);
   int blur, mode = 0;
   double P[][];
   double freq = 1./20;

   // THINGS TO INITIALIZE WHEN STARTING THE APPLET

   public void init() {
      super.init();
      blur = W / 12;

      // CREATE CLOUD OF PARTICLES

      P = new double[700][3];
      for (int i = 0 ; i < P.length ; i++) {
	 double rr = 2;
	 do {
	    for (int j = 0 ; j < 3 ; j++)
	       P[i][j] = 2 * (R.nextDouble() % 1) - 1;
            rr = dot(P[i],P[i]);
         } while (rr > 1);
      }
   }

   // DRAW THE PICTURE

   public void setPix(int frame) {
      super.setPix(frame);

      // FIRST SET ALL PIXELS TO BLACK

      for (int x = 0 ; x < W ; x++)
      for (int y = 0 ; y < H ; y++)
	 pix[xy2i(x,y)] = 0;

      // THEN DRAW EACH PARTICLE

      for (int i = 0 ; i < P.length ; i++) {
	 int x = (int)(W/2 + P[i][0] * (W/2-blur));
	 int y = (int)(H/2 + P[i][1] * (W/2-blur));

	 // MODE == 0: DISPLAY PARTICLE AS A POINT

	 if (mode == 0)
	    pix[xy2i(x,y)] = 255;

         // OTHERWISE, DISPLAY PARTICLE AS A BLURRY SPOT

         else
	    for (int u = -blur ; u < blur ; u++)
	    for (int v = -blur ; v < blur ; v++)
	       pix[xy2i(x+u,y+v)] += Math.max(0, blur*blur - u*u - v*v);
      }

      // FINALLY, CONVERT CLOUD DENSITY TO PACKED R,G,B VALUES

      for (int x = 0 ; x < W ; x++)
      for (int y = 0 ; y < H ; y++) {
	 int c = pix[xy2i(x,y)];
	 if (mode == 0)
	    setPix(x,y, c,c,c);

	 // MODE > 0: DO VARIOUS IMAGE PROCESSING

	 else {

	    // NORMALIZE THE BLURRY SPOT VALUES

	    c = c * 256 / W * 256 / W / 55;
	    setPix(x,y, c,c,c);

            if (mode >= 1) {
	       if (c == 0)
		  setPix(x,y, 0,0,0);
               else {
	          double t = c / 255.;

	          // MODE == 3 OR 4: ADD VARIOUS FREQUENCIES OF NOISE

	          if (mode >= 4)
	             t += .07 * Math.abs(ImprovedNoise.noise(2*freq*x,2*freq*y,.5));
	          if (mode >= 3) {
	             t += .14 * Math.abs(ImprovedNoise.noise(  freq*x,  freq*y,.5));
	             t = Math.max(0, Math.min(1, t));
                  }

	          // MODE > 1: APPLY HIGH CONTRAST FILTER TO CLOUD EDGES

	          if (mode >= 2)
	             c = (int)(255 * filter(t));

                  // BLEND OVER SKY COLOR

		  setPix(x,y, c,c,c);
               }
            }
         }
      }
   }

   // HIGH CONTRAST FILTER TO BRING OUT THE CLOUD EDGES

   double filter(double t) {
      t = bias(t, .67);
      if (t < .5)
         t = gain(t, .86);
      t = bias(t, .35);
      return t;
   }

   // ALLOW EXTERNAL APPLICATION TO SET THE MODE

   public void setMode(int mode) {
      this.mode = mode;
      damage = true;
   }

   // DOT PRODUCT

   static double dot(double a[], double b[]) {
      double d = 0;
      for (int i = 0 ; i < a.length ; i++)
	 d += a[i] * b[i];
      return d;
   }

   // LINEAR INTERPOLATION

   static double lerp(double t, double a, double b) {
      return a + t * (b - a);
   }

   // BIAS AND GAIN FILTERS (FROM PERLIN '85)

   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);
   }
}