/*
	Project: Fractal
	Author: Simon Gayton
	Copyright 2006 (C) Simon Gayton
*/

import java.awt.*;
import java.applet.Applet;

/*
	This applet produces a fractal image using a thread
	to keep the user interaction responsive durring
	image generation.
*/
public class Fractal extends Applet implements Runnable
{
	Image backImage;
	Graphics backGraphics;
	Thread myThread = null;
	boolean render = true;
	
	/*
		Create the backbuffer for rendering.
	*/
	public void init() {
		resize(256, 256);
		backImage = createImage(256, 256);
		backGraphics = backImage.getGraphics();
	}

	/*
		Start the rendering thread.
	*/
	public void start() {
		if (myThread == null) {
			myThread = new Thread(this);
		}
		myThread.start();
	}

	/*
		This is the main loop for the rendering thread.
		It is responsible for producing the fractal image.
	*/
	public void run() {
		// Loop as long as the thread is valid.
		while (myThread != null) {
			// Only render if the render flag is set.
			if (render == true) {
				int x, y;
				Complex c = new Complex(-0.75f, 0.1f);
				Complex z = new Complex();
				
				backGraphics.setColor(new Color(0, 0, 0));
				backGraphics.fillRect(0, 0, 256, 256);
				
				// Loop over all the pixels in the image.
				for (y = 0; y < 256; ++y) {
					for (x = 0; x < 256; ++x) {
						z.set(x, y);
						
						// Iterate the algorithm for 256 steps.
						int i;
						for (i = 0; i < 256; ++i) {
							// Perform the calculation: z = z^2 + c.
							z.mult(z);
							z.add(c);
							
							// By testing for the coord being in a box rather
							// than a circle, we get the scalloped look.
							if (!z.inBox()) {
								int r = (i+1)*20;
								int g = (i+2)*20;
								int b = (i+3)*20;
								backGraphics.setColor(new Color(r%255, g%255, b%255));
								backGraphics.fillRect(x, y, 1, 1);
								break;
							}
						}
					}
				}
				
				// Display the new image.
				repaint();
				
				// Rese the render flag.
				render = false;
			}
		}
	}

	/*
		Stop the render thread.
	*/
	public void stop() {
		if (myThread != null) {
			myThread.stop();
			myThread = null;
		}
	}
	
	/*
		Display the image that is in the backbuffer.
	*/
	public void paint(Graphics g) {
		g.drawImage(backImage, 0, 0, this);
	}
	
	/*
		Respond to a keyDown event.
		Note: This method is depricated but still works.
	*/
	public boolean keyDown(Event event, int theKey) {
		// Reset the view rectangle to its default.
		Complex.setWindow(-2.0f, -2.0f, 2.0f, 2.0f);
		
		// Set the render flag to render the new image.
		render = true;
		
		// Return that the message was handled.
		return true;
	}
	
	/*
		Respond to a mouseDown event.
		Note: This method is depricated but still works.
	*/
	public boolean mouseDown(Event event, int x, int y) {
		// Zoom in to the point clicked on.
		Complex.zoom(x, y);
		
		// Set the render flag to render the new image.
		render = true;
		
		// Return that the message was handled.
		return true;
	}
}

/*
	This class represents a typical 2D complex number.
	I have added a few features that are specific to
	displaying the fractal image.
*/
class Complex
{
	// These come from a complex number represented as (a + bi).
	float a, b;
	
	// These are only used to represent the fractal viewing window.
	static float wndLeft = -2.0f;
	static float wndTop = -2.0f;
	static float wndRight = 2.0f;
	static float wndBottom = 2.0f;
	
	/*
		Default constructor.
	*/
	Complex() {
		this.a = 0.0f;
		this.b = 0.0f;
	}
	
	/*
		Construct a complex number from two reals.
	*/
	Complex(float a, float b) {
		this.a = a;
		this.b = b;
	}
	
	/*
		Set a complex number as a 2D coordinate.
		This takes into accout the view window.
	*/
	void set(int x, int y) {
		this.a = (wndRight - wndLeft) * (float)x / 255.0f + wndLeft;
		this.b = (wndBottom - wndTop) * (float)y / 255.0f + wndTop;
	}
	
	/*
		Add a complex number to this one.
	*/
	void add(Complex z) {
		this.a += z.a;
		this.b += z.b;
	}
	
	/*
		Multiply this complex number by another.
	*/
	void mult(Complex z) {
		/* (a+bi)(c+di)
		   = a*c+a*di+c*bi+b*di^2
		   = (a*c - b*d) + (a*d + b*c)
		*/
		
		// Perform the multiplication into temps.
		float a_temp = this.a * z.a - this.b * z.b;
		float b_temp = this.a * z.b + this.b * z.a;
		
		// Store the result.
		this.a = a_temp;
		this.b = b_temp;
	}
	
	/*
		Check to see if the complex number, as a coordinate, is in a
		predefined box {-2, -2, 2, 2}.
	*/
	boolean inBox() {
		if (a < -2.0f || b < -2.0f || a > 2.0f || b > 2.0f) {
			return false;
		} else {
			return true;
		}
	}
	
	/*
		Set the view window extents.
	*/
	static void setWindow(float left, float top, float right, float bottom) {
		wndLeft = left;
		wndTop = top;
		wndRight = right;
		wndBottom = bottom;
	}
	
	/*
		Zoom the view window by a scale of 2 about the given point.
	*/
	static void zoom(int x, int y) {
		float pt_x = (wndRight - wndLeft) * (float)x / 255.0f + wndLeft;
		float pt_y = (wndBottom - wndTop) * (float)y / 255.0f + wndTop;
		
		float newW = (wndRight - wndLeft) / 4.0f;
		float newH = (wndBottom - wndTop) / 4.0f;
		
		wndLeft = pt_x - newW;
		wndTop = pt_y - newH;
		wndRight = pt_x + newW;
		wndBottom = pt_y + newH;
	}
}
