import java.applet.Applet;
import java.awt.*;
import java.util.*;
import java.awt.image.*;
import java.net.URL;

public class VGOBlob extends Applet {
    
    MemoryImageSource 	source;
    Image		image;
    int[]		pixels;
    int			pixWidth,pixHeight,pixSize;
    DrawThread		drawThread;
    int tmap = 0;
    int maxmap = 4;
    int lastMX = 1;
    int lastMY = 1;
    
    int[] normalImg;
    int   normalImgW;
    int   normalImgH;
    
    Applet appInst; 
    
    Image myBump;
    
    public void init() {
	    appInst = this;
	    pixWidth = getSize().width;
	    pixHeight = getSize().height;
	    pixSize = pixHeight * pixWidth ;
	    pixels = new int[pixSize];
	    int value = getBackground().getRGB();
	    for (int i = 0; i < pixSize; i++) {
		pixels[i] = value;
	    }

	    source = new MemoryImageSource(pixWidth, pixHeight, pixels, 0, pixWidth);
	    source.setAnimated(true);
	    try{   
		    
		    myBump = getImage( new URL(getCodeBase(), "images/tex.jpg"));
		    
		 
		    
	    } catch (Exception E) {
		    System.err.println(E.toString());
		    System.err.println("Malformed URL!");
	    }
	    image = createImage(source);
	    source.newPixels(0,0,pixWidth,pixHeight);
	    repaint();
    }
    

    boolean stopped = false;
    public void start() {
	    
	drawThread = new DrawThread();
	drawThread.start();
	
    }    
    

    public void stop() {
        stopped=true;
    }

    public void destroy() {
        
    }

    public void update(Graphics g)
    {
	    paint (g);
    }

    public void paint(Graphics g) {
	//Draw a Rectangle around the applet's display area.

        g.drawImage(image,0,0,this);
	
	//g.drawString("Hello "+String.valueOf(pixHeight)+" -" + String.valueOf(pixWidth),125+((int)(Math.random()*113)),25);
	

    }
	
	public boolean mouseDown( Event evt, int x, int y )
	{
		//drawThread.setIndent(x,y);
		tmap++;
		tmap=tmap%maxmap;
		//tmap = (int)Math.floor(Math.random()*3.5);
		lastMX = x; lastMY=y;
		drawThread.makeNormalMap(tmap);	 

		return true;
	}
    
    
    //------------------------------------------
    public class DrawThread extends Thread
    {
	    
		
	    public class Blob 
	    {
		    float x,y,i,s,o,I;
		    public Blob(float x,float y, float i,float s,float o)
		    {
			    this.x=x;
			    this.y=y;
			    this.i=i;			    
				this.s=s;
				this.o=o;
		    }
	    }
		
		public DrawThread()
	    {
	        
	
	    }
		    
		    
	    long begTime;
	    int numBlobs=6;
		int oosAlias = 2;
		float[][] ooSqrtMap;
		Blob[] blobs;
		
	    
	    
	    float[] alphaMap;
	    float[] alphaBorder;
		
		float refract = 5;
		float reflect = 5;
		float[] nmX;
		float[] nmY;
		float[] nmZ;
		
		boolean indent = false;
		int indentX = 0;
		int indentY = 0;
		
		int[] backMap;
		
		boolean checkedLoaded = false;
		
	    public void run()
	    {
		
			
		//MAKE BLOB MAPS
		
		ooSqrtMap = new float[pixHeight<<oosAlias][pixWidth<<oosAlias];
		for(int i=0;i<pixHeight<<oosAlias;i++) {
			for(int j=0;j<pixWidth<<oosAlias;j++) {
				float ri = i/((float)(1<<oosAlias));
				float rj = j/((float)(1<<oosAlias));
				float oosqrt = (float)(1/(Math.sqrt(ri*ri+rj*rj)+.001));
				ooSqrtMap[i][j]=oosqrt;
			}
		}
		
		blobs = new Blob[numBlobs];
		for(int k=0;k<numBlobs;k++)
		{
			float mxbI = 4f;
			float mnbI = 3.5f;
			blobs[k]=new Blob(
				(float)Math.random()*pixWidth,
				(float)Math.random()*pixHeight,
				mnbI+(float)Math.random()*mxbI,
				(float)Math.random()*.2f,
				(float)((float)k/numBlobs)*4);
			if(blobs[k].s<.1f) {blobs[k].s-=.2f;}
		}
		
		alphaMap=new float[pixHeight*pixWidth];
		alphaBorder=new float[pixHeight*pixWidth];
		
		makeAlphaBorder();
		makeAlphaMap(0);
		
		//MAKE NORMAL MAPS
		nmX = new float[pixSize];
		nmY = new float[pixSize];
		nmZ = new float[pixSize];		
		makeNormalMap(0);
		
		//MAKE BACKGROUND MAPS
		backMap = new int[pixSize];
		
		//START MAIN LOOP
		begTime = (new Date()).getTime();
		int startSilly = 5000;
		reflect=0;
		refract=0;
		while(!stopped)
		{
			long elapsed = (new Date()).getTime() - begTime;
			long offset = elapsed ;
			if(this.indent) 
			{
				
				indentPixel(indentX,indentY);
				this.indent=false;
			}
			if(offset>startSilly) {
				
				reflect=(float)(-(Math.cos((float)(offset-startSilly)/2500)-1))*3;
				refract=(float)(-(Math.cos((float)(offset-startSilly)/1250)-1))*3;
			}
			
			
			makeAlphaMap((int) offset>>4 );
			makeBackMap((int) offset>>6);
			drawBuffer();
			source.newPixels(0,0,pixWidth,pixHeight);
			repaint();
			try{
				this.sleep(25);
			} catch(Exception E)
			{
				System.err.println("Sleep Error!?");
			}
		}    
	    }
		
		public void normalize(int p)
		{
			double od = 1/Math.sqrt(nmX[p]*nmX[p]+nmY[p]*nmY[p]+nmZ[p]*nmZ[p]);
			nmX[p]*=od;nmY[p]*=od;nmZ[p]*=od;
		}
		
		public void makeNormalMap(int um)
		{
			int p = 0;
			for(double I=0;I<pixHeight;I++) {				
				for(double J=0;J<pixWidth;J++) {										
					if(um==2) {
						double dy = I-(lastMY);
						double dx = J-(lastMX);
						double dist=Math.sqrt(dy*dy+dx*dx);
						nmX[p]=(float)(Math.cos(dist*.3));nmY[p]=(float)(Math.sin(dist*.3));nmZ[p]=1;
					
					} else if(um==1) {
						double dy = I-(lastMY);
						double dx = J-(lastMX);
						double dist=Math.sqrt(dy*dy+dx*dx);
						nmX[p]=(float)(Math.cos(dx*dist*.03));nmY[p]=(float)(Math.sin(dy*dist*.03));nmZ[p]=1;					
					} else if(um==3) {
						if(!checkedLoaded) {
							try{
								System.err.println(String.valueOf(myBump) +"-----"+ appInst.toString() +"----"+VGOBlob.this.toString());
								while(myBump.getWidth(appInst) == -1 ) {  }
								normalImgW = myBump.getWidth(VGOBlob.this);		    
								normalImgH = myBump.getHeight(VGOBlob.this);		    
								normalImg = new int[normalImgW*normalImgH];
								PixelGrabber pg = new PixelGrabber(myBump, 0, 0, normalImgW,normalImgH, normalImg, 0, normalImgW);
								pg.grabPixels();
							} catch (Exception E) {
								System.err.println(E.toString() + " - ERROR");
								E.printStackTrace();
							}
							checkedLoaded=true;
						}
						if(I>0 && I<pixHeight-1) {
							int nmp = ((int)(I%(normalImgH-2))+1)*normalImgW+(((int)J%(normalImgW-2))+1);
							float dy = (normalImg[nmp]&0xFF)-(normalImg[nmp-normalImgW]&0xFF);
							float dx = (normalImg[nmp]&0xFF)-(normalImg[nmp-1]&0xFF);
							nmX[p]=dx;nmY[p]=dy;nmZ[p]=1f;	
						}
					
					} else { 
						nmX[p]=(float)(Math.cos(J*.23+I*.25));nmY[p]=(float)(Math.sin(J*.25-I*.23));nmZ[p]=1;	
					}
			        normalize(p);
				    p++;
				}
		    }
			
		}
		
		
		
		
		public void setNormal(int p, float x,float y,float z)
		{
			nmX[p]=x;nmY[p]=y;nmZ[p]=z;
			normalize(p);
		}
		public void indentPixel(int x, int y)
		{
			int pos = x + pixWidth*y;

			setNormal(pos-1-pixWidth,-1,-1,1);
			setNormal(pos-pixWidth,0,-1,1);
			setNormal(pos+1-pixWidth,1,-1,1);
			setNormal(pos-1,-1,0,1);
			setNormal(pos,0,0,1);
			setNormal(pos+1,+1,0,1);
			setNormal(pos-1+pixWidth,-1,1,1);
			setNormal(pos+pixWidth,0,1,1);
			setNormal(pos+1+pixWidth,1,1,1);
			
		}
		
		
		public void setIndent(int x, int y)
		{
			this.indentX=x;
			this.indentY=y;
			this.indent=true;
			
		}
	    
	    public void makeAlphaBorder()
	    {
		    
		 int po = 0;
		 for(int i=0;i<pixHeight;i++) 
		 {
			 for(int j=0;j<pixWidth;j++)
			 {
			
		           float border=13;
			   float mindist=border;
			   //if(i<border) {mindist-=(border-i);}
			   if(pixHeight-i<border) {mindist-=(border-(pixHeight-i));}
			   //if(pixWidth-j<border) {mindist-=(border-(pixWidth-j));}
			   //if(j<border) {mindist-=border-j;}
			   if(mindist<0) {mindist=0;}
			   alphaBorder[po]=(mindist/border)*.67f;
			   float rnd = ((float)Math.random()*.1f)+.9f;
			   alphaBorder[po]=1-(alphaBorder[po]*rnd);
			   
			   po++;
			 }
		 }		    
	    }
	    
	    
	    public void makeAlphaMap(int offset)
	    {
			int p=0;
			int k=0;
			for(k=0;k<numBlobs;k++)
			{
				float P = blobs[k].o+(float)offset*.0035f*blobs[k].s;				
				double tP = Math.cos(P*.5*Math.PI);
				double xP = tP*tP;
				if(tP<0) xP=-xP;
				
				
				blobs[k].x=((float)xP*pixWidth*.98f+pixWidth)*.5f;
				blobs[k].y=((float)Math.sin(P*.5*Math.PI)*pixHeight*.85f+pixHeight)*.5f;
				//blobs[k].I=blobs[k].i*(float)Math.abs(Math.sin(((P+1)*.25)*Math.PI))+2;
				if(blobs[k].s<0) {blobs[k].I=((float)Math.abs(Math.sin(P*2*Math.PI))*blobs[k].i+1);}
				else {blobs[k].I=((float)Math.abs(Math.cos(P*2*Math.PI))*blobs[k].i+1);}
			}
			for(int i=0;i<pixHeight;i++)
			{
				for(int j=0;j<pixWidth;j++)
				{
					float intens = 0;
					for(k=0;k<numBlobs;k++)
					{
						
						float I = blobs[k].I;
						int X = (int) (Math.abs(blobs[k].x-j)*((float)(1<<oosAlias)));
						int Y = (int) (Math.abs(blobs[k].y-i)*((float)(1<<oosAlias)));
						intens+= I*ooSqrtMap[Y][X];
					}
					float t = 1-(alphaBorder[p]+intens);
					alphaMap[p]= (t<0?0:t);
					
					p++;
				}
			}
		 
	    }
				 
				 
		public void makeBackMap(int offset)	   
		{
			int p=0;
		    
		    int r=offset*2;
		    int b=offset;
		    
		    
		    
		    for(int i = 0;i<pixHeight;i++)
		    {
			    r+=8;
			    b-=4;
			    
			    int g=(offset)+128;
			    for(int j=0;j<pixWidth;j++)
			    {
				
					g++;
					
					int rr,gg,bb;
					if((g&0x100)>0) {gg=0xFF-(g&0xFF);}
					else {gg=g&0xFF;}
					
					if((r&0x100)>0) {rr=0xFF-(r&0xFF);}
					else {rr=r&0xFF;}
					
					if((b&0x100)>0) {bb=0xFF-(b&0xFF);}		
					else {bb=b&0xFF;}
					
					backMap[p]=0xFF000000+(rr<<16)+(gg<<8)+bb;
					p++;
				}
			}
					
							
			
		}
		
	    boolean specular = false;
		double[] spec={.5,.5,-.5};
	    
	    public void drawBuffer()
	    {
			if(specular) {
				double od = 1/Math.sqrt(spec[0]*spec[0] + spec[1]*spec[1] + spec[2]*spec[2]);
				spec[0]*=od;spec[1]*=od;spec[2]*=od;
			}
			int p=0;
			for(int i=0;i<pixHeight;i++) {
				for(int j=0;j<pixWidth;j++) {
					
					
					
					
					float A = 0;
					//A= alphaMap[p];
					int aX =(int)(j+nmX[p]*reflect);
					if(aX>=0 && aX<pixWidth) {
						int aPos=aX+((int)(i+nmY[p]*reflect)*pixWidth);
						if(aPos>=0 && aPos<pixSize) {A = alphaMap[aPos];}
						if(aPos<0) {A=1;}
					} else {
						A=1;
					}
										
					int cc = 0xFF000000;
					
					int bX = (int)(j-nmX[p]*refract);
					int bY = (int)(i-nmY[p]*refract);
					int bPos=bX+(bY*pixWidth);
					if(bX>=0 && bX<pixWidth) {
						if(bPos>=0 && bPos<pixSize) {cc = backMap[bPos];}
					} if (bY>=pixHeight) {cc=0xFFFFFFFF;}
					
					int rr = (cc&0xFF0000)>>16;
					int gg = (cc&0xFF00)>>8;
					int bb = (cc&0xFF);
					
					float R = rr;
					float G = gg;
					float B = bb;
					
					if(specular) {
						float dp= (float)(spec[0]*nmX[p]+spec[1]*nmY[p]+spec[2]*nmZ[p]);
						if(dp<0) {A+=dp*.3;};
					}
						
					
						R=(255*(1-A))+((.5f*R+.25f)*A);
						G=(255*(1-A))+((.5f*G+.25f)*A);
						B=(255*(1-A))+((.5f*B+.25f)*A);					
						pixels[p]=0xFF000000+((int)R<<16)+((int)G<<8)+(int)B;
					
					p++;				    
			    }
		    }	    
		    
	    
	    }
	    
}
    
}
