/*
 * Copyright (C) 1998  Ralf Wiebicke
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.rw7;

import java.awt.Button;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
  immutable<BR>
*/
class Matrix
{
	private static final int n = 3;
	private float M[][];

	Matrix()
	{
		M = new float[n][n];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				if (i == j)
					M[i][j] = 1f;
				else
					M[i][j] = 0f;
	}

	Matrix(float a, char c)
	{
		this();
		float sin = (float) Math.sin(a);
		float cos = (float) Math.cos(a);
		switch (c)
		{
			case 'X' :
				M[1][1] = cos;
				M[2][1] = -sin;
				M[1][2] = sin;
				M[2][2] = cos;
				break;
			case 'Y' :
				M[0][0] = cos;
				M[2][0] = sin;
				M[0][2] = -sin;
				M[2][2] = cos;
				break;
			case 'Z' :
				M[0][0] = cos;
				M[1][0] = -sin;
				M[0][1] = sin;
				M[1][1] = cos;
				break;
			default :
				throw (new IllegalArgumentException());
		}
	}

	Matrix(Matrix A, Matrix B)
	{
		M = new float[n][n];
		float s;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
			{
				s = 0f;
				for (int k = 0; k < n; k++)
					s += A.M[i][k] * B.M[k][j];
				M[i][j] = s;
			}
	}

	public void transform(Spot to, Spot from)
	{
		to.X = M[0][0] * from.X + M[1][0] * from.Y + M[2][0] * from.Z;
		to.Y = M[0][1] * from.X + M[1][1] * from.Y + M[2][1] * from.Z;
		to.Z = M[0][2] * from.X + M[1][2] * from.Y + M[2][2] * from.Z;
	}

} // Matrix

/**
  Kapselung der 3D-Darstellung fuer Monitor
*/
class MonitorCanvas extends java.awt.Canvas implements Runnable
{
	boolean DrawSpots = false;
	boolean DrawLines = false;
	boolean FillSurfs = true;
	boolean DrawSurfs = true;
	boolean makeTaumel = false;
	private boolean drawChanged = true;

	private static final int Delta = 3000;

	private Model Body = new Model();
	public synchronized void setBody(Model Body)
	{
		this.Body = Body;
		updateRender();
	}
	public synchronized void computeLines()
	{
		Body = new Model(Body);
		updateRender();
	}

	private Matrix M = new Matrix();
	private Thread Render = null;
	private Image ScreenBuffer = null;

	public void start()
	{
		ScreenBuffer = createImage(getSize().width, getSize().height);
		if (Render == null)
		{
			Render = new Thread(this);
			Render.setDaemon(true);
			Render.setPriority(Render.MIN_PRIORITY);
			Render.start();
		}
	}

	public void stop()
	{
		if (Render != null)
		{
			Render.stop();
			Render = null;
		};
		ScreenBuffer = null;
	}

	public synchronized void updateRender()
	{
		drawChanged = true;
		notify();
	}

	public void paint(Graphics g)
	{
		synchronized (ScreenBuffer)
		{
			g.drawImage(ScreenBuffer, 0, 0, Color.white, null);
		}
	}

	public void update(Graphics g)
	{
		synchronized (ScreenBuffer)
		{
			g.drawImage(ScreenBuffer, 0, 0, Color.white, null);
		};
	}

	private Dimension computeSize(int Space)
	{
		final Rectangle r = getParent().getBounds();
		int i =
			Math.min(
				Math.min(r.width, r.height),
				Math.max(r.width, r.height) - Space);
		return (new Dimension(i, i));
	}

	public Dimension minimumSize()
	{
		return (computeSize(100));
	}

	public Dimension preferredSize()
	{
		return (computeSize(0));
	}

	private int fromx = 0, fromy = 0;
	public boolean mouseDown(Event e, int x, int y)
	{
		fromx = x;
		fromy = y;
		return (true);
	}
	public boolean mouseDrag(Event e, int x, int y)
	{
		if (makeTaumel)
			return (true);
		M =
			new Matrix(
				M,
				new Matrix(
					new Matrix(0.03f * (float) (fromx - x), 'Y'),
					new Matrix(0.03f * (float) (fromy - y), 'X')));
		fromx = x;
		fromy = y;
		updateRender();
		return (true);
	}

	public synchronized void run()
	{
		int BildX[] = null, BildY[] = null, BildLA[] = null, BildLB[] = null;
		Polygon BildP[] = null;
		Color SpotColor = Color.red;
		Color LineColor = Color.blue;
		Color SurfColor = Color.gray;
		Matrix YMatrix = new Matrix(0.1f, 'Y');
		float Pi2 = (float) (2 * Math.PI);
		float w = 0;
		Model Body = null;
		Matrix M = null;

		while (Render != null)
		{
			if (Body != this.Body)
			{
				Body = this.Body;
				BildX = new int[Body.Spots.length];
				BildY = new int[Body.Spots.length];
				BildLA = new int[Body.Lines.length];
				BildLB = new int[Body.Lines.length];
				for (int i = 0; i < BildLA.length; i++)
				{
					BildLA[i] = Body.Lines[i].A;
					BildLB[i] = Body.Lines[i].B;
				}
				BildP = new Polygon[Body.Surfs.length];
				for (int i = 0; i < BildP.length; i++)
				{
					BildP[i] =
						new Polygon(
							new int[Body.Surfs[i].P.length],
							new int[Body.Surfs[i].P.length],
							Body.Surfs[i].P.length);
				};
			}

			if (M != this.M || drawChanged)
			{
				M = this.M;
				drawChanged = false;
				Spot S = new Spot(0f, 0f, 0f);
				final int shx = getBounds().width / 2;
				final int shy = getBounds().height / 2;

				for (int i = 0; i < Body.Spots.length; i++)
				{
					M.transform(S, Body.Spots[i]);
					BildX[i] = (int) ((Delta * S.X) / (Delta + S.Z));
					BildY[i] = (int) (- (Delta * S.Y) / (Delta + S.Z));
					BildX[i] += shx;
					BildY[i] += shy;
				};

				if (DrawSurfs || FillSurfs)
				{
					int lP[];
					Polygon P;

					for (int i = 0; i < BildP.length; i++)
					{
						lP = Body.Surfs[i].P;
						P = BildP[i];
						for (int j = 0; j < lP.length; j++)
						{
							P.xpoints[j] = BildX[lP[j]];
							P.ypoints[j] = BildY[lP[j]];
						};
						P.npoints =
							(P.xpoints[0] * P.ypoints[1]
								- P.xpoints[1] * P.ypoints[0]
								+ P.xpoints[2] * P.ypoints[0]
								- P.xpoints[2] * P.ypoints[1]
								+ P.xpoints[1] * P.ypoints[2]
								- P.xpoints[0] * P.ypoints[2])
								< 0
								? lP.length
								: 0;
					};
				};

				synchronized (ScreenBuffer)
				{
					Graphics g = ScreenBuffer.getGraphics();
					g.setColor(Color.white);
					g.fillRect(
						0,
						0,
						ScreenBuffer.getWidth(null),
						ScreenBuffer.getWidth(null));
					if ((BildX != null) && (Body != null))
					{
						if (FillSurfs)
						{
							g.setColor(SurfColor);
							for (int i = 0; i < BildP.length; i++)
								if (BildP[i].npoints > 0)
									g.fillPolygon(BildP[i]);
						};
						if (DrawSurfs)
						{
							g.setColor(LineColor);
							for (int i = 0; i < BildP.length; i++)
								if (BildP[i].npoints > 0)
									g.drawPolygon(BildP[i]);
						};
						if (DrawLines)
						{
							g.setColor(LineColor);
							for (int i = 0; i < BildLA.length; i++)
								g.drawLine(
									BildX[BildLA[i]],
									BildY[BildLA[i]],
									BildX[BildLB[i]],
									BildY[BildLB[i]]);
						};
						if (DrawSpots)
						{
							g.setColor(SpotColor);
							for (int i = 0; i < BildX.length; i++)
								g.drawRect(BildX[i] - 3, BildY[i] - 3, 6, 6);
						};
					};
					g.dispose();
				}; // synchronized (ScreenBuffer)

				repaint();
			}

			if (makeTaumel)
				try
				{
					wait(50);
				}
				catch (InterruptedException e)
				{
				}
			else
				try
				{
					wait();
				}
				catch (InterruptedException e)
				{
				};

			if (makeTaumel)
			{
				if ((w += 0.03f) > Pi2)
					w -= Pi2;
				this.M =
					new Matrix(
						this.M,
						new Matrix(
							new Matrix(0.3f * (float) Math.sin(w), 'X'),
							YMatrix));
			};

		}
	}

} // class MonitorCanvas

/**
  3D-Darstellung von r&auml;umlichen Objekten<BR>
  frei nach dem Kurs "Vektorgrafik" der DOS International<BR>
  Ausgabe '92/7 bis '92/10<BR>
  &Uuml;berarbeitung 1993 objektorientiert in Turbo-Pascal<BR>
  &Uuml;berarbeitung 1996 als Java-Applet<BR>
  <BR>
  &copy; 1992,96 by Ralf Wiebicke<BR>
*/
public class Monitor extends java.applet.Applet implements Runnable
{
	MonitorCanvas P = null;
	Choice ModelChoice = null;
	Checkbox an, dS, dL, fF, dF;
	static String compute = "LineBySurf";
	static String Models[] =
		{ "Pyramide", "knoxS.obj", "dinasaur.obj", "cube.obj", "hughes_500.obj" };

	public void init()
	{
		System.out.println("getDocumentBase" + getDocumentBase());
		setLayout(new FlowLayout(FlowLayout.LEFT));
		add(P = new MonitorCanvas());

		ModelChoice = new Choice();
		for (int i = 0; i < Models.length; i++)
			ModelChoice.addItem(Models[i]);
		add(ModelChoice);

		add(an = new Checkbox("Taumel", null, false));
		add(dS = new Checkbox("DrawSpots", null, false));
		add(dL = new Checkbox("DrawLines", null, false));
		add(fF = new Checkbox("FillSurfs", null, true));
		add(dF = new Checkbox("DrawSurfs", null, true));

		add(new Button(compute));
	}

	synchronized public void start()
	{
		P.start();
	}

	synchronized public void stop()
	{
		P.stop();
	}

	synchronized public boolean action(Event evt, Object arg)
	{
		if (evt.target.equals(ModelChoice))
			if (evt.arg == Models[0])
			{
				P.setBody(new Model());
				showStatus(evt.arg + " geladen");
			}
			else
				loadModel((String) evt.arg);

		if (evt.target instanceof Button)
			if (evt.arg == compute)
				P.computeLines();
			else
				return (false);

		if (evt.target instanceof Checkbox)
		{
			boolean b = ((Boolean) arg).booleanValue();
			if (evt.target == dS)
				P.DrawSpots = b;
			else if (evt.target == dL)
				P.DrawLines = b;
			else if (evt.target == fF)
				P.FillSurfs = b;
			else if (evt.target == dF)
				P.DrawSurfs = b;
			else if (evt.target == an)
				P.makeTaumel = b;
			else
				return (false);
		}
		P.updateRender();
		return (true);
	}

	Thread LoadThread = null;
	String LoadString = null;

	public void loadModel(String s)
	{
		if (LoadThread == null)
		{
			LoadString = s;
			LoadThread = new Thread(this);
			LoadThread.start();
		};
	}

	public void run()
	{
		Model newBody = null;
		try
		{
			URL src = new URL(getDocumentBase(), LoadString);
			showStatus("Lade " + src);
			newBody = new Model(src.openStream());
		}
		catch (MalformedURLException e)
		{
			showStatus("Fehler: " + e);
		}
		catch (IOException e)
		{
			showStatus("Fehler: " + e);
		};

		if (newBody != null)
		{
			P.setBody(newBody);
			showStatus(LoadString + " geladen");
		}
		LoadThread = null;
	}

} // class Monitor
