/*
 * The Triangulator - a java applet which animates some brute force
 * Delaunay triangulation algorithms of varying computational 
 * complexity.  
 * 
 * Author: Geoff Leach.  gl@cs.rmit.edu.au
 * Date: 29/3/96
 *
 * License to copy and use this software is granted provided that
 * appropriate credit is given to both RMIT and the author.
 *
 * The point of the applet is educational: 
 *
 * (a) To illustrate the importance of computational complexity.
 * The three Delaunay triangulation algorithms implemented have
 * computational complexities of O(n^2), O(n^3) and O(n^4).  The
 * user can see for themselves the improvement in speed going 
 * from O(n^4) to O(n^2).
 * 
 * (b) To illustrate what Delaunay triangulations are.  The Delaunay
 * triangulation, and the related Voronoi diagram, is a 
 * particularly useful data structure for a number of problems.
 * Without going into the applications the applet attempts to 
 * show what Delaunay triangulation algorithms are.
 *
 * (c) To provide a useful context for me to initially learn Java.
 *
 * The code still needs polishing ... 
 * 
 */

package org.das2.math;

import java.applet.*;
import java.awt.*;
import java.io.PrintStream;

/* 
 * Sometimes we just have to die. 
 */
class Panic {
    static public void panic(String m) {
	System.err.println(m);
	System.exit(1);
    }
}

/* 
 * Wrapper class for basic int type for pass by reference.
 */
class Int {
    int i;

    public void Int() {
	i = 0;
    }

    public void Int(int i) {
	this.i = i;
    }

    public void setValue(int i) {
	this.i = i;
    }

    public int getValue() {
	return i;
    }
}

/*
 * Point class.  RealPoint to avoid clash with java.awt.Point.
 */
class RealPoint {
    float x, y;

    RealPoint() { x = y = 0.0f; }
    RealPoint(float x, float y) { this.x = x; this.y = y; }
    RealPoint(RealPoint p) { x = p.x; y = p.y; }
    public float x() { return this.x; }
    public float y() { return this.y; }
    public void set(float x, float y) { this.x = x; this.y = y; }

    public float distance(RealPoint p) {
	float dx, dy;

	dx = p.x - x;
	dy = p.y - y;
	return (float)Math.sqrt((double)(dx * dx + dy * dy));
    }

    public float distanceSq(RealPoint p) { 
	float dx, dy;

	dx = p.x - x;
	dy = p.y - y;
	return (float)(dx * dx + dy * dy);
    }
}

/* 
 * Edge class. Edges have two vertices, s and t, and two faces,
 * l (left) and r (right). The triangulation representation and
 * the Delaunay triangulation algorithms require edges.
 */
class Edge {
    int s, t;
    int l, r;

    Edge() { s = t = 0; }
    Edge(int s, int t) { this.s =s; this.t = t; }
    int s() { return this.s; }
    int t() { return this.t; }
    int l() { return this.l; }
    int r() { return this.r; }
}


/*
 * Vector class.  A few elementary vector operations.
 */
class Vector {
    float u, v;

    Vector() { u = v = 0.0f; }
    Vector(RealPoint p1, RealPoint p2) { 
	u = p2.x() - p1.x(); 
	v = p2.y() - p1.y(); 
    }
    Vector(float u, float v) { this.u = u; this.v = v; }

    float dotProduct(Vector v) { return u * v.u + this.v * v.v; }

    static float dotProduct(RealPoint p1, RealPoint p2, RealPoint p3) { 
	float u1, v1, u2, v2;

	u1 =  p2.x() - p1.x();
	v1 =  p2.y() - p1.y();
	u2 =  p3.x() - p1.x();
	v2 =  p3.y() - p1.y();

	return u1 * u2 + v1 * v2;
    }

    float crossProduct(Vector v) { return u * v.v - this.v * v.u; }

    static float crossProduct(RealPoint p1, RealPoint p2, RealPoint p3) { 
	float u1, v1, u2, v2;

	u1 =  p2.x() - p1.x();
	v1 =  p2.y() - p1.y();
	u2 =  p3.x() - p1.x();
	v2 =  p3.y() - p1.y();

	return u1 * v2 - v1 * u2;
    }

    void setRealPoints(RealPoint p1, RealPoint p2) { 
	u = p2.x() - p1.x(); 
	v = p2.y() - p1.y(); 
    }
}

/*
 * Circle class. Circles are fundamental to computation of Delaunay 
 * triangulations.  In particular, an operation which computes a 
 * circle defined by three points is required.
 */
class Circle {
    RealPoint c;
    float r;

    Circle() { c = new RealPoint(); r = 0.0f; }
    Circle(RealPoint c, float r) { this.c = c; this.r = r; }
    public RealPoint center() { return c; }
    public float radius() { return r; }
    public void set(RealPoint c, float r) { this.c = c; this.r = r; }

    /* 
     * Tests if a point lies inside the circle instance.
     */
    public boolean inside(RealPoint p) {
	if (c.distanceSq(p) < r * r)
	    return true;
	else
	    return false;
    }

    /* 
     * Compute the circle defined by three points (circumcircle). 
     */
    public void circumCircle(RealPoint p1, RealPoint p2, RealPoint p3) {
	float cp;

	cp = Vector.crossProduct(p1, p2, p3);
	if (cp != 0.0)
	    {
		float p1Sq, p2Sq, p3Sq;
		float num, den;
		float cx, cy;

		p1Sq = p1.x() * p1.x() + p1.y() * p1.y();
		p2Sq = p2.x() * p2.x() + p2.y() * p2.y();
		p3Sq = p3.x() * p3.x() + p3.y() * p3.y();
		num = p1Sq*(p2.y() - p3.y()) + p2Sq*(p3.y() - p1.y()) + p3Sq*(p1.y() - p2.y());
		cx = num / (2.0f * cp);
		num = p1Sq*(p3.x() - p2.x()) + p2Sq*(p1.x() - p3.x()) + p3Sq*(p2.x() - p1.x());
		cy = num / (2.0f * cp);

		c.set(cx, cy);
	    }
    
	// Radius 
	r = c.distance(p1);
    }
}

/*
 * Triangulation class.  A triangulation is represented as a set of
 * points and the edges which form the triangulation.
 */
class Triangulation {
    static final int Undefined = -1;
    static final int Universe = 0;
    int nPoints;
    RealPoint point[];
    int nEdges;
    int maxEdges;
    Edge edge[];

    Triangulation(int nPoints) {

	// Allocate points. 
	this.nPoints = nPoints;
	this.point = new RealPoint[nPoints];
	for (int i = 0; i < nPoints; i++)
	    point[i] = new RealPoint();

	// Allocate edges. 
	maxEdges = 3 * nPoints - 6;	// Max number of edges.
	edge = new Edge[maxEdges];
	for (int i = 0; i < maxEdges; i++)
	    edge[i] = new Edge();
	nEdges = 0;
    }

    /*
     * Sets the number of points in the triangulation.  Reuses already
     * allocated points and edges.
     */
    public void setNPoints(int nPoints) {
	// Fix edge array. 
	Edge tmpEdge[] = edge;
	int tmpMaxEdges = maxEdges;
	maxEdges = 3 * nPoints - 6;	// Max number of edges.
	edge = new Edge[maxEdges];

	// Which is smaller? 
	int minMaxEdges;
	if (tmpMaxEdges < maxEdges) 
	    minMaxEdges = tmpMaxEdges;
	else
	    minMaxEdges = maxEdges;

	// Reuse allocated edges. 
	for (int i = 0; i < minMaxEdges; i++)
	    this.edge[i] = tmpEdge[i];

	// Get new edges. 
	for (int i = minMaxEdges; i < maxEdges; i++)
	    this.edge[i] = new Edge();

	// Fix point array. 
	RealPoint tmpPoint[] = point;
	point = new RealPoint[nPoints];

	// Which is smaller? 
	int minPoints;
	if (nPoints < this.nPoints) 
	    minPoints = nPoints;
	else
	    minPoints = this.nPoints;

	// Reuse allocated points. 
	for (int i = 0; i < minPoints; i++)
	    this.point[i] = tmpPoint[i];

	// Get new points. 
	for (int i = minPoints; i < nPoints; i++)
	    this.point[i] = new RealPoint();

	this.nPoints = nPoints;
    }

    /* 
     * Generates a set of random points to triangulate. 
     */
    public void randomPoints(RealWindow w) {
	for (int i = 0; i < nPoints; i++)
	    {
		point[i].x = (float)Math.random() * w.xMax();
		point[i].y = (float)Math.random() * w.yMax();
	    }
	nEdges = 0;
    }

    /* 
     * Copies a set of points.
     */
    public void copyPoints(Triangulation t) {
	int n;

	if (t.nPoints < nPoints)
	    n = t.nPoints;
	else 
	    n = nPoints;

	for (int i = 0; i < n; i++) {
	    point[i].x = t.point[i].x;
	    point[i].y = t.point[i].y;
	}

	nEdges = 0;
    }

    void addTriangle(int s, int t, int u) {
	addEdge(s, t);
	addEdge(t, u);
	addEdge(u, s);
    }

    public int addEdge(int s, int t) {
	return addEdge(s, t, Undefined, Undefined);
    }

    /* 
     * Adds an edge to the triangulation. Store edges with lowest
     * vertex first (easier to debug and makes no other difference).
     */
    public int addEdge(int s, int t, int l, int r) {
	int e;

	// Add edge if not already in the triangulation. 
	e = findEdge(s, t);
	if (e == Undefined) 
	    if (s < t)
		{
		    edge[nEdges].s = s;
		    edge[nEdges].t = t;
		    edge[nEdges].l = l;
		    edge[nEdges].r = r;
		    return nEdges++;
		} 
	    else
		{
		    edge[nEdges].s = t;
		    edge[nEdges].t = s;
		    edge[nEdges].l = r;
		    edge[nEdges].r = l;
		    return nEdges++;
		}
	else
	    return Undefined;
    }

    public int findEdge(int s, int t) {
	boolean edgeExists = false;
	int i;

	for (i = 0; i < nEdges; i++)
	    if (edge[i].s == s && edge[i].t == t || 
		edge[i].s == t && edge[i].t == s) {
		edgeExists = true;
		break;
	    }

	if (edgeExists)
	    return i;
	else
	    return Undefined;
    }

    /* 
     * Update the left face of an edge. 
     */
    public void updateLeftFace(int eI, int s, int t, int f) {
	if (!((edge[eI].s == s && edge[eI].t == t) ||
	      (edge[eI].s == t && edge[eI].t == s)))
	    Panic.panic("updateLeftFace: adj. matrix and edge table mismatch");
	if (edge[eI].s == s && edge[eI].l == Triangulation.Undefined)
	    edge[eI].l = f;
	else if (edge[eI].t == s && edge[eI].r == Triangulation.Undefined)
	    edge[eI].r = f;
	else
	    Panic.panic("updateLeftFace: attempt to overwrite edge info");
    }

    public void draw(RealWindowGraphics rWG, Color pC, Color eC) {
	drawPoints(rWG, pC);
	drawEdges(rWG, eC);
    }

    public void drawPoints(RealWindowGraphics rWG, Color c) {
	for (int i = 0; i < nPoints; i++)
	    rWG.drawPoint(point[i], c);
    }

    public void drawEdges(RealWindowGraphics rWG, Color c) {
	for (int i = 0; i < nEdges; i++)
	    drawEdge(rWG, edge[i], c);
    }

    public void drawEdge(RealWindowGraphics rWG, Edge e, Color c) {
	rWG.drawLine(point[e.s], point[e.t], c);
    }

    public void print(PrintStream p) {
	printPoints(p);
	printEdges(p);
    }

    public void printPoints(PrintStream p) {
	for (int i = 0; i < nPoints; i++)
	    p.println(String.valueOf(point[i].x) + " " + String.valueOf(point[i].y));
    }

    public void printEdges(PrintStream p) {
	for (int i = 0; i < nEdges; i++)
	    p.println(String.valueOf(edge[i].s) + " " + String.valueOf(edge[i].t));
    }
}

/* 
 * Rectangle class.  Need rectangles for window to viewport mapping.
 */
class RealRectangle {
    RealPoint ll;
    RealPoint ur;

    RealRectangle() { }

    RealRectangle (RealRectangle r) {
	this.ll = new RealPoint(r.ll);
	this.ur = new RealPoint(r.ur);
    }

    RealRectangle (RealPoint ll, RealPoint ur) {
	this.ll = new RealPoint(ll);
	this.ur = new RealPoint(ur);
    }

    RealRectangle(float xMin, float yMin, float xMax, float yMax) {
	this.ll = new RealPoint(xMin, yMin);
	this.ur = new RealPoint(xMax, yMax);
    }

    public float width() { return ur.x() - ll.x(); }
    public float height() { return ur.y() - ll.y(); }

    public RealPoint ll() { return ll; }
    public RealPoint ur() { return ur; }

    public float xMin() { return ll.x; }
    public float yMin() { return ll.y; }

    public float xMax() { return ur.x; }
    public float yMax() { return ur.y; }
}

/* 
 * A window is essentially a rectangle. 
 */
class RealWindow extends RealRectangle {
    RealWindow() {}
    RealWindow(float xMin, float yMin, float xMax, float yMax) {
	super(xMin, yMin, xMax, yMax); 
    }
    RealWindow(RealWindow w) { super(w.ll(), w.ur()); }
}

/*
 * RealWindowGraphics class. Has a window, a viewport and a 
 * graphics context into which to draw.  The graphics context
 * is only set after calls to repaint result in calls to update.
 * Contains drawing operations, drawTriangle for example, needed 
 * elsewhere.
 */
class RealWindowGraphics {
    RealWindow w = null;	// window
    Dimension v = null;	// viewport
    Graphics g = null;
    float scale = 1.0f;

    static final float realPointRadius = 0.04f;
    static final int pixelPointRadius = 4;
    static final int halfPixelPointRadius = 2;

    RealWindowGraphics(RealWindow w) {
	this.w = new RealWindow(w);
    }
    
    RealWindowGraphics(RealWindow w, Dimension d, Graphics g) {
	this.w = new RealWindow(w);
	this.v = new Dimension(d.width, d.height);
	this.g = g;
	calculateScale();
    }

    public void setWindow(RealWindow w) {
	this.w = new RealWindow(w);
	calculateScale();
    }

    public void setViewport(Dimension d) {
	this.v = new Dimension(d.width, d.height);
	calculateScale();
    }

    public void setGraphics(Graphics g) {
	this.g = g;
    }

    public Graphics getGraphics(Graphics g) {
	return g;
    }

    public void calculateScale() {
	float sx, sy;

	sx = v.width / w.width();
	sy = v.height / w.height();

	if (sx < sy)
	    scale = sx;
	else
	    scale = sy;
    }

    public void drawTriangle(RealPoint p1, 
			     RealPoint p2, 
			     RealPoint p3, 
			     Color c) { 
	drawLine(p1, p2, c);
	drawLine(p2, p3, c);
	drawLine(p3, p1, c);
    }

    public void drawLine(RealPoint p1, RealPoint p2, Color c) { 
	int x1, y1, x2, y2;

	g.setColor(c);
	x1 = (int)(p1.x() * scale);
	y1 = (int)(p1.y() * scale);
	x2 = (int)(p2.x() * scale);
	y2 = (int)(p2.y() * scale);
	g.drawLine(x1, y1, x2, y2);
    }

    public void drawPoint(RealPoint p, Color c) {
	g.setColor(c);

	g.fillOval((int)(scale * p.x()) - halfPixelPointRadius, 
		   (int)(scale * p.y()) - halfPixelPointRadius, 
		   pixelPointRadius, pixelPointRadius);
    }

    public void drawCircle(Circle circle, Color c) {
	drawCircle(circle.center().x(), circle.center().y(), circle.radius(), c);
    }

    public void drawCircle(RealPoint p, float r, Color c) {
	drawCircle(p.x(), p.y(), r, c);
    }

    public void drawCircle(float x, float y, float r, Color c) {
	g.setColor(c);

	g.drawOval((int)(scale * (x - r)), (int)(scale * (y - r)), 
		   (int)(2.0f * r * scale), (int)(2.0f * r * scale));
    }

    public void fillCircle(float x, float y, float r, Color c) {
	g.setColor(c);

	g.fillOval((int)(scale * (x - r)), (int)(scale * (y - r)), 
		   (int)(2.0f * r * scale), (int)(2.0f * r * scale));
    }
}

/*
 * AlgorithmUIHeading class. Provides a heading for part of the user
 * interface. 
 */
class AlgorithmUIHeading extends Panel {

    public AlgorithmUIHeading() {
	// Headings. 
	setLayout(new GridLayout(0,7));
	add(new Label("Algorithm", Label.LEFT));
	add(new Label("Run", Label.LEFT));
	add(new Label("Points", Label.LEFT));
	add(new Label("Triangles", Label.LEFT));
	add(new Label("Circles", Label.LEFT));
	add(new Label("Points", Label.LEFT));
	add(new Label("Pause (mS)", Label.LEFT));
    }
}

/* 
 * TriangulationCanvas class. Each of the triangulation algorithms
 * needs a canvas to draw into.
 */
@SuppressWarnings("deprecation")
class TriangulationCanvas extends Canvas {
    Triangulation t;
    RealWindowGraphics rWG;	// Does the actual drawing. 
    boolean needToClear = false;
    boolean newPoints = false;
    TriangulationAlgorithm alg;	// The algorithm which uses this canvas. 

    TriangulationCanvas(Triangulation t, 
			RealWindow w, 
			TriangulationAlgorithm alg) {
	this.t = t;
	rWG = new RealWindowGraphics(w);
	this.alg = alg;
    }

    public Insets insets() {
	return new Insets(2,10,2,15);
    }

    public void paint(Graphics g) {
	if (needToClear) {
	    g.clearRect(0, 0, size().width, size().height);
     
	    needToClear = false;
	}
	g.drawRect(0, 0, size().width-1, size().height-1);
	rWG.setGraphics(g);
	rWG.setViewport(size());
	alg.draw(rWG, t);
    }

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

/* 
 * AlgorithmUI class.  Each algorithm has a set of user interface
 * controls.  This class provides them.
 */
@SuppressWarnings("deprecation")
class AlgorithmUI extends Panel {
    TextField nPointsTextField;
    Checkbox animateCheckBox[];
    Checkbox runCheckBox;
    TextField pauseTextField;
    TriangulationAlgorithm algorithm;  // Algorithm which uses this UI. 

    public AlgorithmUI(TriangulationAlgorithm algorithm,
		       String label, int nPoints, int pause) {

	this.algorithm = algorithm;

	// One set of controls per algorithm. 
	setLayout(new GridLayout(0,7));
	add(new Label(label, Label.LEFT));
	add(runCheckBox = new Checkbox(null, null, true));
	add(nPointsTextField = new TextField(String.valueOf(nPoints), 5));
	animateCheckBox = new Checkbox[AnimateControl.nEntities];
	animateCheckBox[AnimateControl.triangles] = new Checkbox(null, null, true);
	add(animateCheckBox[AnimateControl.triangles]);
	animateCheckBox[AnimateControl.circles] = new Checkbox(null, null, true);
	add(animateCheckBox[AnimateControl.circles]);
	animateCheckBox[AnimateControl.points] = new Checkbox(null, null, true);
	add(animateCheckBox[AnimateControl.points]);
	pauseTextField = new TextField(String.valueOf(pause), 5);
	add(pauseTextField);
    }

    public void setAlgorithm(TriangulationAlgorithm algorithm) {
	this.algorithm = algorithm;
    }

    // Gets the current value in a text field.  
    int getValue(TextField tF) {
	int i;
	try {
	    i = Integer.valueOf(tF.getText()).intValue(); 
	} catch (java.lang.NumberFormatException e) {
	    i = 0;
	}
	return i;
    }

    public boolean handleEvent(Event evt) {
	if (evt.id == Event.ACTION_EVENT) {
	    if (evt.target == runCheckBox) {
		algorithm.control().setRun(((Boolean)evt.arg).booleanValue());
		return true;
	    } else if (evt.target == animateCheckBox[AnimateControl.triangles]) {
		algorithm.control().setAnimate(AnimateControl.triangles, 
					       ((Boolean)evt.arg).booleanValue());
		return true;
	    } else if (evt.target == animateCheckBox[AnimateControl.circles]) {
		algorithm.control().setAnimate(AnimateControl.circles, 
					       ((Boolean)evt.arg).booleanValue());
		return true;
	    } else if (evt.target == animateCheckBox[AnimateControl.points]) {
		algorithm.control().setAnimate(AnimateControl.points, 
					       ((Boolean)evt.arg).booleanValue());
		return true;
	    } else if (evt.target == pauseTextField) {
		algorithm.control().setPause(getValue(pauseTextField));
		return true;
	    } else if (evt.target == nPointsTextField) {
		algorithm.control().nPoints = getValue(nPointsTextField);
		return true;
	    }
	}

	return false;
    }
}

/*
 * AnimateControl class.  Each algorithm animation has various entities
 * which can be displayed.  This class provides the state which controls
 * what is being displayed.  It is manipulated by AlgorithmUI and 
 * accessed by the animation routines in each algorithm.
 */
class AnimateControl {
    TriangulationAlgorithm triAlg;
    static final int automatic = 0;
    static final int manual = 1;
    int animateMode = automatic;
    int pause = 10;
    static final int algorithm = 0;
    static final int triangles = 1;
    static final int points = 2;
    static final int circles = 3;
    static final int nEntities = 4;
    boolean run;
    boolean animate[];
    int nPoints;

    AnimateControl(TriangulationAlgorithm algorithm) {
	triAlg = algorithm;
	animate = new boolean[nEntities];
	for (int i = 0; i < nEntities; i++)
	    animate[i] = true;
	run = true;
    }

    AnimateControl(TriangulationAlgorithm algorithm, int nPoints) {
	this(algorithm);
	this.nPoints = nPoints;
    }

    public void setAnimate(int entity, boolean v) {
	animate[entity] = v;
	if (!v)
	    triAlg.canvas().needToClear = true;
    }

    public boolean animate(int entity) {
	return animate[entity];
    }

    public int mode() {
	return animateMode;
    }

    public void setManualAnimateMode() {
	animateMode = manual;
    }

    public void setAutomaticAnimateMode() {
	animateMode = automatic;
    }

    public int getPause() {
	return pause;
    }

    public void setPause(int p) {
	pause = p;
    }

    public int getNPoints() {
	return nPoints;
    }

    public void setNPoints(int n) {
	nPoints = n;
    }

    public void setRun(boolean v) {
	run = v;
    }

    public boolean getRun() {
	return run;
    }
}

/*
 * TriangulationAlgorithm class.  Absract.  Superclass for
 * actual algorithms.  Has several abstract function members -
 * including the triangulation member which actually computes
 * the triangulation.
 */
abstract class TriangulationAlgorithm {
    String algName;
    TriangulationCanvas triCanvas;
    AnimateControl aniControl;
    AlgorithmUI algorithmUI;

    // Variables and constants for animation state. 
    final int nStates = 5;
    boolean state[] = new boolean[nStates];
    static final int triangulationState = 0;
    static final int pointState = 1;
    static final int triangleState = 2;
    static final int insideState = 4;
    static final int edgeState = 5;

    public TriangulationAlgorithm(Triangulation t, RealWindow w, 
				  String name, int nPoints) {
	algName = name;
	aniControl = new AnimateControl(this, nPoints);
	algorithmUI = new AlgorithmUI(this, name, nPoints, aniControl.getPause());
	triCanvas = new TriangulationCanvas(t, w, this);

	for (int s = 0; s < nStates; s++)
	    state[s] = false;
	triCanvas.needToClear = true;
    }

    public void setCanvas(TriangulationCanvas tc) {
	triCanvas = tc;
    }

    public AnimateControl control() {
	return aniControl;
    }

    public AlgorithmUI algorithmUI() {
	return algorithmUI;
    }

    public TriangulationCanvas canvas() {
	return triCanvas;
    }

    public void setAlgorithmState(int stateVar, boolean value) {
	state[stateVar] = value;
    }

    public void pause() {
	if (aniControl.mode() == AnimateControl.automatic)
	    try {
		wait(aniControl.getPause());
	    } catch (InterruptedException e){}
	else
	    try {wait();} catch (InterruptedException e){}
    }

    public void animate(int state) {
	if ((aniControl.animate(AnimateControl.triangles) ||
	     aniControl.animate(AnimateControl.circles)) && 
	    state == triangulationState)
	    triCanvas.needToClear = true;

	setAlgorithmState(state, true);

	triCanvas.repaint();
    
	pause();
    
	setAlgorithmState(state, false);
    }

    public void reset() {
	for (int s = 0; s < nStates; s++)
	    state[s] = false;
	triCanvas.needToClear = true;
    }

    public synchronized void nextStep() { notify(); }
    abstract public void triangulate(Triangulation t);
    abstract public void draw(RealWindowGraphics rWG, Triangulation t);
}

/* 
 * QuarticAlgorithm class.  O(n^4) algorithm. The most brute-force 
 * of the algorithms.
 */
class QuarticAlgorithm extends TriangulationAlgorithm {
    int i, j, k, l;
    Circle c = new Circle();
    final static String algName = "O(n^4)";

    public QuarticAlgorithm(Triangulation t, RealWindow w, int nPoints) {
	super(t, w, algName, nPoints);
    }

    public void reset() {
	i = j = k = l = 0;
	super.reset();
    }

    public void draw(RealWindowGraphics rWG, Triangulation t) {
	if (state[triangleState]) {
	    if (aniControl.animate(AnimateControl.triangles))
		rWG.drawTriangle(t.point[i], t.point[j], t.point[k], Color.green);
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(c, Color.green);
	} else if (state[pointState]) {
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(t.point[l], Color.orange);
	} else if (state[insideState]) {
	    if (aniControl.animate(AnimateControl.triangles))
		rWG.drawTriangle(t.point[i], t.point[j], t.point[k], Color.red);
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(c, Color.red);
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(t.point[l], Color.red);
	} else if (state[triangulationState]) {
	    t.draw(rWG, Color.black, Color.black);
	} else {
	    t.draw(rWG, Color.black, Color.black);
	}
    }

    public synchronized void triangulate(Triangulation t) {
	boolean pointFree;
	int n = t.nPoints;
	RealPoint p[] = t.point;

	for (i = 0; i < n-2; i++)
	    for (j = i + 1; j < n-1; j++)
		if (j != i)
		    for (k = j + 1; k < n; k++)
			if (k != i && k != j)
			    { 
				c.circumCircle(p[i], p[j], p[k]);
				animate(triangleState);
				pointFree = true;
				for (l = 0; l < n; l++)
				    if (l != i && l != j && l != k) {
					animate(pointState);
					if (c.inside(p[l])) {
					    animate(insideState);
					    pointFree = false;
					    break;
					}
				    }

				if (pointFree)
				    t.addTriangle(i, j, k);
		
				animate(triangulationState);
			    }
    }
}

/* 
 * CubicAlgorithm class.  O(n^3) algorithm. 
 */
class CubicAlgorithm extends TriangulationAlgorithm {
    int s, t, u, i;
    Circle bC = new Circle();
    final static String algName = "O(n^3)";
    int nFaces;

    public CubicAlgorithm(Triangulation t, RealWindow w, int nPoints) {
	super(t, w, algName, nPoints);
    }

    public void reset() {
	nFaces = 0;
	triCanvas.needToClear = true;
	super.reset();
    }

    public void draw(RealWindowGraphics rWG, Triangulation tri) {
	if (state[triangleState]) {
	    if (aniControl.animate(AnimateControl.triangles)) {
		rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[u], 
				 Color.green);
		rWG.drawLine(tri.point[s], tri.point[t], Color.blue);
	    }
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(bC, Color.green);
	} else if (state[pointState]) {
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(tri.point[i], Color.orange);
	} else if (state[insideState]) {
	    if (aniControl.animate(AnimateControl.triangles)) {
		rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[u], Color.red);
		rWG.drawLine(tri.point[s], tri.point[t], Color.blue);
	    }
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(bC, Color.red);
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(tri.point[s], Color.red);
	} else if (state[triangulationState]) {
	    tri.draw(rWG, Color.black, Color.black);
	} else {
	    tri.draw(rWG, Color.black, Color.black);
	}
    }

    public synchronized void triangulate(Triangulation tri) {
	int seedEdge, currentEdge;
	int nFaces;
	Int s, t;

	// Initialise. 
	nFaces = 0;
	s = new Int();
	t = new Int();
  
	// Find closest neighbours and add edge to triangulation. 
	findClosestNeighbours(tri.point, tri.nPoints, s, t);

	// Create seed edge and add it to the triangulation. 
	seedEdge = tri.addEdge(s.getValue(), t.getValue(), 
			       Triangulation.Undefined, 
			       Triangulation.Undefined);

	currentEdge = 0;
	while (currentEdge < tri.nEdges)
	    {
		if (tri.edge[currentEdge].l == Triangulation.Undefined) {
		    completeFacet(currentEdge, tri, nFaces);
		    animate(triangulationState);
		}
		if (tri.edge[currentEdge].r == Triangulation.Undefined) {
		    completeFacet(currentEdge, tri, nFaces);
		    animate(triangulationState);
		}
		currentEdge++;
	    }
    }

    // Find the two closest points.  
    public void findClosestNeighbours(RealPoint p[], int nPoints,
				      Int u, Int v) {
	int i, j;
	float d, min;
	int s, t;

	s = t = 0;
	min = Float.MAX_VALUE;
	for (i = 0; i < nPoints-1; i++)
	    for (j = i+1; j < nPoints; j++)
		{
		    d = p[i].distanceSq(p[j]);
		    if (d < min)
			{
			    s = i;
			    t = j;
			    min = d;
			}
		}
	u.setValue(s);
	v.setValue(t);
    }

    /* 
     * Complete a facet by looking for the circle free point to the left
     * of the edge "e_i".  Add the facet to the triangulation.
     *
     * This function is a bit long and may be better split.
     */
    public void completeFacet(int eI, Triangulation tri, int nFaces) {
	float cP;
	boolean pointFree;
	Edge e[] = tri.edge;
	RealPoint p[] = tri.point;

	// Cache s and t. 
	if (e[eI].l == Triangulation.Undefined)
	    {
		s = e[eI].s;
		t = e[eI].t;
	    }
	else if (e[eI].r == Triangulation.Undefined)
	    {
		s = e[eI].t;
		t = e[eI].s;
	    }
	else
	    // Edge already completed. 
	    return;

	// Find point free circumcircle to the left. 
	for (u = 0; u < tri.nPoints; u++)
	    if (u != s && u != t) {
		if (Vector.crossProduct(p[s], p[t], p[u]) > 0.0) {
		    bC.circumCircle(p[s], p[t], p[u]);
		    animate(triangleState);
		    pointFree = true;
		    for (i = 0; i < tri.nPoints; i++)
			if (i != s && i != t && i != u) {
			    animate(pointState);
			    cP = Vector.crossProduct(p[s], p[t], p[i]);
			    if (cP > 0.0)
				if (bC.inside(p[i])) {
				    animate(insideState);
				    pointFree = false;
				    break;
				}
			}
		    animate(triangulationState);
		    if (pointFree)
			break;
		}
	    }

	// Add new triangle or update edge info if s-t is on hull. 
	if (u < tri.nPoints) {
	    int bP = u;

	    // Update face information of edge being completed. 
	    tri.updateLeftFace(eI, s, t, nFaces);
	    nFaces++;

	    // Add new edge or update face info of old edge. 
	    eI = tri.findEdge(bP, s);
	    if (eI == Triangulation.Undefined)
		// New edge. 
		eI = tri.addEdge(bP, s, nFaces, Triangulation.Undefined);
	    else
		// Old edge. 
		tri.updateLeftFace(eI, bP, s, nFaces);

	    // Add new edge or update face info of old edge. 
	    eI = tri.findEdge(t, bP);
	    if (eI == Triangulation.Undefined)
		// New edge.  
		eI = tri.addEdge(t, bP, nFaces, Triangulation.Undefined);
	    else
		// Old edge.  
		tri.updateLeftFace(eI, t, bP, nFaces); 
	} else
	    tri.updateLeftFace(eI, s, t, Triangulation.Universe); 
    }
}

/* 
 * QuadraticAlgorithm class.  O(n^2) algorithm. 
 */
class QuadraticAlgorithm extends TriangulationAlgorithm {
    int s, t, u, bP;
    Circle bC = new Circle();
    final static String algName = "O(n^2)";
    int nFaces;

    public QuadraticAlgorithm(Triangulation t, RealWindow w, int nPoints) {
	super(t, w, algName, nPoints);
    }

    public void reset() {
	nFaces = 0;
	triCanvas.needToClear = true;
	super.reset();
    }

    public void draw(RealWindowGraphics rWG, Triangulation tri) {
	if (state[triangleState]) {
	    if (aniControl.animate(AnimateControl.triangles)) {
		rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[bP], 
				 Color.green);
		rWG.drawLine(tri.point[s], tri.point[t], Color.blue);
	    }
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(bC, Color.green);
	} else if (state[pointState]) {
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(tri.point[u], Color.orange);
	} else if (state[insideState]) {
	    if (aniControl.animate(AnimateControl.triangles)) {
		rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[bP], Color.red);
		rWG.drawLine(tri.point[s], tri.point[t], Color.blue);
	    }
	    if (aniControl.animate(AnimateControl.circles))
		rWG.drawCircle(bC, Color.red);
	    if (aniControl.animate(AnimateControl.points))
		rWG.drawPoint(tri.point[s], Color.red);
	} else if (state[triangulationState]) {
	    tri.draw(rWG, Color.black, Color.black);
	} else {
	    tri.draw(rWG, Color.black, Color.black);
	}
    }

    public synchronized void triangulate(Triangulation tri) {
	int seedEdge, currentEdge;
	int nFaces;
	Int s, t;

	// Initialise. 
	nFaces = 0;
	s = new Int();
	t = new Int();
  
	// Find closest neighbours and add edge to triangulation. 
	findClosestNeighbours(tri.point, tri.nPoints, s, t);

	// Create seed edge and add it to the triangulation. 
	seedEdge = tri.addEdge(s.getValue(), t.getValue(), 
			       Triangulation.Undefined, 
			       Triangulation.Undefined);

	currentEdge = 0;
	while (currentEdge < tri.nEdges) {
	    if (tri.edge[currentEdge].l == Triangulation.Undefined) {
		completeFacet(currentEdge, tri, nFaces);
		animate(triangulationState);
	    }
	    if (tri.edge[currentEdge].r == Triangulation.Undefined) {
		completeFacet(currentEdge, tri, nFaces);
		animate(triangulationState);
	    }
	    currentEdge++;
	}
    }

    // Find the two closest points.  
    public void findClosestNeighbours(RealPoint p[], int nPoints,
				      Int u, Int v) {
	int i, j;
	float d, min;
	int s, t;

	s = t = 0;
	min = Float.MAX_VALUE;
	for (i = 0; i < nPoints-1; i++)
	    for (j = i+1; j < nPoints; j++)
		{
		    d = p[i].distanceSq(p[j]);
		    if (d < min)
			{
			    s = i;
			    t = j;
			    min = d;
			}
		}
	u.setValue(s);
	v.setValue(t);
    }

    /* 
     * Complete a facet by looking for the circle free point to the left
     * of the edge "e_i".  Add the facet to the triangulation.
     *
     * This function is a bit long and may be better split.
     */
    public void completeFacet(int eI, Triangulation tri, int nFaces) {
	float cP;
	int i;
	Edge e[] = tri.edge;
	RealPoint p[] = tri.point;

	// Cache s and t. 
	if (e[eI].l == Triangulation.Undefined)
	    {
		s = e[eI].s;
		t = e[eI].t;
	    }
	else if (e[eI].r == Triangulation.Undefined)
	    {
		s = e[eI].t;
		t = e[eI].s;
	    }
	else
	    // Edge already completed. 
	    return;
    

	// Find a point on left of edge. 
	for (u = 0; u < tri.nPoints; u++)
	    {
		if (u == s || u == t)
		    continue;
		if (Vector.crossProduct(p[s], p[t], p[u]) > 0.0)
		    break;
	    }

	// Find best point on left of edge. 
	bP = u;
	if (bP < tri.nPoints)
	    {
		bC.circumCircle(p[s], p[t], p[bP]);

		animate(triangleState);

		for (u = bP+1; u < tri.nPoints; u++)
		    {
			if (u == s || u == t)
			    continue;

			animate(pointState);

			cP = Vector.crossProduct(p[s], p[t], p[u]);

			if (cP > 0.0)
			    if (bC.inside(p[u]))
				{
				    animate(insideState);
				    bP = u;
				    bC.circumCircle(p[s], p[t], p[u]);
				    animate(triangleState);
				}
		    }
	    }

	// Add new triangle or update edge info if s-t is on hull. 
	if (bP < tri.nPoints)
	    {
		// Update face information of edge being completed. 
		tri.updateLeftFace(eI, s, t, nFaces);
		nFaces++;

		// Add new edge or update face info of old edge. 
		eI = tri.findEdge(bP, s);
		if (eI == Triangulation.Undefined)
		    // New edge. 
		    eI = tri.addEdge(bP, s, nFaces, Triangulation.Undefined);
		else
		    // Old edge. 
		    tri.updateLeftFace(eI, bP, s, nFaces);

		// Add new edge or update face info of old edge. 
		eI = tri.findEdge(t, bP);
		if (eI == Triangulation.Undefined)
		    // New edge.  
		    eI = tri.addEdge(t, bP, nFaces, Triangulation.Undefined);
		else
		    // Old edge.  
		    tri.updateLeftFace(eI, t, bP, nFaces); 
	    } else
		tri.updateLeftFace(eI, s, t, Triangulation.Universe); 
    }
}

/*
 * AppletUI class. Provides most of the user interface for the applet.
 */
class AppletUI extends Panel {
    AlgorithmUI AlgorithmUI[];

    public AppletUI(TriangulationAlgorithm algorithm[]) {
	Label l;
	Panel p;

	setLayout(new BorderLayout());

	// Per algorithm controls. 
	p = new Panel();
	p.setLayout(new GridLayout(0,1));

	// Headings for algorithm controls. 
	p.add(new AlgorithmUIHeading());

	// One set of controls per algorithm.
	for (int i = 0; i < algorithm.length; i++)
	    p.add(algorithm[i].algorithmUI());

	// Add panel to controls. 
	add("Center", p);

	// Applet controls. 
	p = new Panel();
	p.setLayout(new GridLayout(0,1));
	p.add(new Button("Start"));
	p.add(new Button("Stop"));
	p.add(new Button("New"));
	p.add(new Label("Step Mode", Label.CENTER));
	Choice c = new Choice();
	c.addItem("Auto");
	c.addItem("Manual");
	p.add(c);

	// Add panel to controls. 
	add("East", p);
    }
}

/* 
 * TriangulationApplet class.  "Main Class"
 */
@SuppressWarnings("deprecation")
public class Triangulator extends Applet implements Runnable {
    Thread triangulateThread[];
    int nPoints = 10;
    Triangulation triangulation[];
    TriangulationAlgorithm algorithm[];
    RealWindow w;
    RealWindowGraphics rWG;
    AppletUI appUI;
    public static final int On2 = 0;
    public static final int On3 = 1;
    public static final int On4 = 2;
    Panel canvases;
    static final int nAlgorithms = 3;

    public void init() {

	setBackground(Color.lightGray);
	resize(600,350);

	// Create a rectangle in the real plane for points. 
	w = new RealWindow(0.0f, 0.0f, 1.0f, 1.0f);

	// Create array of triangulations, including random points. 
	triangulation = new Triangulation[nAlgorithms];
	triangulation[0] = new Triangulation(nPoints);
	triangulation[0].randomPoints(w);
	for (int i = 1; i < nAlgorithms; i++) {
	    triangulation[i] = new Triangulation(nPoints);
	    triangulation[i].copyPoints(triangulation[0]);
	}

	// Create an array of triangulation algorithms. 
	algorithm = new TriangulationAlgorithm[nAlgorithms];
	algorithm[0] = new QuadraticAlgorithm(triangulation[0], w, nPoints);
	algorithm[1] = new CubicAlgorithm(triangulation[1], w, nPoints);
	algorithm[2] = new QuarticAlgorithm(triangulation[2], w, nPoints);

	// Array of thread references (one for each algorithm). 
	triangulateThread = new Thread[nAlgorithms];

	// Create user interface. 
	Panel heading = new Panel();
	heading.setLayout(new BorderLayout());
	heading.add("Center", new Label("The Triangulator", Label.CENTER));
	Panel algHeadings = new Panel();
	algHeadings.setLayout(new GridLayout(0, nAlgorithms));
	for (int i = 0; i < nAlgorithms; i++)
	    algHeadings.add(new Label(algorithm[i].algName, Label.CENTER));
	heading.add("South", algHeadings);
	canvases = new Panel();
	canvases.setLayout(new GridLayout(0, nAlgorithms));
	for (int i = 0; i < nAlgorithms; i++)
	    canvases.add(algorithm[i].canvas());
	setLayout(new BorderLayout());
	add("North", heading);
	add("Center", canvases);
	appUI = new AppletUI(algorithm);
	add("South", appUI);
    }

    /*
     * Called for each algorithm thread when started.
     */
    public void run() {
	int algNo;
	String threadName;

	// Work out which algorithm to run from the thread name. 
	threadName = Thread.currentThread().getName();
	algNo = Integer.parseInt(threadName.substring(threadName.length()-1));
	algorithm[algNo].triangulate(triangulation[algNo]);
    }

    public Insets insets() {
	// Right offset is more than left, due to Choice bug.
	return new Insets(5,10,5,15);
    }
    
    /* 
     * Actually start the triangulation algorithms running.
     */
    private synchronized void startTriangulate() {
	for (int i = 0; i < triangulateThread.length; i++)
	    if (triangulateThread[i] != null && triangulateThread[i].isAlive()) {
		stop();
	    }
	for (int i = 0; i < triangulateThread.length; i++) {
	    if (algorithm[i].control().getRun()) {
		triangulateThread[i] = new Thread(this, "Triangulation-" + String.valueOf(i));
		triangulateThread[i].setPriority(Thread.MIN_PRIORITY);
		triangulateThread[i].start();
	    }
	}
    }

    /*
     * Generate new points for the algorithms.
     */
    private synchronized void newPoints() {
	int max, alg;

	stop();

	// Find algorithm with max points. 
	max = 0;
	alg = -1;
	for (int i = 0; i < nAlgorithms; i++)
	    if (algorithm[i].control().getRun() && 
		algorithm[i].control().getNPoints() > max) {
		max = algorithm[i].control().getNPoints();
		alg = i;
	    }

	// Generate maximum number of points. 
	if (alg != -1) {
	    triangulation[alg].setNPoints(algorithm[alg].control().getNPoints());
	    triangulation[alg].randomPoints(w);
	}
    
	/* Now copy points into other algorithms.  This has the effect
	 * that algorithms with the same number of points wind up with
	 * the same points.
	 */
	for (int i = 0; i < nAlgorithms; i++)
	    if (algorithm[i].control().getRun() && i != alg) {
		triangulation[i].setNPoints(algorithm[i].control().getNPoints());
		triangulation[i].copyPoints(triangulation[alg]);
	    }

	for (int i = 0; i < nAlgorithms; i++)
	    if (algorithm[i].control().getRun()) {
		algorithm[i].reset();
		algorithm[i].canvas().repaint();
	    }
    }

    /*
     * Stop the applet. Kill the triangulation algorithm if
     * still triangulating.
     */

    public synchronized void stop() {
	for (int i = 0; i < triangulateThread.length; i++) {
	    if (triangulateThread[i] != null) {
		try { 
		    triangulateThread[i].stop(); 
		} catch (IllegalThreadStateException e) {}
		triangulateThread[i] = null;
	    }
	}
    }

    /* 
     * Gets the current value in a text field.  
     */
    int getValue(TextField tF) {
	int i;
	try {
	    i = Integer.valueOf(tF.getText()).intValue(); 
	} catch (java.lang.NumberFormatException e) {
	    i = 0;
	}
	return i;
    }

    /*
     * Handle main level events. 
     */
    public boolean handleEvent(Event evt) {
	if (evt.id == Event.ACTION_EVENT) {
	    if ("Start".equals(evt.arg)) {
		startTriangulate();
		return true;
	    } else if ("Stop".equals(evt.arg)) {
		stop();
		return true;
	    } else if ("New".equals(evt.arg)) {
		newPoints();
	    } else if ("Manual".equals(evt.arg)) {
		for (int i = 0; i < nAlgorithms; i++)
		    algorithm[i].control().setManualAnimateMode();
		return true;
	    } else if ("Auto".equals(evt.arg)) {
		for (int i = 0; i < nAlgorithms; i++)
		    algorithm[i].control().setAutomaticAnimateMode();
		return true;
	    }
	} else if (evt.id == Event.MOUSE_DOWN) {
	    // These events only occur in the canvases. 
	    for (int i = 0; i < nAlgorithms; i++)
		if (algorithm[i].control().mode() == AnimateControl.manual)
		    algorithm[i].nextStep();
	    return true;
	} else if (evt.id == Event.MOUSE_MOVE) {
	    return true;
	}

	return false;
    }
}
