import java.applet.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;

/* portions of this code were adapted from the DitherTest applet */
/* noise synthesis code adapted from source code by Ken Perlin
 * and F. Kenton Musgrave to accompany:
 * Texturing and Modeling: A Procedural Approach
 * Ebert, D., Musgrave, K., Peachey, P., Perlin, K., and Worley, S.
 * AP Professional, September, 1994. ISBN 0-12-228760-6
 * Web site: http://www.cs.umbc.edu/~ebert/book/book.html
 */

public class NoiseSynthesisApplet extends Applet implements Runnable
{
    /************************************************************
     * Boilerplate methods to handle painting and initialization
     ************************************************************/

    private int width = 256;
    private int height = 256;
    private double scalefactor = 1.0;
    private int[][] heightfield;
    private Image img;
    private boolean started;

    public boolean handleEvent(Event evt)
    {
	if (evt.id == Event.WINDOW_DESTROY)  System.exit(0);
	return super.handleEvent(evt);
    }
    public boolean action( Event evt, Object arg)
    {
	return true;
    }
    String calcString = "Calculating...";
    String calcStyle = "planet";
    public void paint(Graphics g)
    {
	int w = size().width;
	int h = size().height;
	if (img == null) {
	    super.paint(g);
	    g.setColor(Color.black);
	    FontMetrics fm = g.getFontMetrics();
	    int x = (w - fm.stringWidth(calcString))/2;
	    int y = h/2;
	    g.drawString(calcString, x, y);
	} else {
	    g.drawImage(img, 0, 0, w, h, this);
	}
    }
    public void init()
    {
	int w = Integer.parseInt(getParameter("width"));
	if ((w <= 0) || (w > 256)) w=width;
	width=w;
	height=w;
	scalefactor = 256/width;
	started = false;
	calcStyle = getParameter("style");
	start();
    }
    synchronized void BuildImage()
    {
        /* build the image for display -- greyscale */
	int pixels[];
	int i, j, a, index = 0, min, max;
	// calculate range of values in heightfield
	min = heightfield[0][0];
	max = heightfield[0][0];
	for (i=0;i < width;i++)
	{
	    for (j=0;j < width;j++)
	    {
		if (heightfield[i][j] < min) min = heightfield[i][j];
		if (heightfield[i][j] > max) max = heightfield[i][j];
	    }
	}
	scalefactor = 255.0 / (max-min);
	pixels = new int[width * height];
	for (i=0;i < width;i++)
	{
	    for (j=0;j < width;j++)
	    {
		a = (int)((heightfield[i][j] - min) * scalefactor);
		if (a < 0) a=0;
		if (a > 255) a=255;
		/*if (a > 255) a=255;*/
		pixels[index++] = (255 << 24) | (a << 16) | (a << 8) | a;
	    }
	}

	img = createImage(new MemoryImageSource(width, height,
						ColorModel.getRGBdefault(),
						pixels, 0, width));
	repaint();
    }

    /************************************************************
     * Thread methods to handle processing in the background
     ************************************************************/

    Thread kicker;

    public /*synchronized*/ void start() {
	if (!started) {
	    started = true;
	    kicker = new Thread(this);
	    kicker.start();
	} else if ((kicker != null) && (kicker.isAlive()))
	    kicker.resume();
    }

    public /*synchronized*/ void stop() {
	try {
	    if ((kicker != null) && (kicker.isAlive())) {
		kicker.suspend();
	    }
	} catch (Exception e) {
	}
    }

    public void restart() {
	try {
	    if (kicker != null) {
		kicker.stop();
	    }
	} catch (Exception e) {
	}
	kicker = null;
	img = null;
	started = false;
	start();
    }

    public void run()
    {
	Thread me = Thread.currentThread();
	me.setPriority(4);
	if (calcStyle.equals("basic"))
	    DoBasicfBm(0.5, 2.0, 7.0);
	else if (calcStyle.equals("multi"))
	    DoMultifractal(0.5, 2.0, 7.0, 1.0);
	else if (calcStyle.equals("hetero"))
	    DoHeteroTerrain(0.5, 2.0, 7.0, 0.0);
	else if (calcStyle.equals("hybrid"))
	    DoHybridMultifractal(0.25, 2.0, 7.0, 0.7);
	else if (calcStyle.equals("ridged"))
	    DoRidgedMultifractal(1.0, 2.0, 7.0, 1.0, 2.0);
    }

    synchronized public void DoBasicfBm(double H, double lacunarity, double octaves)
    {
	int i,j;
	double point[] = new double[3];
	double buffer[][] = new double[width][width];
	double min, max;
	heightfield = new int[width][width];
	PerlinSolidNoiseGenerator psng = new PerlinSolidNoiseGenerator(H, lacunarity, octaves);
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    point[0] = ((double)i)/((double)width)+1.0;
	    point[1] = ((double)j)/((double)width)+1.0;
	    buffer[i][j] = psng.value(point[0], point[1], 0.5);
	}
	min = max = buffer[0][0];
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    if (min > buffer[i][j]) min = buffer[i][j];
	    if (max < buffer[i][j]) max = buffer[i][j];
	}
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    heightfield[i][j] = (int)(((buffer[i][j]-min)/(max-min))*256);
	}
	BuildImage();
    }

    synchronized public void DoMultifractal(double H, double lacunarity, 
					double octaves, double offset)
    {
	int i,j;
	double point[] = new double[3];
	double buffer[][] = new double[width][width];
	double min, max;
	heightfield = new int[width][width];
	PerlinSolidNoiseGenerator psng = new PerlinSolidNoiseGenerator(
	    PerlinSolidNoiseGenerator.METHOD_MULTIFRACTAL, H, lacunarity, octaves, offset);
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    point[0] = ((double)i)/((double)width)+1.0;
	    point[1] = ((double)j)/((double)width)+1.0;
	    buffer[i][j] = psng.value(point[0], point[1], 0.5);
	}
	min = max = buffer[0][0];
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    if (min > buffer[i][j]) min = buffer[i][j];
	    if (max < buffer[i][j]) max = buffer[i][j];
	}
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    heightfield[i][j] = (int)(((buffer[i][j]-min)/(max-min))*256);
	}
	BuildImage();
    }

    synchronized public void DoHeteroTerrain(double H, double lacunarity, 
					double octaves, double offset)
    {
	int i,j;
	double point[] = new double[3];
	double buffer[][] = new double[width][width];
	double min, max;
	heightfield = new int[width][width];
	PerlinSolidNoiseGenerator psng = new PerlinSolidNoiseGenerator(
	    PerlinSolidNoiseGenerator.METHOD_HETERO_TERRAIN, H, lacunarity, octaves, offset);
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    point[0] = ((double)i)/((double)width)+1.0;
	    point[1] = ((double)j)/((double)width)+1.0;
	    buffer[i][j] = psng.value(point[0], point[1], 0.5);
	}
	min = max = buffer[0][0];
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    if (min > buffer[i][j]) min = buffer[i][j];
	    if (max < buffer[i][j]) max = buffer[i][j];
	}
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    heightfield[i][j] = (int)(((buffer[i][j]-min)/(max-min))*256);
	}
	BuildImage();
    }
    
    synchronized public void DoHybridMultifractal(double H, double lacunarity, 
					double octaves, double offset)
    {
	int i,j;
	double point[] = new double[3];
	double buffer[][] = new double[width][width];
	double min, max;
	heightfield = new int[width][width];
	PerlinSolidNoiseGenerator psng = new PerlinSolidNoiseGenerator(
	    PerlinSolidNoiseGenerator.METHOD_HYBRID_MULTIFRACTAL, H, lacunarity, octaves, offset);
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    point[0] = ((double)i)/((double)width)+1.0;
	    point[1] = ((double)j)/((double)width)+1.0;
	    buffer[i][j] = psng.value(point[0], point[1], 0.5);
	}
	min = max = buffer[0][0];
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    if (min > buffer[i][j]) min = buffer[i][j];
	    if (max < buffer[i][j]) max = buffer[i][j];
	}
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    heightfield[i][j] = (int)(((buffer[i][j]-min)/(max-min))*256);
	}
	BuildImage();
    }
    
    synchronized public void DoRidgedMultifractal(double H, double lacunarity, 
					double octaves, double offset, double gain)
    {
	int i,j;
	double point[] = new double[3];
	double buffer[][] = new double[width][width];
	double min, max;
	heightfield = new int[width][width];
	PerlinSolidNoiseGenerator psng = new PerlinSolidNoiseGenerator(
	    H, lacunarity, octaves, offset, gain);
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    point[0] = ((double)i)/((double)width)+1.0;
	    point[1] = ((double)j)/((double)width)+1.0;
	    buffer[i][j] = psng.value(point[0], point[1], 0.5);
	}
	min = max = buffer[0][0];
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    if (min > buffer[i][j]) min = buffer[i][j];
	    if (max < buffer[i][j]) max = buffer[i][j];
	}
	for (i=0;i < width;i++) for (j=0;j < width;j++)
	{
	    heightfield[i][j] = (int)(((buffer[i][j]-min)/(max-min))*256);
	}
	BuildImage();
    }
}