/*****************************************************
 * "JawaBump" v1.0
 * Written by Jawed Karim
 * http://www.jawed.com/bump/
 *
 * You may use and distribute this source code freely.
 * If you decide to use it, please give credit to the
 * original author of this source code.
 * Always leave this disclaimer intact.
 *****************************************************/

import java.applet.Applet;
import java.awt.image.*;
import java.awt.*;
import java.awt.event.*;

public class bump extends Applet implements MouseMotionListener
{
	//normal vector
	private float normalVectors[][];

	// image pixel array
	private int imgcolors[][];

	// double buffer
	int buffer[];

	private int Width, Height;

	// mouse coords
	private int mouseX = 0, mouseY = 0;

	public void init ()
	{
		// mouse handling
		addMouseMotionListener (this);

		// load images
		String imageName = getParameter ("image");
		String bumpName = getParameter ("bumpmap");

		if (imageName == null || bumpName == null)
		{
			showStatus ("ERROR: need parameters 'image' and 'bumpmap'!");
		}

		Image img = getImage (getDocumentBase (), imageName);
		Image map = getImage (getDocumentBase (), bumpName);

		MediaTracker imageTracker = new MediaTracker (this);
		imageTracker.addImage (img, 0);
		imageTracker.addImage (map, 0);
		try {
			imageTracker.waitForAll ();
		}
		catch (InterruptedException e) {
			System.err.println("interrupted waiting for images!");
		}

		Width = img.getWidth (this);
		Height = img.getHeight (this);

		// normal vectors
		normalVectors = new float[Width * Height][3];

		// precalculate normal vectors
		FindNormalVectors (map, normalVectors);

		// color array
		imgcolors = new int[Width * Height][3];

		// fill color array
		ImageToRGBArray (img, imgcolors);

		// allocate doublebuffer
		buffer = new int[Width * Height];

		// release images
		img.flush ();
		map.flush ();
	}

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

	public void paint (Graphics g)
	{
		int x, y;
		float intensity;

		int newR, newG, newB;
		int oldR, oldG, oldB;

		for (x = 0; x < Width; x++)
		{
			for (y = 0; y < Height; y++)
			{
				intensity = BumpIntensity (mouseX, mouseY, x, y);

				// brighten slightly (ambient)
				intensity += 0.18;
				if (intensity > 1.0)
					intensity = (float)1.0;

				oldR = imgcolors[y * Width + x][0];
				oldG = imgcolors[y * Width + x][1];
				oldB = imgcolors[y * Width + x][2];

				newR = (int)(intensity * oldR);
				newG = (int)(intensity * oldG);
				newB = (int)(intensity * oldB);

				// Color c = new Color (newR, newG, newB);
				// g.setColor (c);
				// g.drawLine (x, y, x, y);

				buffer[y * Width + x] = (255 << 24) | (newR << 16) | (newG << 8) | newB;
			}
		}

		Image blitthis = createImage (new MemoryImageSource (Width, Height, buffer, 0, Width));
		g.drawImage (blitthis, 0, 0, this);
		blitthis.flush ();
	}

	void ImageToRGBArray (Image image, int RGBarray[][])
	{
		// Store RGB values into array

		int imageWidth = image.getWidth (this);
		int imageHeight = image.getHeight (this);

		int[] pixelarray = new int [imageWidth * imageHeight];
		PixelGrabber pg = new PixelGrabber (image, 0, 0, imageWidth, imageHeight, pixelarray, 0, imageWidth);

		try {
			pg.grabPixels ();
		}
		catch (InterruptedException e) {
			System.err.println("interrupted waiting for pixels!");
		}

		for (int x = 0; x < Width; x++)
		{
			for (int y = 0; y < Height; y++)
			{
				Color c = GetPixelColor (pixelarray, x, y);

				RGBarray[y * imageWidth + x][0] = (int)c.getRed ();
				RGBarray[y * imageWidth + x][1] = (int)c.getGreen ();
				RGBarray[y * imageWidth + x][2] = (int)c.getBlue ();
			}
		}
	}

	Color GetPixelColor (int[] arr, int x, int y)
	{
		int pixel = arr [y * Width + x];

		int alpha = (pixel >> 24) & 0xff;
		int red   = (pixel >> 16) & 0xff;
		int green = (pixel >>  8) & 0xff;
		int blue  = (pixel      ) & 0xff;

		return (new Color (red, green, blue));
	}

	float BumpIntensity (int lightX, int lightY, int pixelX, int pixelY)
	{
		float Nx, Ny, Nz;

		// get normal vector of map
		Nx = normalVectors[pixelY * Width + pixelX][0];
		Ny = normalVectors[pixelY * Width + pixelX][1];
		Nz = normalVectors[pixelY * Width + pixelX][2];

		// make vector from pixel to light
		float lightvX = (float)(lightX - pixelX);
		float lightvY = (float)(lightY - pixelY);
		float lightvZ = (float)(30.0f  - 0.0f);

		// normalize
		float length = (float)Math.sqrt (lightvX*lightvX + lightvY*lightvY + lightvZ*lightvZ);

		lightvX /= length;
		lightvY /= length;
		lightvZ /= length;

		// take dot product
		float intensity = (Nx * lightvX + Ny * lightvY + Nz * lightvZ);

		if (intensity < 0.0f)
			intensity = 0.0f;

		return intensity;
	}

	// fills up the array with normal vectors of the image
	void FindNormalVectors (Image imagemap, float nv[][])
	{
		PixelGrabber pg;
		int[] pixelarray;

		int imageWidth = imagemap.getWidth (this);
		int imageHeight = imagemap.getHeight (this);

		// get pixels into pixelarray
		int[] pixelarraymap = new int [imageWidth * imageHeight];

		pg = new PixelGrabber (imagemap, 0, 0, imageWidth, imageHeight, pixelarraymap, 0, imageWidth);

		try {
			pg.grabPixels ();
		}
		catch (InterruptedException e) {
			System.err.println("interrupted waiting for pixels!");
		}

		for (int x = 1; x < Width - 1; x++)
		{
			for (int y = 1; y < Height - 1; y++)
			{
				Color X0 = GetPixelColor (pixelarraymap, x + 1, y);
				Color X1 = GetPixelColor (pixelarraymap, x - 1, y);

				Color Y0 = GetPixelColor (pixelarraymap, x, y + 1);
				Color Y1 = GetPixelColor (pixelarraymap, x, y - 1);

				float Xd = (X0.getRed() - X1.getRed())
					 + (X0.getGreen() - X1.getGreen())
					 + (X0.getBlue() - X1.getBlue());

				float Yd = (Y0.getRed() - Y1.getRed())
					 + (Y0.getGreen() - Y1.getGreen())
					 + (Y0.getBlue() - Y1.getBlue());

				// maximum for Xd, Yd is: (MAX - 0) + (MAX - 0) + (MAX - 0) = 3 * MAX

				Xd /= (float)(3 * 255);
				Yd /= (float)(3 * 255);

				float Nx = Xd;
				float Ny = Yd;
				float Nz = (float)(1 - Math.sqrt ((Xd * Xd) + (Yd * Yd)));

				if (Nz < 0.0f)
					Nz = 0.0f;

				nv[y * imageWidth + x][0] = Nx;
				nv[y * imageWidth + x][1] = Ny;
				nv[y * imageWidth + x][2] = Nz;
			}
		}
	}

	public void mouseDragged (MouseEvent e) {}

	public void mouseMoved (MouseEvent e)
	{
		mouseX = e.getX();
		mouseY = e.getY();

		showStatus ("JawaBump, Copyright (c) 1999 Jawed Karim, WWW.JAWED.COM");
		repaint ();
	}
}