import java.awt.*;
import java.applet.Applet;
import java.awt.image.PixelGrabber;
import java.awt.image.MemoryImageSource;

public class HawkRay extends Applet implements Runnable {
	private int w, h, dispx = 0, dispz = 0, m_sw, m_sh, texel[], m_cos1, m_sin1, xres, yres;
	private int screenPixel[], m_alti = 120, d = 1, viewA = 360, dViewA = 3;
	private int paintCnt = 0, garbageCnt = 0;
	private MemoryImageSource screenMem;
	private Thread theThread;
	private Image screenImage;
	private boolean m_tAnimate = true;

//  Initialisation code

	public void init() {
		MediaTracker tracker = new MediaTracker(this);
		Image piccy = getImage(getDocumentBase(), getParameter("image"));
		m_sh = size().height << 2; m_sw = size().width;
		screenPixel = new int[m_sw * m_sh >> 2];
		screenMem = new MemoryImageSource(m_sw, m_sh >> 2, screenPixel, 0, m_sw);
		tracker.addImage(piccy, 0);
		try tracker.waitForID(0); catch(Exception e) {}
		w = piccy.getWidth(this); h = piccy.getHeight(this);
		texel = new int[w * h];
		PixelGrabber pixelGrabber = new PixelGrabber(piccy.getSource(),
			0, 0, w, h, texel, 0, w);
		try pixelGrabber.grabPixels(); catch(Exception e) return;
		piccy = null;
	}

// Running section

	public void start() {
		(theThread = new Thread(this)).start();
	}

	public void stop() {
		if (theThread != null) {
			theThread.stop();
			theThread = null;
		}
	}

	public void run() {
		while (theThread != null) {
			if ((paintCnt++ < 5) && m_tAnimate) {
				if (viewA > 720)
					dViewA = -1;
				else if (viewA < 360)
					dViewA = 1;
				viewA += dViewA;
				renderImage();
				repaint();
				try Thread.sleep(50); catch(InterruptedException e) {}
			}
			else
				try Thread.sleep(1000); catch(InterruptedException e) {}
			if (garbageCnt++ > 120) {
				System.gc ();
				garbageCnt = 0;
			}
		}
	}

// Mouse events

	public boolean mouseDown(Event evt, int x, int y) {
		m_tAnimate = !m_tAnimate;
		return true;
	}

// Painting section

	public void update(Graphics g) {
		paint(g);
	}

	public void paint(Graphics g) {
		paintCnt = 0;
		if (screenImage != null)
			g.drawImage(screenImage, 0, 0, this);
	}

//  Rendering section

	private void renderImage() {
		int x1, y1, x2, y2, x3, y3, x4, y4, va, p = 0, xinc, yinc;
		m_alti = (int)(240.0 - 120.0 * Math.cos((double)viewA * 3.1415926 / 180.0));
		m_sin1 = (int)((1 << 10) * Math.sin((double)viewA * 3.1415926 / 180.0));
		m_cos1 = (int)((1 << 10) * Math.cos((double)viewA * 3.1415926 / 180.0));
		va = (w * (viewA % 360) / 360);
		dispz += m_cos1 << 3;
		dispx += m_sin1 << 3;
		for (int y = ((3 * m_sh) >> 2) + 1; y <= m_sh; y++) {
			planePoint (0, y); x1 = xres; y1 = yres;
			planePoint (m_sw - 1, y); x2 = xres; y2 = yres;
			xinc = (x2 - x1) / (m_sw - 1);
			yinc = (y2 - y1) / (m_sw - 1);
			xinc = ((xinc % (w << 10)) + (w << 10)) % (w << 10);
			yinc = ((yinc % (h << 10)) + (h << 10)) % (h << 10);
			for (int x = 0; x < m_sw; x++) {
				x3 = (x1 >> 10) % w;
				if (x3 < 0)
					x3 += w;
				y3 = (y1 >> 10) % h;
				if (y3 < 0)
					y3 += h;
				screenPixel[p++] = texel[y3 * w + x3];
				x1 += xinc; y1 += yinc;
			}
		}
		screenImage = createImage(screenMem);
	}

	private void planePoint (int i, int j) {
		double x1, y1, y2, x2;
		y1 = ((double)(j << 1) / (double)m_sh) - 1.0;
		y2 = (double)(d * (m_alti - y1)) / y1;
		x1 = (double)(i - (m_sw >> 1)) / (double)m_sw;
		x2 = x1 * (y2 + d) / (double)d;
		xres = (int)(m_cos1 * x2 + m_sin1 * y2) + dispx;
		yres = (int)(-m_sin1 * x2 + m_cos1 * y2) + dispz;
	}
}
