/* placed in the public domain 2000 Lawrence Leinweber Cleveland Ohio U.S.A. */
import java.applet.*;
import java.awt.*;

public class planimtr extends Applet {

/* version 1.0 */
/* apologies for the alphabet soup and lack of actual objects */
/* note that the display y axis is flipped */
/* in this code, ccw references internal Cartesian system, not display system */
/* areas are generally 2*square pixels, positive ccw */
/* too many cooks spoil the soup: synchronize everything public */

/* GUI crap */

private Image image;	/* planimeter drawing area buffer */
private Image back;	/* background (with sample) */
private Graphics graphics; /* planimeter drawing area buffer graphics context */
private Panel p_right;
private Checkbox cb_clearandtrace, cb_continuetrace, cb_discontinuoustrace,
	cb_move, cb_movepivot, cb_movetracer, cb_movewheel, cb_ruler,
	cb_scalefactor, cb_scalename;
private TextField tf_movepivot, tf_movetracer, tf_movewheel, tf_ruler,
	tf_scalefactor, tf_scalename, tf_botharms, tf_polearm,
	tf_tracerwheel, tf_pivotwheel, tf_wheelreading,
	tf_zerocircle, tf_wheelpluscircle, tf_wheelminuscircle;
private Choice c_sample;
private Scrollbar sb_horizontal, sb_vertical;
private Label l_scrollbar;	/* patch at lower left between scrollbars */

private int back_x0, back_y0;	/* background offset */
private int back_x1, back_y1;	/* height and width of background, or 0 */
private Checkbox cb_last;	/* last checkbox state */
private TextField tf_last;	/* last text field being editted */
private String str_last;	/* last legal text of text field before edit */
private int disag;		/* polygon area from previous discontinuous */
private boolean tracemode;	/* all the trace checkbox states collectively */
private boolean movemode;	/* all the move checkbox states collectively */
private double dblscale;	/* user's scale factor as a double */
private double dblscale2;	/* user's scale factor squared as a double */
private int intscale;		/* user's scale factor as an int */
private int intscale2;		/* user's scale factor squared as an int */
private boolean isintscale2;	/* is user's scale factor squared an int?*/ 
private boolean backbusy;	/* the background is not completely loaded */

private int polescale;		/* scale factor for pole arm (length) */
private int tracerscale;	/* scale factor for tracer arm (length) */

/* coordinates relative to the applet panel */

private int px0, py0;	/* planimeter drawing area upper left */
private int px1, py1;	/* planimeter drawing area size */

/* coordinates relative to the planimeter drawing area */

private double FPx, FPy;/* FP direction unit vector */
private double FP;	/* FP directed length */
private double TFx, TFy;/* TF direction unit vector */
private double TF, WF;	/* TF, WF directed length */

private int Fx, Fy;	/* pivot (nominal) */
private int FP2;	/* distance of pole from pivot squared */
private int Px, Py;	/* pole */
private int TF2;	/* distance of tracer from pivot squared */
private int Tx, Ty;	/* tracer */
private int FW2;	/* distance of pivot from wheel squared */
private int TW2;	/* distance of tracer from wheel squared (nominal) */
private int Wx, Wy;	/* wheel */
private int TPmin2, TPmax2;/* min and max of TP squared */
private int FWT;	/* W is before F (<0), between F & T (0), after T (>0)*/
private boolean ccw;	/* tracer arm is ccw of pole? */
private int ZCa;	/* area of "zero circle" */
private boolean isruler;/* is ruler non-zero? */
private int rx0, ry0;	/* ruler starting point */
private int rx1, ry1;	/* ruler end point */

/* reference circles and user polygons, FP, TW, FW, and TP */
	/* FP is pivot to pole circles and polygons, (F to P) */
	/* TW is tracer to wheel circles and polygons, (T to W) */
	/* FW is pivot to wheel circle (F to W) */
	/* TP is (mutant) tracer to pole polygon (T to P, sort of) */

private int   FPc,  TWc,  FWc;		/* circumference */
private int   FPag, TWag, FWag;		/* current polygon area */
private int[] FPa,  TWa,  FWa;		/* relative area (1/2)*(a^2), ref */
			/* forward */
				/* reference (3 circles) */
private int[] FPfx, TWfx, FWfx;		/* vector x */
private int[] FPfy, TWfy, FWfy;		/* vector y */
private int[] FPfdx,TWfdx,FWfdx;	/* difference from PREVIOUS vector x */
private int[] FPfdy,TWfdy,FWfdy;	/* difference from PREVIOUS vector y */
private int   FPfi, TWfi, FWfi;		/* current position */
				/* user's polygon */
private int[] FPfxg,TWfxg,FWfxg;	/* vector x */
private int[] FPfyg,TWfyg,FWfyg;	/* vector y */
private int   FPflg,TWflg,FWflg;	/* allocated length */
private int   FPfug,TWfug,FWfug;	/* used portion */
private int   FPfig,TWfig,FWfig;	/* current position */
			/* backward */
				/* reference (3 circles) */
private int[] FPbx, TWbx, FWbx;		/* vector x */
private int[] FPby, TWby, FWby;		/* vector y */
private int[] FPbdx,TWbdx,FWbdx;	/* difference from PREVIOUS vector x */
private int[] FPbdy,TWbdy,FWbdy;	/* difference from PREVIOUS vector y */
private int   FPbi, TWbi, FWbi;		/* current position */
				/* user's polygon */
private int[] FPbxg,TWbxg,FWbxg;	/* vector x */
private int[] FPbyg,TWbyg,FWbyg;	/* vector y */
private int   FPblg,TWblg,FWblg;	/* allocated length */
private int   FPbug,TWbug,FWbug;	/* used portion */
private int   FPbig,TWbig,FWbig;	/* current position */

			/* user's polygon swept by both arms (T to P, kinda) */
private int[] TPxg;			/* vector x */
private int[] TPyg;			/* vector y */
private int   TPlg;			/* allocated length */
private int   TPig;			/* current position (used portion) */
private int   TPag;			/* current polygon area */

private final static int tick = 5;	/* length of tick mark (pixels) */
private final static int tick2 = tick*2;/* twice length of tick mark (pixels) */
private final static int minarm = 10;	/* min length of either arm (pixels) */

/* initial initialization */
public planimtr()
{
	super();

	GridBagLayout gb;
	GridBagConstraints gbc;
	CheckboxGroup cbg;
	Panel p;
	int i;

	gb = new GridBagLayout();
	gbc = new GridBagConstraints();
	c_sample = new Choice();
	for (i = 0; backname[i] != null; i++) c_sample.addItem(backname[i]);
	cbg = new CheckboxGroup();
	cb_clearandtrace = new Checkbox("clear and trace", cbg, true);
	cb_continuetrace = new Checkbox("continue trace", cbg, false);
	cb_discontinuoustrace = new Checkbox("discontinuous trace", cbg, false);
	cb_move = new Checkbox("move", cbg, false);
	cb_movepivot = new Checkbox("move pivot", cbg, false);
	cb_movetracer = new Checkbox("move tracer", cbg, false);
	cb_movewheel = new Checkbox("move wheel", cbg, false);
	cb_ruler = new Checkbox("ruler", cbg, false);
	cb_scalefactor = new Checkbox("scale factor", cbg, false);
	cb_scalename = new Checkbox("scale name", cbg, false);
	tf_movepivot = dotf(); tf_movetracer = dotf();
	tf_movewheel = dotf(); tf_ruler = dotf();
	tf_scalefactor = dotf(); tf_scalename = dotf();
	tf_botharms = dotf(); tf_polearm = dotf();
	tf_tracerwheel = dotf(); tf_pivotwheel = dotf();
	tf_wheelreading = dotf(); tf_zerocircle = dotf();
	tf_wheelpluscircle = dotf(); tf_wheelminuscircle = dotf();
	tf_ruler.setText("1");
	tf_scalefactor.setText("1"); tf_scalename.setText("pixels");
	sb_horizontal = new Scrollbar(Scrollbar.HORIZONTAL);
	sb_vertical = new Scrollbar(Scrollbar.VERTICAL);
	l_scrollbar = new Label("");
	p_right = new Panel();
	p = new Panel();
	/* that it for the new stuff */
	super.setBackground(Color.white);
	super.setLayout(null);
	super.add(sb_horizontal);
	super.add(sb_vertical);
	super.add(l_scrollbar);
	super.add(p_right);
	p_right.setLayout(gb);
	gbc.insets = new Insets(0, 2, 0, 2);
	p_right.add(dogbc(gb, gbc, 0, 0, 1, 1, 1, 1, p));
	gbc.insets = new Insets(0, 0, 0, 0);
	p.setLayout(gb);
	p.add(dogbc(gb, gbc, 0, 0, 1, 1, 1, 0, new Label("both arms")));
	p.add(dogbc(gb, gbc, 0, 1, 1, 1, 1, 1, tf_botharms));
	p.add(dogbc(gb, gbc, 1, 0, 1, 1, 1, 0, new Label("pole arm")));
	p.add(dogbc(gb, gbc, 1, 1, 1, 1, 1, 1, tf_polearm));
	p.add(dogbc(gb, gbc, 2, 0, 1, 1, 1, 0, new Label("tracer-wheel")));
	p.add(dogbc(gb, gbc, 2, 1, 1, 1, 1, 1, tf_tracerwheel));
	p.add(dogbc(gb, gbc, 3, 0, 1, 1, 1, 0, new Label("pivot-wheel")));
	p.add(dogbc(gb, gbc, 3, 1, 1, 1, 1, 1, tf_pivotwheel));
	p.add(dogbc(gb, gbc, 4, 0, 1, 1, 1, 0, new Label("wheel reading")));
	p.add(dogbc(gb, gbc, 4, 1, 1, 1, 1, 1, tf_wheelreading));
	p.add(dogbc(gb, gbc, 5, 0, 1, 1, 1, 0, new Label("zero circle")));
	p.add(dogbc(gb, gbc, 5, 1, 1, 1, 1, 1, tf_zerocircle));
	p.add(dogbc(gb, gbc, 6, 0, 1, 1, 1, 0, new Label("wheel + circle")));
	p.add(dogbc(gb, gbc, 6, 1, 1, 1, 1, 1, tf_wheelpluscircle));
	p.add(dogbc(gb, gbc, 7, 0, 1, 1, 1, 0, new Label("wheel - circle")));
	p.add(dogbc(gb, gbc, 7, 1, 1, 1, 1, 1, tf_wheelminuscircle));
	p.add(dogbc(gb, gbc, 8, 0, 1, 2, 1, 1, c_sample));
	p.add(dogbc(gb, gbc, 9, 0, 1, 2, 1, 1, cb_clearandtrace));
	p.add(dogbc(gb, gbc,10, 0, 1, 2, 1, 1, cb_continuetrace));
	p.add(dogbc(gb, gbc,11, 0, 1, 2, 1, 1, cb_discontinuoustrace));
	p.add(dogbc(gb, gbc,12, 0, 1, 2, 1, 0, cb_move));
	p.add(dogbc(gb, gbc,13, 0, 1, 1, 1, 0, cb_movepivot));
	p.add(dogbc(gb, gbc,13, 1, 1, 1, 1, 1, tf_movepivot));
	p.add(dogbc(gb, gbc,14, 0, 1, 1, 1, 0, cb_movetracer));
	p.add(dogbc(gb, gbc,14, 1, 1, 1, 1, 1, tf_movetracer));
	p.add(dogbc(gb, gbc,15, 0, 1, 1, 1, 0, cb_movewheel));
	p.add(dogbc(gb, gbc,15, 1, 1, 1, 1, 1, tf_movewheel));
	p.add(dogbc(gb, gbc,16, 0, 1, 1, 1, 0, cb_ruler));
	p.add(dogbc(gb, gbc,16, 1, 1, 1, 1, 1, tf_ruler));
	p.add(dogbc(gb, gbc,17, 0, 1, 1, 1, 0, cb_scalefactor));
	p.add(dogbc(gb, gbc,17, 1, 1, 1, 1, 1, tf_scalefactor));
	p.add(dogbc(gb, gbc,18, 0, 1, 1, 1, 0, cb_scalename));
	p.add(dogbc(gb, gbc,18, 1, 1, 1, 1, 1, tf_scalename));
}

/* return a TextField with some defaults */
private TextField dotf()
{
	TextField tf;

	tf = new TextField("0");
	tf.setEditable(false);
	return (tf);
}

/* do some gridbag stuff */
private Component dogbc(GridBagLayout gb, GridBagConstraints gbc,
	int row, int col, int nrow, int ncol, int wrow, int wcol, Component c)
{
	gbc.fill = gbc.BOTH;
	gbc.gridx = col;
	gbc.gridy = row;
	gbc.gridwidth = ncol;
	gbc.gridheight = nrow;
	gbc.weightx = (double) wcol;
	gbc.weighty = (double) wrow;
	gb.setConstraints(c, gbc);
	return (c);
}

/* named list of internally generated background samples */
private String[] backname = { "small square", "small circle",
	"large square", "large circle", "hexagon", "eccentric",
	"I beam", "box beam", "triangles", "sine", null };

/* make the background sample if it's on backname list, or load it from file */
private Image getback(String s)
{
	Graphics g;
	Image i;
	Polygon p;
	int n;

	backbusy = false;
	i = super.createImage(400, 400);
	g = i.getGraphics();
	g.setColor(Color.white);
	g.fillRect(0, 0, 400, 400);
	g.setColor(Color.magenta);
	if (s.equals("small square")) {
		g.fillRect(275, 175, 50, 50);
		caption(g, "area=2500", 300, 225);
	}
	else if (s.equals("small circle")) {
		g.fillOval(275, 175, 50, 50);
		caption(g, "area=1963", 300, 230);
	}
	else if (s.equals("large square")) {
		g.fillRect(100, 100, 200, 200);
		caption(g, "area=40000", 200, 300);
	}
	else if (s.equals("large circle")) {
		g.fillOval(100, 100, 200, 200);
		caption(g, "area=31416", 200, 300);
	}
	else if (s.equals("hexagon")) {
		p = new Polygon();
		p.addPoint(350, 200); p.addPoint(325, 157);
		p.addPoint(275, 157); p.addPoint(250, 200);
		p.addPoint(275, 243); p.addPoint(325, 243);
		g.fillPolygon(p);
		caption(g, "area=6495", 300, 243);
	}
	else if (s.equals("eccentric")) {
		g.fillOval(252, 152, 96, 96);
		caption(g, "area=5429", 300, 248);
		g.setColor(Color.white);
		g.fillOval(284, 176, 48, 48);
	}
	else if (s.equals("I beam")) {
		g.fillRect(270, 150, 60, 100);
		caption(g, "area=3600", 300, 250);
		g.setColor(Color.white);
		g.fillRect(270, 170, 20, 60);
		g.fillRect(310, 170, 20, 60);
	}
	else if (s.equals("box beam")) {
		g.fillRect(250, 150, 100, 100);
		caption(g, "area=6400", 300, 250);
		g.setColor(Color.white);
		g.fillRect(270, 170, 60, 60);
	}
	else if (s.equals("triangles")) {
		p = new Polygon();
		p.addPoint( 20,300); p.addPoint(145,300); p.addPoint(145,140);
		g.fillPolygon(p);
		p = new Polygon();
		p.addPoint(165,300); p.addPoint(265,300); p.addPoint(265,100);
		g.fillPolygon(p);
		p = new Polygon();
		p.addPoint(285,300); p.addPoint(365,300); p.addPoint(365, 50);
		g.fillPolygon(p);
		caption(g, "area=10000",  82, 300);
		caption(g, "area=10000", 215, 300);
		caption(g, "area=10000", 325, 300);
	}
	else if (s.equals("sine")) {
		p = new Polygon();
		p.addPoint(43, 200);
		for (n = -157; n <= 157; n++)
			p.addPoint(200 + n, (int) (200 - 100.0 *
					Math.sin(n / 50.0)));
		p.addPoint(357, 200);
		g.fillPolygon(p);
		caption(g, "area=10000", 279, 200);
		n = g.getFontMetrics().getHeight();
		caption(g, "area=10000", 121, 200 - n - n);
	}
	else backbusy = true;
	g.dispose();
	if (backbusy) {
		i.flush();
		i = super.getImage(this.getDocumentBase(), s);
	}
	return (i);
}

/* center caption s below position x,y in graphics context g */
private void caption(Graphics g, String s, int x, int y)
{
	g.drawString(s, x - g.getFontMetrics().stringWidth(s) / 2,
			y + g.getFontMetrics().getHeight());
}

/* pick up parameters param<num> and make default planimeter */
public synchronized void init()
{
	super.init();

	int i;

	for (i = 0; this.getParameter("param" + i) != null; i++)
		c_sample.addItem(super.getParameter("param" + i));
	Px = 200; Py = 200;
	FPx = 0.0; FPy = 1.0;
	TFx = 1.0; TFy = 0.0;
	FP = 75.0;
	TF = 100.0;
	WF = -15.0;
	tf_movepivot.setText(String.valueOf(FP)); FP2 = sqr(FP);
	tf_movetracer.setText(String.valueOf(TF)); TF2 = sqr(TF);
	tf_movewheel.setText(String.valueOf(WF)); FW2 = sqr(WF);
	doscale(1.0);
	domove();
	tracemode = true;
	movemode = false;
	make();
	cb_last = cb_clearandtrace;
	tf_last = null; str_last = null;
	back = getback(backname[0]);
	doreshape();
}

/* do explicit deallocations */
public synchronized void destroy()
{
	if (graphics != null) { graphics.dispose(); graphics = null; }
	if (image != null) { image.flush(); image = null; }
	if (back != null) { back.flush(); back = null; }
}

/* simulate a resize event without changing anything (to update display) */
private void doreshape()
{
	reshape(super.location().x, super.location().y,
		super.size().width, super.size().height);
}

/* AWT system calls this when applet panel gets dimensions set */
public synchronized void reshape(int x, int y, int width, int height)
{
	Dimension d;
	int sb_x, sb_y;
	boolean flag;

	super.reshape(x, y, width, height);
	if (width <= 0 || height <= 0) return;
	d = super.size();
	sb_x = sb_vertical.preferredSize().width;
	sb_y = sb_horizontal.preferredSize().height;
	px0 = 0; py0 = 0;
	px1 = Math.max(d.width - 200, d.width * 2 / 3);
	py1 = d.height;
	p_right.reshape(px0 + px1, py0, d.width - px0 - px1, py1);
	if (back == null || (back_x1 = back.getWidth(this)) == -1) back_x1 = 0;
	if (back == null || (back_y1 = back.getHeight(this)) == -1) back_y1 = 0;
	back_x0 = (px1 - back_x1) / 2;
	back_y0 = (py1 - back_y1) / 2;
	if (back_x0 < 0 || back_y0 < 0)
		if (back_x0 >= sb_x / 2) back_x0 -= sb_x / 2;
		else if (back_y0 >= sb_y / 2) back_y0 -= sb_y / 2;
	l_scrollbar.show(back_x0 < 0 || back_y0 < 0);
	sb_horizontal.show(back_x0 < 0);
	sb_vertical.show(back_y0 < 0);
	if (back_x0 < 0) py1 -= sb_y;
	if (back_y0 < 0) px1 -= sb_x;
	if (back_x0 < 0) {
		sb_horizontal.reshape(px0, py0 + py1, px1, sb_y);
		sb_horizontal.setValues(back_x0 = 0, px1, 0, back_x1);
		sb_horizontal.setPageIncrement(px1 / 2);
	}
	if (back_y0 < 0) {
		sb_vertical.reshape(px0 + px1, py0, sb_x, py1);
		sb_vertical.setValues(back_y0 = 0, py1, 0, back_y1);
		sb_vertical.setPageIncrement(py1 / 2);
	}
	if (graphics != null) { graphics.dispose(); graphics = null; }
	if (image != null) { image.flush(); image = null; }
	image = super.createImage(px1, py1);
	graphics = image.getGraphics();
}

/* callback function from queued, asynchronous image load */
public synchronized boolean imageUpdate(Image i, int flags,
	int x0, int y0, int x1, int y1)
{
	int mask;

	mask = super.ABORT | super.ERROR;
	if (i != back || (flags & mask) != 0) { back = null; return (false); }
	mask = super.WIDTH | super.HEIGHT;
	if ((flags & mask) == mask) doreshape();
	mask = super.SOMEBITS | super.ALLBITS;
	if ((flags & mask) != 0) super.repaint(px0, py0, px1, py1);
	mask = super.WIDTH | super.HEIGHT | super.ALLBITS;
	return (backbusy = (flags & mask) != mask);
}

/*		w/o device lines		w/device lines
	white	nothing (just backgound)	sample+swept error+swept error
	magenta	sample				swept area+swept error
	cyan	swept area			sample+swept error
	yellow	swept error			sample+swept areaswept error
	green	swept area+swept error		sample
	red	sample+swept error		swept area
	blue	sample+swept area		swept error
	black	sampe+swept area+swept error	nothing (just device)
*/

/* clear backgroud (placebo) and call paint */
public void update(Graphics g)
{
	this.paint(g);
}

/* draw planimeter graphics (gui components take care of themselves) */
public synchronized void paint(Graphics g)
{
	Rectangle r;
	int x, y, x0, y0, x1, y1;

	r = g.getClipRect();
	r.x -= px0; r.y -= py0;
	if (r.x >= px1 || r.x + r.width <= 0 ||
			r.y >= py1 || r.y + r.height <= 0)
		return;
	graphics.setPaintMode();		/* paint background */
	graphics.setColor(Color.white);
	r.x -= back_x0; r.y -= back_y0;
	if (back == null || backbusy ||
			r.x < 0 || r.x + r.width > back_x1 ||
			r.y < 0 || r.y + r.height > back_y1)
		graphics.fillRect(0, 0, px1, py1);
	graphics.translate(back_x0, back_y0);
	if (back != null) graphics.drawImage(back, 0, 0, Color.white, this);
	graphics.setXORMode(Color.white);	/* xor areas */
	graphics.setColor(Color.black);		/* device, ruler in black */
	graphics.drawOval(Px - tick, Py - tick, tick2, tick2);
	if (FW2 != 0 && (FW2 != TF2 || FWT < 0))
		graphics.drawOval(Wx - tick, Wy - tick, tick2, tick2);
	graphics.drawOval(Tx - tick, Ty - tick, tick2, tick2);
	graphics.drawOval(Fx - tick, Fy - tick, tick2, tick2);
	x = (Fx - Px) * (tick2 + 3) / polescale >> 1;
	y = (Fy - Py) * (tick2 + 3) / polescale >> 1;
	graphics.drawLine(Px - y, Py + x, Fx - y, Fy + x);
	graphics.drawLine(Px + y, Py - x, Fx + y, Fy - x);
	if (FWT < 0) { x0 = Wx; y0 = Wy; } else { x0 = Fx; y0 = Fy; }
	if (FWT > 0) { x1 = Wx; y1 = Wy; } else { x1 = Tx; y1 = Ty; }
	x = (x1 - x0) * (tick2 + 3) / tracerscale >> 1;
	y = (y1 - y0) * (tick2 + 3) / tracerscale >> 1;
	graphics.drawLine(x0 - y, y0 + x, x1 - y, y1 + x);
	graphics.drawLine(x0 + y, y0 - x, x1 + y, y1 - x);
	x = (x1 - x0) * (tick2 - 1) / tracerscale >> 1;
	y = (y1 - y0) * (tick2 - 1) / tracerscale >> 1;
	if (FW2 != TF2 || FWT < 0) {
		graphics.drawLine(Wx - y, Wy + x, Wx + y, Wy - x);
		graphics.drawLine(Tx - y, Ty + x, Tx + y, Ty - x);
	}
	graphics.drawLine(Tx - x, Ty - y, Tx + x, Ty + y);
	if (isruler) graphics.drawLine(rx0, ry0, rx1, ry1);
	graphics.setColor(Color.cyan);		/* TF xors to cyan */
	graphics.translate(Px, Py);
	if (FPfig > 2) graphics.fillPolygon(FPfxg, FPfyg, FPfig);
	if (FPbig > 2) graphics.fillPolygon(FPbxg, FPbyg, FPbig);
	if (TPig  > 4) graphics.fillPolygon(TPxg,  TPyg,  TPig);
	graphics.translate(-Px, -Py);
	graphics.setColor(Color.yellow);	/* FW,TF xor to yellow */
	graphics.translate(Wx, Wy);
	if (FWfig > 2) graphics.fillPolygon(FWfxg, FWfyg, FWfig);
	if (FWbig > 2) graphics.fillPolygon(FWbxg, FWbyg, FWbig);
	if (TWfig > 2) graphics.fillPolygon(TWfxg, TWfyg, TWfig);
	if (TWbig > 2) graphics.fillPolygon(TWbxg, TWbyg, TWbig);
	graphics.translate(-Wx, -Wy);
	graphics.translate(-back_x0, -back_y0);
	g.drawImage(image, px0, py0, px1, py1, this); /* buffer to display */
}

/* handle gui component events */
public synchronized boolean handleEvent(Event e)
{
	Checkbox cb;
	double d;
	boolean flag;
	Image i;

	if (e.target instanceof Scrollbar) {
		if (e.id != e.SCROLL_ABSOLUTE &&
			e.id != e.SCROLL_LINE_UP && e.id != e.SCROLL_LINE_DOWN&&
			e.id != e.SCROLL_PAGE_UP && e.id != e.SCROLL_PAGE_DOWN)
			return (super.handleEvent(e));
		if (e.target == sb_horizontal)
			back_x0 = -((Integer) e.arg).intValue();
		else	back_y0 = -((Integer) e.arg).intValue();
		super.repaint(px0, py0, px1, py1);
		return (true);
	}
	if (e.id != e.ACTION_EVENT)  return (super.handleEvent(e));
	if (e.target == c_sample) {
		i = getback((String) e.arg);
		if (back != null) { back.flush(); back = null; }
		back = i;
		doreshape();
		super.repaint(px0, py0, px1, py1);
		return (true);
	}
	if (tf_last != null && tf_last != tf_scalename) {
		flag = true; d = 0.0;
		try { d = Double.valueOf(tf_last.getText()).doubleValue(); }
		catch (NumberFormatException ee) { flag = false; }
		if (!flag) tf_last.setText(str_last);
		else if (tf_last == tf_movepivot)
			if (Math.abs(d) >= minarm) { FP = d; domove(); }
			else tf_last.setText(str_last);
		else if (tf_last == tf_movetracer)
			if (Math.abs(d) >= minarm) { TF = d; domove(); }
			else tf_last.setText(str_last);
		else if (tf_last == tf_movewheel) { WF = d; domove(); }
		else if (tf_last == tf_ruler) doruler();
		else if (tf_last == tf_scalefactor) {
			doscale(d);
			doreadings();
		}
		super.repaint(px0, py0, px1, py1);
		str_last = tf_last.getText();
	}
	if (e.target instanceof Checkbox) {
		cb = (Checkbox) e.target;
		if (cb == cb_last) return (true);
		if (tf_last != null) tf_last.setEditable(false);
		tf_last = null;
		if (movemode != (cb == cb_move || cb == cb_movepivot || 
				cb == cb_movetracer || cb == cb_movewheel))
			if (movemode) { make(); movemode = false; }
			else {
				makepolygons();
				doreadings();
				super.repaint(px0, py0, px1, py1);
				undomove();
				movemode = true;
			}
		tracemode = cb == cb_clearandtrace || cb == cb_continuetrace ||
				cb == cb_discontinuoustrace;
		if (cb == cb_movepivot) tf_last = tf_movepivot;
		if (cb == cb_movetracer) tf_last = tf_movetracer;
		if (cb == cb_movewheel) tf_last = tf_movewheel;
		if (cb == cb_ruler) tf_last = tf_ruler;
		if (cb == cb_scalefactor) tf_last = tf_scalefactor;
		if (cb == cb_scalename) tf_last = tf_scalename;
		cb_last = cb;
		if (tf_last != null) {
			tf_last.setEditable(true);
			str_last = tf_last.getText();
		}
		return (true);
	}
	return (super.handleEvent(e));
}

/* handle a mouse button push on the planimeter graphics area */
public synchronized boolean mouseDown(Event e, int x, int y)
{
	return (domouse(x, y, true)? true: super.mouseDown(e, x, y));
}

/* handle a mouse move with button down on the planimeter graphics area */
public synchronized boolean mouseDrag(Event e, int x, int y)
{
	return (domouse(x, y, false)? true: super.mouseDrag(e, x, y));
}

/* handle mouse moved to x,y in planimeter graphics area;
	click means "was button just pushed?" */
private boolean domouse(int x, int y, boolean click)
{
	double d;

	x -= px0; y -= py0;
	if (x < 0 || x >= px1 || y < 0 || y >= py1) return (false);
	x -= back_x0; y -= back_y0;
	if (tracemode) {
		if (click)
			if (cb_discontinuoustrace.getState())
				disag += doreadings();
			else if (cb_clearandtrace.getState()) disag = 0;
		if (trace(x, y) != 0) return (true);
		if (click)
			if (!cb_continuetrace.getState()) makepolygons();
		doreadings();
	}
	else if (cb_last == cb_move) {
		x -= Px; y -= Py;
		Px += x; Py += y;
		Fx += x; Fy += y;
		Tx += x; Ty += y;
		Wx += x; Wy += y;
	}
	else if (cb_last == cb_movepivot) {
		d = FPx * (x - Px) + FPy * (y - Py);
		if (Math.abs(d) < minarm) return (false);
		tf_movepivot.setText(String.valueOf(FP = d));
		domove();
	}
	else if (cb_last == cb_movetracer) {
		d = TFx * (x - Fx) + TFy * (y - Fy);
		if (Math.abs(d) < minarm) return (false);
		tf_movetracer.setText(String.valueOf(TF = d));
		domove();
	}
	else if (cb_last == cb_movewheel) {
		d = TFx * (x - Fx) + TFy * (y - Fy);
		tf_movewheel.setText(String.valueOf(WF = d));
		domove();
	}
	else if (cb_last == cb_ruler) {
		if (click) { rx0 = x; ry0 = y; }
		rx1 = x; ry1 = y;
		isruler = rx0 != rx1 || ry0 != ry1;
		doruler();
	}
	else return (false);
	super.repaint(px0, py0, px1, py1);
	return (true);
}

/* update everything due to a planimeter move or resize;
	get ready to exit movemode */
private void domove()
{
	int d;

	FP2 = sqr(FP);
	TF2 = sqr(TF);
	FW2 = sqr(WF);
	Fx = Px + (int) Math.round(FP*FPx); Fy = Py + (int) Math.round(FP*FPy);
	Tx = Fx + (int) Math.round(TF*TFx); Ty = Fy + (int) Math.round(TF*TFy);
	Wx = Fx + (int) Math.round(WF*TFx); Wy = Fy + (int) Math.round(WF*TFy);
	FWT = WF < 0.0? -1: WF < TF? 0: 1;
	FWT = TF < 0.0? WF < TF? 1: WF < 0.0? 0: -1:
			WF < 0.0? -1: WF < TF? 0: 1;
	d = 2 * sqrt((long) TF2 * (long) FW2);
	TW2 = FWT < 0? TF2 + FW2 + d: TF2 + FW2 - d;
	ccw = (FP >= 0) != (TF >= 0);
	ZCa = areacircle(FP2) + areacircle(TW2) - areacircle(FW2);
	doscale(dblscale);
	doreadings();
	polescale = sqrt(FP2);
	tracerscale = sqrt(FWT < 0? TW2: FWT == 0? TF2: FW2);
}

/* update move/resize mode info; get ready to enter movemode */
private void undomove()
{
	double d;

	d = Math.sqrt(sqr(Fx - Px) + sqr(Fy - Py));
	FPx = (FP >= 0? Fx - Px: Px - Fx) / d;
	FPy = (FP >= 0? Fy - Py: Py - Fy) / d;
	d = Math.sqrt(sqr(Tx - Fx) + sqr(Ty - Fy));
	TFx = (TF >= 0? Tx - Fx: Fx - Tx) / d;
	TFy = (TF >= 0? Ty - Fy: Fy - Ty) / d;
}

/* update display numbers (the ones the user can't change) */
private synchronized int doreadings()
{
	String tp, fp, tw, fw, w, zc, wpc, wmc;
	int i;

	i = TPag - FPag - TWag + FWag;
	if (isintscale2) {
		tp = String.valueOf(intscale2 * (TPag >> 1));
		fp = String.valueOf(intscale2 * -(FPag >> 1));
		tw = String.valueOf(intscale2 * -(TWag >> 1));
		fw = String.valueOf(intscale2 * (FWag >> 1));
		w = String.valueOf(intscale2 * (i + disag >> 1));
		zc = String.valueOf(intscale2 * (ZCa >> 1));
		wpc = String.valueOf(intscale2 * (i + disag + ZCa >> 1));
		wmc = String.valueOf(intscale2 * (i + disag - ZCa >> 1));
	}
	else {
		tp = String.valueOf(dblscale2 * (TPag >> 1));
		fp = String.valueOf(dblscale2 * -(FPag >> 1));
		tw = String.valueOf(dblscale2 * -(TWag >> 1));
		fw = String.valueOf(dblscale2 * (FWag >> 1));
		w = String.valueOf(dblscale2 * (i + disag >> 1));
		zc = String.valueOf(dblscale2 * (ZCa >> 1));
		wpc = String.valueOf(dblscale2 * (i + disag + ZCa >> 1));
		wmc = String.valueOf(dblscale2 * (i + disag - ZCa >> 1));
	}
	tf_botharms.setText(tp);
	tf_polearm.setText(fp);
	tf_tracerwheel.setText(tw);
	tf_pivotwheel.setText(fw);
	tf_wheelreading.setText(w);
	tf_zerocircle.setText(zc);
	tf_wheelpluscircle.setText(wpc);
	tf_wheelminuscircle.setText(wmc);
	return (i);
}

/* set scale variables to d */
private void doscale(double d)
{
	dblscale = d;
	dblscale2 = dblscale * dblscale;
	if ((d < 0.0) != (TF < 0.0)) dblscale2 = -dblscale2;
	intscale = (int) dblscale;
	intscale2 = (int) dblscale2;
	isintscale2 = dblscale2 == intscale2;
}

/* do ruler mode (user entered value or moved mouse over planimeter */
private void doruler()
{
	double d;
	boolean flag;

	flag = true; d = 0.0;
	try { d = Double.valueOf(tf_last.getText()).doubleValue(); }
	catch (NumberFormatException ee) { flag = false; }
	if (!flag) { tf_ruler.setText(str_last); return; }
	if (isruler) d /= Math.sqrt(sqr(rx0 - rx1) + sqr(ry0 - ry1));
	doscale(d);
	if (intscale == dblscale)
		tf_scalefactor.setText(String.valueOf(intscale));
	else	tf_scalefactor.setText(String.valueOf(dblscale));
	doreadings();
}

/* number crunching below */

/* initialize everything given a legit device (get ready for tracemode):
   Px,Py 	pole to graphics origin vector
   FP2   	pole to pivot length squared
   TF2		tracer to pivot length squared
   FW2		wheel to pivot length squared
   FWT		W is before F (<0), between F & T (0), after T (>0)
   ccw		tracer arm goes counterclockwise from pole arm */
private void make()
{
	int d, n, x, y;

	d = 2 * sqrt((long) FP2 * (long) TF2);
	TPmin2 = FP2 + TF2 - d + d / 29; /* 1/29 ~ 1 - cos(15 degrees) */
	TPmax2 = FP2 + TF2 + d - d / 29;
	/* FP */
	n = 17 * sqrt(FP2) + 29;
	FPc = makerefcircle(FPfx = FPbx = new int[n],
		FPfdx = FPbdx = new int[n],
		FPfy = new int[n], FPfdy = new int[n],
		FPby = new int[n], FPbdy = new int[n],
		FPa = new int[n], FP2);
	FPbi = FPc - (FPfi = FPc / 4);
	if (ccw) { d = FPfi; FPfi = FPbi; FPbi = d; }
	/* FW */
	n = 17 * sqrt(FW2) + 29;
	FWc = makerefcircle(FWfx = FWbx = new int[n],
		FWfdx = FWbdx = new int[n],
		FWfy = new int[n], FWfdy = new int[n],
		FWby = new int[n], FWbdy = new int[n],
		FWa = new int[n], FW2);
	FWbi = FWc - (FWfi = FWT < 0? 0: FWc / 2);
	/* TW */
	d = 2 * sqrt((long) TF2 * (long) FW2);
	TW2 = FWT < 0? TF2 + FW2 + d: TF2 + FW2 - d;
	n = 17 * sqrt(TW2) + 29;
	TWc = makerefcircle(TWfx = TWbx = new int[n],
		TWfdx = TWbdx = new int[n],
		TWfy = new int[n], TWfdy = new int[n],
		TWby = new int[n], TWbdy = new int[n],
		TWa = new int[n], TW2);
	TWbi = TWc - (TWfi = FWT <= 0? 0: TWc / 2);
	x = Tx; y = Ty;
	Fx = Px + FPfx[FPfi]; Fy = Py + FPfy[FPfi];
	Wx = Fx - FWfx[FWfi]; Wy = Fy - FWfy[FWfi];
	Tx = Wx + TWfx[TWfi]; Ty = Wy + TWfy[TWfi];
	makepolygons();
	trace(x, y);
	makepolygons();
}

/* make and initialize user polygons (also used to reset the polygons) */
private void makepolygons()
{

	/* FP */
	FPflg = 4; FPfug = 2; FPfig = 2;
	FPblg = 4; FPbug = 2; FPbig = 2;
	FPfxg = new int[4]; FPfyg = new int[4];
	FPbxg = new int[4]; FPbyg = new int[4];
	FPfxg[0] = FPbxg[0] =       0; FPfyg[0] = FPbyg[0] =       0;
	FPfxg[1] = FPbxg[1] = Fx - Px; FPfyg[1] = FPbyg[1] = Fy - Py;
	/* FW */
	FWflg = 4; FWfug = 2; FWfig = 2;
	FWblg = 4; FWbug = 2; FWbig = 2;
	FWfxg = new int[4]; FWfyg = new int[4];
	FWbxg = new int[4]; FWbyg = new int[4];
	FWfxg[0] = FWbxg[0] =       0; FWfyg[0] = FWbyg[0] =       0;
	FWfxg[1] = FWbxg[1] = Fx - Wx; FWfyg[1] = FWbyg[1] = Fy - Wy;
	/* TW */
	TWflg = 4; TWfug = 2; TWfig = 2;
	TWblg = 4; TWbug = 2; TWbig = 2;
	TWfxg = new int[4]; TWfyg = new int[4];
	TWbxg = new int[4]; TWbyg = new int[4];
	TWfxg[0] = TWbxg[0] =       0; TWfyg[0] = TWbyg[0] =       0;
	TWfxg[1] = TWbxg[1] = Tx - Wx; TWfyg[1] = TWbyg[1] = Ty - Wy;
	/* TP */
	TPlg = 4; TPig = 4;
	TPxg = new int[4]; TPyg = new int[4];
	TPxg[0] =       0; TPyg[0] =       0;
	TPxg[1] = Fx - Px; TPyg[1] = Fy - Py;
	TPxg[2] = Tx - Px; TPyg[2] = Ty - Py;
	TPxg[3] = Fx - Px; TPyg[3] = Fy - Py;
	/* reset areas */
	FPag = FWag = TWag = TPag = 0;
	/* fix up TW/FW edge */
	if (FWT < 0) {
		TWfug++; TWfig++; TWbug++; TWbig++;
		TWfxg[2] = TWbxg[2] = TWfxg[1]; TWfyg[2] = TWbyg[2] = TWfyg[1];
		TWfxg[1] = TWbxg[1] = FWfxg[1]; TWfyg[1] = TWbyg[1] = FWfyg[1];
	}
	if (FWT > 0) {
		FWfug++; FWfig++; FWbug++; FWbig++;
		FWfxg[2] = FWbxg[2] = FWfxg[1]; FWfyg[2] = FWbyg[2] = FWfyg[1];
		FWfxg[1] = FWbxg[1] = TWfxg[1]; FWfyg[1] = FWbyg[1] = TWfyg[1];
	}
}

/* make reference circle(s) with radius squared r2: area a
	vector: forward fx,fy; backward fx,by
	difference from PREVIOUS vector: forward dx,fdy; backward dx,bdy */
private int makerefcircle(int[] fx, int[] dx,
		int[] fy, int[] fdy, int[] by, int[] bdy, int[] a, int r2)
{
	int c, i, j, k, x, y, aa;

	c = makecircle(fx, fy, r2);
	for (i = c + c; i > 0; i -= c) {
		System.arraycopy(fx, 0, fx, i, c);
		System.arraycopy(fy, 0, fy, i, c);
	}
	for (i = 0, j = c - 1; i < c; i++, j++) {
		by[i] = -fy[i];
		dx[i] = fx[i] - fx[j];
		fdy[i] = fy[i] - fy[j];
		bdy[i] = -fdy[i];
	}
	for (i = c + c; i > 0; i -= c) {
		System.arraycopy(by, 0, by, i, c);
		System.arraycopy(dx, 0, dx, i, c);
		System.arraycopy(fdy, 0, fdy, i, c);
		System.arraycopy(bdy, 0, bdy, i, c);
	}
	j = c >> 3;	/* first 1/8: dy == 1 */
	for (i = 0, x = y = 0, aa = 0; i <= j; x = fx[i], y = fy[i], i++)
		a[i] = aa += dx[i] < 0? x + y: x;
	j = c >> 2;	/* second 1/8: dx == -1 */
	for (; i <= j; x = fx[i], y = fy[i], i++)
		a[i] = aa += fdy[i] > 0? x + y: y;
	k = c + c + c;	/* rest is same as first 1/4 plus first 1/4 total */
	for (i = 0; j < k; a[j++] = a[i++] + aa);
	return (c);
}

/* make circle in ax,ay with radius squared r2, return circumference */
/* needs array with about 8*sqrt(2)/2*sqrt(r2) elements; 5.66*r */
/* makes 4 points if r == 0.  you better round up sqrt() */
private int makecircle(int[] ax, int[] ay, int r2)
{
	int x, y, dx2, dy2, err, inerr, n, i, j;

	/* 1st 1/8 by least squares */
	x = sqrt(r2) + 1;
	err = x*x - r2 + 1;
	dx2 = x + x - 1; dy2 = -1;
	for (y = 0; x >= y; y++, dy2 += 2) {
		err += dy2;
		inerr = err - dx2;
		if (err + inerr >= 0) { err = inerr; x--; dx2 -= 2; }
		ax[y] = x; ay[y] = y;
	}
	/* 2nd 1/8 by reflection */
	n = x + y - 1;
	for (i = n, j = 0; --i > ++j; ax[i] = ay[j], ay[i] = ax[j]);
	/* last 3/4 by rotation */
	i = n; n += n; n += n;
	for (j = 0; i < n; i++, j++) { ax[i] = -ay[j]; ay[i] = ax[j]; }
	return (n);
}

/* return area of circle with radius squared r2, by the same algorithm */
private int areacircle(int r2)
{
	int x, y, dx2, dy2, err, inerr, n, i, j;
	int a, la;

	x = sqrt(r2) + 1;
	err = x*x - r2 + 1;
	dx2 = x + x - 1; dy2 = -1;
	a = -x;
	if (err > x) a++;
	for (y = 0; x >= y; y++, dy2 += 2) {
		err += dy2;
		inerr = err - dx2;
		if (err + inerr >= 0) { err = inerr; x--; dx2 -= 2; a += y; }
		a += x;
	}
	n = x + y - 1;
	a <<= 1;
	if (x != --y) a -= x + y;
	return (a << 2);
}

/* move tracer point to x,y, return error code: */
/* -1: too close to pole, 1 too far, 0 just right */
private int trace(int x, int y)
{
	int TPx, TPy, TP2, FPx, FPy, FTx, FTy, FWx, FWy, TWx, TWy, TFx, TFy;
	boolean	fdir;	/* F needs to go forward (ccw) to its new position */
	boolean tdir;	/* F is forward (ccw) of T (xor ccw) */
	boolean fwdir;	/* W needs to go forward (ccw), relative to F */
	boolean twdir;	/* W needs to go forward (ccw), relative to T */
	boolean fwfar;	/* W needs to go at least 90 degrees, relative to F */
	boolean twfar;	/* W needs to go at least 90 degrees, relative to T */
	int i, n;

	if (Tx == x && Ty == y) return (0);
	TPx = x - Px; TPy = y - Py;
	TP2 = TPx*TPx + TPy*TPy;
	if (TP2 < TPmin2) return (-1);
	if (TP2 > TPmax2) return ( 1);
	FWx = Fx - Wx; FWy = Fy - Wy;
	TWx = Tx - Wx; TWy = Ty - Wy;
	Tx = x; Ty = y;
	FPx = Fx - Px; FPy = Fy - Py;
	/* search for F and update FP polygon */
	/* direction around FP (drawing of fdir on a plane forms a ying-yang) */
	tdir = (TPx*FPy - TPy*FPx < 0) != ccw;
	if (tdir)	{ x = TPx + FPx; y = TPy + FPy; }
	else		{ x = TPx - FPx; y = TPy - FPy; }
	fdir = (x*x + y*y > TF2) == tdir != ccw;
	if (fdir) {	/* move ccw on FP circle to find F on TF circle */
		i = FPfi;
		if (tdir) {	/* first move ccw on FP circle to cross T */
			i = searchline(i, FPfdx, FPfdy, TPx, TPy, FPx, FPy);
			FPx = FPfx[i]; FPy = FPfy[i];
		}
		FTx = FPx - TPx; FTy = FPy - TPy;
		i = searchcircle(i - 1, FPfdx, FPfdy, TF2,
				FTx - FPfdx[i], FTy - FPfdy[i]);
		if (i < FPfi) i = FPfi;
		FPx = FPfx[i]; FPy = FPfy[i];
		FPag += FPa[i] - FPa[FPfi];
		FPfig += i - FPfi;	/* claim FP circle updated */
		FPbig -= i - FPfi;
		if (FPfig > FPflg) {	/* need more polygon space */
			while (FPfig > (FPflg += FPflg));
			FPfxg = newint(FPfxg, FPflg, FPfug);
			FPfyg = newint(FPfyg, FPflg, FPfug);
		}
		n = FPfig - FPfug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(FPfx, i - n + 1, FPfxg, FPfug, n);
			System.arraycopy(FPfy, i - n + 1, FPfyg, FPfug, n);
			FPfug += n;
		}
		if (i >= FPc) i -= FPc;
		FPfi = i; FPbi = FPc - i;	/* update reference position */
	}
	else {		/* move cw on FP circle to find F on TF circle */
		i = FPbi;
		if (tdir) {	/* first move cw on FP circle to cross T */
			i = searchline(i, FPbdx, FPbdy, TPx, TPy, FPx, FPy);
			FPx = FPbx[i]; FPy = FPby[i];
		}
		FTx = FPx - TPx; FTy = FPy - TPy;
		i = searchcircle(i - 1, FPbdx, FPbdy, TF2,
				FTx - FPbdx[i], FTy - FPbdy[i]);
		if (i < FPbi) i = FPbi;
		FPx = FPbx[i]; FPy = FPby[i];
		FPag -= FPa[i] - FPa[FPbi];
		FPbig += i - FPbi;	/* claim pole polygon updated */
		FPfig -= i - FPbi;
		if (FPbig > FPblg) {	/* need more polygon space */
			while (FPbig > (FPblg += FPblg));
			FPbxg = newint(FPbxg, FPblg, FPbug);
			FPbyg = newint(FPbyg, FPblg, FPbug);
		}
		n = FPbig - FPbug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(FPbx, i - n + 1, FPbxg, FPbug, n);
			System.arraycopy(FPby, i - n + 1, FPbyg, FPbug, n);
			FPbug += n;
		}
		if (i >= FPc) i -= FPc;
		FPbi = i; FPfi = FPc - i;	/* update reference position */
	}
	Fx = FPx + Px; Fy = FPy + Py;	/* update Fx,Fy */
	TFx = Tx - Fx; TFy = Ty - Fy;
	/* search for W and update FW polygon (if FW non-zero) */
	fwdir = (TFx*FWy - TFy*FWx <  0) == (FWT <  0);
	twdir = (TFx*TWy - TFy*TWx <  0) == (FWT <= 0);
	fwfar = (TFx*FWx + TFy*FWy <= 0) == (FWT <  0);
	twfar = (TFx*TWx + TFy*TWy <= 0) == (FWT <= 0);
	if (fwdir != twdir && (FW2 > TW2? fwfar: twfar))
		if (FW2 > TW2)	twdir = fwdir;
		else		fwdir = twdir;
	if (FW2 != 0) if (fwdir) {
			/* move ccw on FW circle to find W */
		i = FWfi;
		if (fwfar) i += FWc >> 2;
		i = searchline(i - 1, FWfdx, FWfdy, TFx, TFy,
				FWfx[i] - FWfdx[i], FWfy[i] - FWfdy[i]);
		if (i < FWfi) i = FWfi;
		FWx = FWfx[i]; FWy = FWfy[i];
		FWag += FWa[i] - FWa[FWfi];
		FWfig += i - FWfi;	/* claim FW circle updated */
		FWbig -= i - FWfi;
		if (FWfig > FWflg) {	/* need more polygon space */
			while (FWfig > (FWflg += FWflg));
			FWfxg = newint(FWfxg, FWflg, FWfug);
			FWfyg = newint(FWfyg, FWflg, FWfug);
		}
		n = FWfig - FWfug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(FWfx, i - n + 1, FWfxg, FWfug, n);
			System.arraycopy(FWfy, i - n + 1, FWfyg, FWfug, n);
			FWfug += n;
		}
		if (i >= FWc) i -= FWc;
		FWfi = i; FWbi = FWc - i;	/* update reference position */
	}
	else {		/* move cw on FW circle to find W */
		i = FWbi;
		if (fwfar) i += FWc >> 2;
		i = searchline(i - 1, FWbdx, FWbdy, TFx, TFy,
				FWbx[i] - FWbdx[i], FWby[i] - FWbdy[i]);
		if (i < FWbi) i = FWbi;
		FWx = FWbx[i]; FWy = FWby[i];
		FWag -= FWa[i] - FWa[FWbi];
		FWbig += i - FWbi;	/* claim pole polygon updated */
		FWfig -= i - FWbi;
		if (FWbig > FWblg) {	/* need more polygon space */
			while (FWbig > (FWblg += FWblg));
			FWbxg = newint(FWbxg, FWblg, FWbug);
			FWbyg = newint(FWbyg, FWblg, FWbug);
		}
		n = FWbig - FWbug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(FWbx, i - n + 1, FWbxg, FWbug, n);
			System.arraycopy(FWby, i - n + 1, FWbyg, FWbug, n);
			FWbug += n;
		}
		if (i >= FWc) i -= FWc;
		FWbi = i; FWfi = FWc - i;	/* update reference position */
	}
	/* search for W and update TW polygon (if TW non-zero) */
	if (TW2 != 0) if (twdir) {
			/* move ccw on TW circle to find W */
		i = TWfi;
		if (twfar) i += TWc >> 2;
		i = searchline(i - 1, TWfdx, TWfdy, TFx, TFy,
				TWfx[i] - TWfdx[i], TWfy[i] - TWfdy[i]);
		if (i < TWfi) i = TWfi;
		TWx = TWfx[i]; TWy = TWfy[i];
		TWag += TWa[i] - TWa[TWfi];
		TWfig += i - TWfi;	/* claim TW circle updated */
		TWbig -= i - TWfi;
		if (TWfig > TWflg) {	/* need more polygon space */
			while (TWfig > (TWflg += TWflg));
			TWfxg = newint(TWfxg, TWflg, TWfug);
			TWfyg = newint(TWfyg, TWflg, TWfug);
		}
		n = TWfig - TWfug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(TWfx, i - n + 1, TWfxg, TWfug, n);
			System.arraycopy(TWfy, i - n + 1, TWfyg, TWfug, n);
			TWfug += n;
		}
		if (i >= TWc) i -= TWc;
		TWfi = i; TWbi = TWc - i;	/* update reference position */
	}
	else {		/* move cw on TW circle to find W */
		i = TWbi;
		if (twfar) i += TWc >> 2;
		i = searchline(i - 1, TWbdx, TWbdy, TFx, TFy,
				TWbx[i] - TWbdx[i], TWby[i] - TWbdy[i]);
		if (i < TWbi) i = TWbi;
		TWx = TWbx[i]; TWy = TWby[i];
		TWag -= TWa[i] - TWa[TWbi];
		TWbig += i - TWbi;	/* claim pole polygon updated */
		TWfig -= i - TWbi;
		if (TWbig > TWblg) {	/* need more polygon space */
			while (TWbig > (TWblg += TWblg));
			TWbxg = newint(TWbxg, TWblg, TWbug);
			TWbyg = newint(TWbyg, TWblg, TWbug);
		}
		n = TWbig - TWbug;
		if (n > 0) {		/* need more polygon data */
			System.arraycopy(TWbx, i - n + 1, TWbxg, TWbug, n);
			System.arraycopy(TWby, i - n + 1, TWbyg, TWbug, n);
			TWbug += n;
		}
		if (i >= TWc) i -= TWc;
		TWbi = i; TWfi = TWc - i;	/* update reference position */
	}
	Wx = (Fx - FWx + Tx - TWx) >> 1;
	Wy = (Fy - FWy + Ty - TWy) >> 1;
	/* user swept polygon */
	if (TPig == TPlg) {			/* need more polygon space */
		TPlg += TPlg;
		TPxg = newint(TPxg, TPlg, TPig);
		TPyg = newint(TPyg, TPlg, TPig);
	}
	TPig -= 2;
	i = TPig++;
	TPag -= TPxg[i] * TPyg[TPig] - TPyg[i] * TPxg[TPig];
	TPxg[TPig] = TPx; TPyg[TPig] = TPy;	/* update T */
	TPag += TPxg[i] * TPyg[TPig] - TPyg[i] * TPxg[TPig];
	i = TPig++;
	TPxg[TPig] = FPx; TPyg[TPig] = FPy;	/* update F */
	TPag += TPxg[i] * TPyg[TPig] - TPyg[i] * TPxg[TPig];
	TPig++;
	return (0);
}

/* return a new int array, size dstlen, with copy of src, size srcused */
private int[] newint(int[] src, int dstlen, int srcused)
{
	int[] dst;

	dst = new int[dstlen];
	System.arraycopy(src, 0, dst, 0, srcused);
	return (dst);
}

/* search path for crossing with circle with radius squared r2.
   return position in path. Path is rasterized curve with increments (+1,0,-1)
   in dx[],dy[]. Initial position i. Initial distance from circle center x,y.
   CAREFUL: detects a crossing, doesn't know if you're already there! */
private int searchcircle(int i, int[] dx, int[] dy, int r2, int x, int y)
{
	int err;	/* current error (signed) */
	int olderr;	/* last err */
	int d;
	boolean isneg;	/* is err negative? */

	err = x*x + y*y - r2;
	isneg = err < 0;
	do {
		olderr = err;
		i++;
		if ((d = dx[i]) != 0)
			if (d > 0)	{ err += x; x++; err += x; }
			else		{ err -= x; x--; err -= x; }
		if ((d = dy[i]) != 0)
			if (d > 0)	{ err += y; y++; err += y; }
			else		{ err -= y; y--; err -= y; }
	} while ((err < 0) == isneg);
	return ((err + olderr < 0) == isneg? i: i - 1);
}

/* search path for crossing with line through x0,y0 (and through origin)
   return position in path. Path is rasterized curve with increments (+1,0,-1)
   in dx[],dy[]. Initial position i.  Initial point is x,y.
   CAREFUL: detects a crossing, doesn't know if you're already there! */
private int searchline(int i, int[] dx, int[] dy, int x0, int y0, int x, int y)
{
	int err;	/* current error (signed) */
	int olderr;	/* last err */
	int d;
	boolean isneg;	/* is err negative? */

	err = x0*y - y0*x;
	isneg = err < 0;
	do {
		olderr = err;
		i++;
		if ((d = dx[i]) != 0)
			if (d > 0)	err -= y0;
			else		err += y0;
		if ((d = dy[i]) != 0)
			if (d > 0)	err += x0;
			else		err -= x0;
	} while ((err < 0) == isneg);
	return ((err + olderr < 0) == isneg? i: i - 1);
}

/* return the area of polygon with n vertices (x,y) */
private int area(int[] x, int[] y, int n)
{
	int t, i, x0, y0, x1, y1, x2, y2;

	t = 0;
	x0 = x[0]; y0 = y[0];
	x1 = x[1] - x0; y1 = y[1] - y0;
	for (i = 2; i < n; i++) {
		x2 = x[i] - x0; y2 = y[i] - y0;
		t += x1 * y2 - x2 * y1;
		x1 = x2; y1 = y2;
	}
	return (t);
}

/* a good argument for operator overloading, if you enjoy arguing */
int sqr(int r) { return (r*r); }
int sqr(double r) { return ((int) Math.round(r*r)); }

/* cheesy Newton's method. always gives truncated result (r*r <= r2) */
private int sqrt(long y)
{
	long x, yx;

	if (y == 0) return (0);
	for (x = y; (yx = y / x) < x; x += yx, x >>= 1);
	return ((int) x);
}

} /* end of class */
