// base.java (C) 2001 by Paul Falstad, www.falstad.com
// this file must be run through cpp with one of BUILD_E, BUILD_V,
// BUILD_M defined before compiling with java compiler.  This was done
// to reduce class file sizes.

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.awt.image.MemoryImageSource;
import java.lang.Math;
import java.awt.event.*;

#ifdef BUILD_E
#define BUILD_CASE_EMV(E,M,V) E
#elif defined(BUILD_M)
#define BUILD_CASE_EMV(E,M,V) M
#else
#define BUILD_CASE_EMV(E,M,V) V
#endif

class Vec3DemoCanvas extends Canvas {
    Vec3DemoFrame pg;
    Vec3DemoCanvas(Vec3DemoFrame p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateVec3Demo(g);
    }
    public void paint(Graphics g) {
	pg.updateVec3Demo(g);
    }
};

class Vec3DemoLayout implements LayoutManager {
    public Vec3DemoLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
	return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
	int barwidth = 0;
	int i;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (d.width > barwidth)
		    barwidth = d.width;
	    }
	}
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw-barwidth;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	cw += insets.left;
	int h = insets.top;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (m instanceof Scrollbar || m instanceof TextComponent)
		    d.width = barwidth;
		if (m instanceof Choice && d.width > barwidth)
		    d.width = barwidth;
		if (m instanceof Label) {
		    h += d.height/5;
		    d.width = barwidth;
		}
		m.move(cw, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};

public class Vec3Demo extends Applet implements ComponentListener {
    static Vec3DemoFrame ogf;
    void destroyFrame() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
    boolean started = false;
    public void init() {
	addComponentListener(this);
    }
    
    public static void main(String args[]) {
        ogf = new Vec3DemoFrame(null);
        ogf.init();
    }

    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new Vec3DemoFrame(this);
	    ogf.init();
	    repaint();
	}
    }
    
    public void paint(Graphics g) {
	String s = "Applet is open in a separate window.";
	if (!started)
	    s = "Applet is starting.";
	else if (ogf == null)
	    s = "Applet is finished.";
	else
	    ogf.show();
	g.drawString(s, 10, 30);
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) { showFrame(); }
    public void componentResized(ComponentEvent e) {}
    
    public void destroy() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
};

class Vec3DemoFrame extends Frame
    implements ComponentListener, ActionListener, AdjustmentListener,
	       MouseMotionListener, MouseListener, ItemListener {
    
    Thread engine = null;

    Dimension winSize;
    Rectangle viewMain, viewAxes;
    Image dbimage;
    
    Vec3Demo applet;
    Random random;
    
    public String getAppletInfo() {
	return "Vec3Demo by Paul Falstad";
    }

    static final double pi = 3.14159265358979323846;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    Vec3DemoCanvas cv;
    Checkbox stoppedCheck;
    Button resetButton;
    Button kickButton;
    Checkbox reverseCheck;
    Button infoButton;
    Choice functionChooser;
    Choice dispChooser;
#ifndef BUILD_M
    static final int DISP_PART_VELOC    = 0;
    static final int DISP_PART_FORCE    = 1;
    static final int DISP_VECTORS       = 2;
    static final int DISP_LINES         = 3;
    static final int DISP_EQUIPS        = 4;
    static final int DISP_PART_VELOC_A  = -1;
    static final int DISP_VECTORS_A     = -2;
    static final int DISP_PART_MAG      = -3;
    static final int DISP_VIEW_PAPER    = -4;
#else
    static final int DISP_PART_VELOC    = 0;
    static final int DISP_PART_VELOC_A  = 1;
    static final int DISP_VECTORS       = 2;
    static final int DISP_VECTORS_A     = 3;
    static final int DISP_LINES         = 4;
    static final int DISP_PART_MAG      = 5;
    static final int DISP_VIEW_PAPER    = 6;
    static final int DISP_EQUIPS        = -1;
    static final int DISP_PART_FORCE    = -4;
#endif
    Choice sliceChooser;
    static final int SLICE_NONE = 0;
    static final int SLICE_X = 1;
    static final int SLICE_Y = 2;
    static final int SLICE_Z = 3;
    Label partCountLabel;
    Label textFieldLabel;
    Label strengthLabel;
    Scrollbar partCountBar;
    Scrollbar strengthBar;
    Scrollbar aux1Bar;
    Scrollbar aux2Bar;
    Scrollbar aux3Bar;
    double fieldStrength, partMult;
    Color darkYellow = new Color(144, 144, 0);
    final double lineWidth = .01;
    class AuxBar {
	Scrollbar bar;
	Label label;
	AuxBar(Label l, Scrollbar b) { label = l; bar = b; }
    };
    AuxBar auxBars[];
    Label vecDensityLabel;
    Scrollbar vecDensityBar;
    Label potentialLabel;
    Scrollbar potentialBar;
    Label lineDensityLabel;
    Scrollbar lineDensityBar;
    Choice modeChooser;
    TextField textFields[];
    static final int MODE_ANGLE = 0;
    static final int MODE_ZOOM = 1;
    static final int MODE_SLICE = 2;
    int reverse;
    int xpoints[];
    int ypoints[];
    int slicerPoints[][];
    double sliceFaces[][];
    double sliceFace[];
    Particle particles[];
    FieldVector vectors[];
    int vecCount;
    int density[][][];
    double sliceval = 0;
    double rotmatrix[];
    double cameraPos[];
    double intersection[];
    double intersectionDistance;
    int vectorSpacing = 16;
    int currentStep;
    boolean selectedSlice, mouseDown, getPot;
    boolean showA;
    boolean parseError;
    Color fieldColors[];
    Color equipColors[];

    // was 1./5, 10; 1./3, 6 worked better
    static final double densitygroupsize = 1./2;
    static final int densitygridsize = 4;
    static final int maxParticleCount = 5000;
    double zoom = 3;
    boolean dragging;
    int oldDragX, oldDragY, dragX, dragY, dragStartX, dragStartY;
    double dragZoomStart, lastXRot, lastYRot;
    Vector functionList;
    VecFunction curfunc;
    int pause = 20;

    Vec3DemoFrame(Vec3Demo a) {
	super("3-D Vector Fields Applet v1.3c");
	applet = a;
    }

    public void init() {
	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	
	functionList = new Vector();
	VecFunction vf = new
	    BUILD_CASE_EMV(InverseSquaredRadial(),
			   InverseRotational(),
			   InverseSquaredRadial());
	while (vf != null) {
	    functionList.addElement(vf);
	    vf = vf.createNext();
	}
	random = new Random();
	particles = new Particle[maxParticleCount];
	int i;
	for (i = 0; i != maxParticleCount; i++)
	    particles[i] = new Particle();
	xpoints = new int[4];
	ypoints = new int[4];
	slicerPoints = new int[2][5*2];
	sliceFaces = new double[4][3];
	rotmatrix = new double[9];
	setXYView();
	density = new int[densitygridsize][densitygridsize][densitygridsize];
	setLayout(new Vec3DemoLayout());
	cv = new Vec3DemoCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	/*infoButton = new Button("Function Info");
	add(infoButton);
	infoButton.addActionListener(this);*/

	add(new Label("Field selection:"));
	functionChooser = new Choice();
	for (i = 0; i != functionList.size(); i++)
	    functionChooser.add(
	       ((VecFunction) functionList.elementAt(i)).getName());
	add(functionChooser);
	functionChooser.addItemListener(this);

	dispChooser = new Choice();
	dispChooser.addItemListener(this);
	setupDispChooser(true);
	add(dispChooser);

	modeChooser = new Choice();
	modeChooser.add("Mouse = Adjust Angle");
	modeChooser.add("Mouse = Adjust Zoom");
	modeChooser.addItemListener(this);
	add(modeChooser);
	
	sliceChooser = new Choice();
	sliceChooser.add("No Slicing");
	sliceChooser.add("Show X Slice");
	sliceChooser.add("Show Y Slice");
	sliceChooser.add("Show Z Slice");
	sliceChooser.addItemListener(this);
	add(sliceChooser);
	
	stoppedCheck = new Checkbox("Stopped");
	stoppedCheck.addItemListener(this);
	add(stoppedCheck);

	reverseCheck = new Checkbox("Reverse");
	reverseCheck.addItemListener(this);
	add(reverseCheck);

	resetButton = new Button("Reset");
	add(resetButton);
	resetButton.addActionListener(this);
	kickButton = new Button("Kick");
#ifndef BUILD_M
	add(kickButton);
	kickButton.addActionListener(this);
	kickButton.disable();
#endif

	add(strengthLabel = new Label("Field Strength", Label.CENTER));
	add(strengthBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 100));
	strengthBar.addAdjustmentListener(this);

	add(partCountLabel = new Label("Number of Particles", Label.CENTER));
	add(partCountBar = new Scrollbar(Scrollbar.HORIZONTAL,
					 500, 1, 1,
					 maxParticleCount));
	partCountBar.addAdjustmentListener(this);

	add(vecDensityLabel = new Label("Vector Density", Label.CENTER));
	add(vecDensityBar = new Scrollbar(Scrollbar.HORIZONTAL, 16, 1, 2, 64));
	vecDensityBar.addAdjustmentListener(this);

	add(lineDensityLabel = new Label(
#ifdef BUILD_V
					 "Streamline Density",
#else
					 "Field Line Density",
#endif
					 Label.CENTER));
	add(lineDensityBar = new Scrollbar(Scrollbar.HORIZONTAL, 5, 1, 3, 16));
	lineDensityBar.addAdjustmentListener(this);

	add(potentialLabel = new Label("Potential", Label.CENTER));
	add(potentialBar = new Scrollbar(Scrollbar.HORIZONTAL, 250,
					 1, 0, 1000));
	potentialBar.addAdjustmentListener(this);

	Label lb;
	auxBars = new AuxBar[3];
	add(lb = new Label("Aux 1", Label.CENTER));
	add(aux1Bar = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 100));
	aux1Bar.addAdjustmentListener(this);
	auxBars[0] = new AuxBar(lb, aux1Bar);

	add(lb = new Label("Aux 2", Label.CENTER));
	add(aux2Bar = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 100));
	aux2Bar.addAdjustmentListener(this);
	auxBars[1] = new AuxBar(lb, aux2Bar);

	add(lb = new Label("Aux 3", Label.CENTER));
	add(aux3Bar = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 100));
	aux3Bar.addAdjustmentListener(this);
	auxBars[2] = new AuxBar(lb, aux3Bar);

#ifdef BUILD_V
	add(textFieldLabel = new Label("", Label.CENTER));
#endif

	textFields = new TextField[3];
	for (i = 0; i != 3; i++) {
	    add(textFields[i] = new TextField());
	    textFields[i].addActionListener(this);
	}
	fieldColors = new Color[513];
	for (i = 0; i != 256; i++) {
	    int col = (255<<24) | (i<<8);
	    fieldColors[i] = new Color(col);
	}
	for (i = 0; i != 256; i++) {
	    int col = (255<<24) | (255<<8) | (i * (0x10001));
	    fieldColors[i+256] = new Color(col);
	}
	fieldColors[512] = fieldColors[511];
	equipColors = new Color[513];
	for (i = 0; i != 256; i++) {
	    int r = 255-i/2;
	    int gb = i/2;
	    int col = (255<<24) | (r<<16) | (gb<<8) | gb;
	    equipColors[i] = new Color(col);
	}
	for (i = 0; i != 256; i++) {
	    int g = 128+i/2;
	    int rb = 128-i/2;
	    int col = (255<<24) | (rb<<16) | (g<<8) | rb;
	    equipColors[i+256] = new Color(col);
	}
	equipColors[512] = equipColors[511];

	add(new Label("http://www.falstad.com", Label.CENTER));
	intersection = new double[3];

	reinit();
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);

	resize(500, 500);
	handleResize();
	Dimension screen = getToolkit().getScreenSize();
	Dimension x = getSize();
	setLocation((screen.width  - x.width)/2,
		    (screen.height - x.height)/2);
	functionChanged();
	dispChooserChanged();
	show();
	requestFocus();
    }

    void setViewMatrix(double a, double b) {
	int i;
	for (i = 0; i != 9; i++)
	    rotmatrix[i] = 0;
	rotmatrix[0] = rotmatrix[4] = rotmatrix[8] = 1;
	rotate(a, b);
	lastXRot = lastYRot = 0;
    }

    void setXYView() {
	setViewMatrix(0, pi/11);
    }

    void setXYViewExact() {
	setViewMatrix(0, 0);
    }

    void setXZView() {
	setViewMatrix(0, pi/11-pi/2);
    }

    void setXZViewExact() {
	setViewMatrix(0, -pi/2);
    }

    void handleResize() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	dbimage = createImage(d.width, d.height);
	scaleworld();
	viewMain = new Rectangle(winSize);
	viewAxes = new Rectangle(winSize.width-100, 0, 100, 100);
    }

    void resetDensityGroups() {
	int i, j, k;
	for (i = 0; i != densitygridsize; i++)
	    for (j = 0; j != densitygridsize; j++)
		for (k = 0; k != densitygridsize; k++)
		    density[i][j][k] = 0;
	int slice = sliceChooser.getSelectedIndex();
	boolean sliced = (slice > 0);
	int pcount = getParticleCount();
	for (i = 0; i != pcount; i++) {
	    Particle p = particles[i];
	    if (sliced)
		p.pos[slice-SLICE_X] = sliceval;
	    addToDensityGroup(p);
	}

	// invalidate all unused particles so they will be
	// repositioned if the particle slider is moved to the right,
	// rather than just falling in where they happened to be last time
	for (; i != maxParticleCount; i++) {
	    Particle p = particles[i];
	    p.lifetime = -100;
	}
    }

    int addToDensityGroup(Particle p) {
	int a = (int)((p.pos[0]+1)*(densitygridsize/2));
	int b = (int)((p.pos[1]+1)*(densitygridsize/2));
	int c = (int)((p.pos[2]+1)*(densitygridsize/2));
	int n = 0;
	try {
	n = ++density[a][b][c];
	if (n > maxParticleCount)
	    System.out.print(a + " " + b + " " + c + " " + density[a][b][c] + "\n");
	} catch (Exception e) {
	    System.out.print(p.pos[0] + " " + p.pos[1] + " " + p.pos[2] + "\n");
	    e.printStackTrace();
	}
	return n;
    }

    void removeFromDensityGroup(Particle p) {
	int a = (int)((p.pos[0]+1)*(densitygridsize/2));
	int b = (int)((p.pos[1]+1)*(densitygridsize/2));
	int c = (int)((p.pos[2]+1)*(densitygridsize/2));
	try {
	    if (--density[a][b][c] < 0)
		System.out.print(a + " " + b + " " + c + " " + density[a][b][c] + "\n");
	} catch (Exception e) {
	    System.out.print(p.pos[0] + " " + p.pos[1] + " " + p.pos[2] + "\n");
	    e.printStackTrace();
	}
    }

    void positionParticle(Particle p) {
	int x, y, z;
	int bestx = 0, besty = 0, bestz = 0;
	int best = 10000;

	// we avoid scanning the grid in the same order every time
	// so that we treat equal-density squares as equally as possible.
	int randaddx = getrand(densitygridsize);
	int randaddy = getrand(densitygridsize);
	int randaddz = getrand(densitygridsize);
	for (x = 0; x != densitygridsize; x++)
	    for (y = 0; y != densitygridsize; y++)
		for (z = 0; z != densitygridsize; z++) {
		    int ix = (randaddx + x) % densitygridsize;
		    int iy = (randaddy + y) % densitygridsize;
		    int iz = (randaddz + z) % densitygridsize;
		    if (density[ix][iy][iz] <= best) {
			bestx = ix;
			besty = iy;
			bestz = iz;
			best = density[ix][iy][iz];
		    }
		}
	p.pos[0] = bestx*densitygroupsize +
	    getrand(100)*densitygroupsize/100.0 - 1;
	p.pos[1] = besty*densitygroupsize +
	    getrand(100)*densitygroupsize/100.0 - 1;
	p.pos[2] = bestz*densitygroupsize +
	    getrand(100)*densitygroupsize/100.0 - 1;
	p.lifetime = curfunc.redistribute() ? 500 : 5000;
	p.stepsize = 1;
	p.theta = (getrand(101)-50)*pi/50.;
	p.phi   = (getrand(101)-50)*pi/50.;
	int j;
	for (j = 0; j != 3; j++)
	    p.vel[j] = 0;
    }

    int getParticleCount() {
	return partCountBar.getValue();
    }

    void resetParticles() {
	int pcount = getParticleCount();
	int i, j;
	for (i = 0; i != pcount; i++) {
	    Particle p = particles[i];
	    for (j = 0; j != 3; j++) {
		p.pos[j] = getrand(200)/100.0 - 1;
		p.vel[j] = 0;
	    }
	    p.lifetime = i*2;
	    p.stepsize = 1;
	}
	resetDensityGroups();
    }

    void kickParticles() {
	int i, j;
	for (i = 0; i != getParticleCount(); i++) {
	    Particle p = particles[i];
	    for (j = 0; j != 3; j++)
		p.vel[j] += (getrand(100)/99.0 - .5) * .04;
	}
    }


    // multiply rotation matrix by rotations through angle1 and angle2
    void rotate(double angle1, double angle2) {
	double r1cos = java.lang.Math.cos(angle1);
	double r1sin = java.lang.Math.sin(angle1);
	double r2cos = java.lang.Math.cos(angle2);
	double r2sin = java.lang.Math.sin(angle2);
	double rotm2[] = new double[9];

	// angle1 is angle about y axis, angle2 is angle about x axis
	rotm2[0] = r1cos;
	rotm2[1] = -r1sin*r2sin;
	rotm2[2] = r2cos*r1sin;

	rotm2[3] = 0;
	rotm2[4] = r2cos;
	rotm2[5] = r2sin;

	rotm2[6] = -r1sin;
	rotm2[7] = -r1cos*r2sin;
	rotm2[8] = r1cos*r2cos;
	rotate(rotm2);
    }

    void rotate(double rotm2[]) {
	double rotm1[] = rotmatrix;
	rotmatrix = new double[9];

	int i, j, k;
	for (j = 0; j != 3; j++)
	    for (i = 0; i != 3; i++) {
		double v = 0;
		for (k = 0; k != 3; k++)
		    v += rotm1[k+j*3]*rotm2[i+k*3];
		rotmatrix[i+j*3] = v;
	    }
    }

    void reinit() {
	handleResize();
	resetParticles();
    }

    void centerString(Graphics g, String s, int y) {
	FontMetrics fm = g.getFontMetrics();
        g.drawString(s, (winSize.width-fm.stringWidth(s))/2, y);
    }

    public void paint(Graphics g) {
	cv.repaint();
    }

    static final double root2 = 1.4142135623730950488016887242096981;
    double scalex, scaley;
    static final double viewDistance = 5;

    void map3d(double x, double y, double z, int xpoints[],
	       int ypoints[], int pt) {
	map3d(x, y, z, xpoints, ypoints, pt, viewMain);
    }

    void map3d(double x, double y, double z,
	       int xpoints[], int ypoints[], int pt, Rectangle view) {
	double rotm[] = rotmatrix;
	double realx =               x*rotm[0] + y*rotm[3] + z*rotm[6];
	double realy =               x*rotm[1] + y*rotm[4] + z*rotm[7];
	double realz = viewDistance-(x*rotm[2] + y*rotm[5] + z*rotm[8]);
	double scalex = view.width*zoom/2;
	double scaley = view.height*zoom/2;
	double aratio = view.width/(double) view.height;
	// preserve aspect ratio regardless of window dimensions
	if (aratio < 1)
	    scaley *= aratio;
	else
	    scalex /= aratio;
	xpoints[pt] = view.x + view.width /2 + (int) (scalex*realx/realz);
	ypoints[pt] = view.y + view.height/2 - (int) (scaley*realy/realz);
    }

    double getScalingFactor(double x, double y, double z) {
	double rotm[] = rotmatrix;
	double realz = viewDistance-(x*rotm[2] + y*rotm[5] + z*rotm[8]);
	double scalex = winSize.width*zoom/2;
	double scaley = winSize.height*zoom/2;
	double aratio = winSize.width/(double) winSize.height;
	// preserve aspect ratio regardless of window dimensions
	if (aratio < 1)
	    scaley *= aratio;
	else
	    scalex /= aratio;
	// scalex and scaley should now be approx. equal
	return scalex/realz;
    }

    // map point on screen to 3-d coordinates assuming given z depth
    void unmap3d(double x3[], int x, int y, double z, Rectangle view) {
	double scalex = view.width*zoom/2;
	double scaley = view.height*zoom/2;

	double aratio = view.width/(double) view.height;
	// preserve aspect ratio regardless of window dimensions
	if (aratio < 1)
	    scaley *= aratio;
	else
	    scalex /= aratio;

	double realz = viewDistance-z;
	double realx =  (x-(view.width/2))*realz/scalex;
	double realy = -(y-(view.height/2))*realz/scaley;
	double rotm[] = rotmatrix;
	x3[0] = (realx*rotm[0] + realy*rotm[1] + z*rotm[2]);
	x3[1] = (realx*rotm[3] + realy*rotm[4] + z*rotm[5]);
	x3[2] = (realx*rotm[6] + realy*rotm[7] + z*rotm[8]);
    }

    // map point on screen to 3-d coordinates assuming it lies on a given plane
    void unmap3d(double x3[], int x, int y, double pn[], double pp[],
		 Rectangle view) {
	// first, find all points which map to (x,y) on the screen.
	// this is a line.
	double scalex = view.width*zoom/2;
	double scaley = view.height*zoom/2;

	double aratio = view.width/(double) view.height;
	// preserve aspect ratio regardless of window dimensions
	if (aratio < 1)
	    scaley *= aratio;
	else
	    scalex /= aratio;

	double vx =  (x-(view.width/2))/scalex;
	double vy = -(y-(view.height/2))/scaley;
	// vz = -1
	
	// map the line vector to object space (we know where the camera
	// is in object space already so no need to map that)
	double rotm[] = rotmatrix;
	double mvx = (vx*rotm[0] + vy*rotm[1] - rotm[2]);
	double mvy = (vx*rotm[3] + vy*rotm[4] - rotm[5]);
	double mvz = (vx*rotm[6] + vy*rotm[7] - rotm[8]);
	
	// calculate the intersection between the line and the given plane
	double t = ((pp[0]-cameraPos[0])*pn[0] +
		    (pp[1]-cameraPos[1])*pn[1] +
		    (pp[2]-cameraPos[2])*pn[2]) /
	    (pn[0]*mvx+pn[1]*mvy+pn[2]*mvz);

	x3[0] = cameraPos[0]+mvx*t;
	x3[1] = cameraPos[1]+mvy*t;
	x3[2] = cameraPos[2]+mvz*t;
    }

    void scaleworld() {
	scalex = winSize.width/2;
	scaley = winSize.height/2;
    }

    long lastTime;
    double timeStep;
    
    public void updateVec3Demo(Graphics realg) {
	Graphics g = dbimage.getGraphics();
	if (winSize == null || winSize.width == 0)
	    return;
	if (xpoints == null)
	    return;
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	g.setColor(cv.getForeground());
	// XXX make it true, then fix it so current animation works w/ vectors
	boolean allquiet = false;

	curfunc.setupFrame();
	int disp = dispChooser.getSelectedIndex();
	timeStep = 1;
	if (!stoppedCheck.getState()) {
	    if (lastTime > 0)
		timeStep = (System.currentTimeMillis()-lastTime)*.03;
	    if (timeStep > 3)
		timeStep = 3;
	    lastTime = System.currentTimeMillis();
	    if (disp != DISP_VECTORS && disp != DISP_VECTORS_A &&
		disp != DISP_LINES && disp != DISP_EQUIPS) {
		moveParticles();
		allquiet = false;
	    }
	    currentStep = (int)
		(reverse*(lastTime/30) % 800);
	    if (currentStep < 0)
		currentStep += 800;
	} else {
	    lastXRot = lastYRot = 0;
	    lastTime = 0;
	}

	drawCube(g, true);

	cameraPos = new double[3];
	unmap3d(cameraPos, winSize.width/2, winSize.height/2, viewDistance,
		viewMain);
	if (disp == DISP_VECTORS || disp == DISP_VECTORS_A)
	    drawVectors(g);
	else if (disp == DISP_LINES) {
	    genLines();
	    drawLines(g);
#ifndef BUILD_M
	} else if (disp == DISP_EQUIPS) {
	    genEquips();
	    drawLines(g);
#endif
	} else if (disp == DISP_VIEW_PAPER)
	    drawViewPaper(g);
	else
	    drawParticles(g);

	g.setColor(Color.gray);
	drawCube(g, false);

	drawAxes(g);
	curfunc.finishFrame();

	if (parseError)
	    centerString(g, "Can't parse expression", winSize.height-20);

	//g.drawString("FR: " + framerate, 0, 10);
	realg.drawImage(dbimage, 0, 0, this);
	long t = System.currentTimeMillis();
	frames++;
	if (firsttime == 0)
	    firsttime = t;
	else if (t-firsttime > 1000) {
	    framerate = frames;
	    firsttime = t;
	    frames = 0;
	}
	if (mouseDown)
	    lastXRot = lastYRot = 0;
	else if (lastXRot != 0 || lastYRot != 0) {
	    rotate(lastXRot*timeStep, lastYRot*timeStep);
	    allquiet = false;
	}
	if (!stoppedCheck.getState() && !allquiet)
	    cv.repaint(pause);
    }

    void drawCurrentArrow(Graphics g, int x1, int y1, int x2, int y2) {
	if (reverse == 1)
	    drawArrow(g, null, x1, y1, x2, y2, 7);
	else
	    drawArrow(g, null, x2, y2, x1, y1, 7);
    }

    void drawCurrentLine(Graphics g, int x1, int y1, int x2, int y2, int n,
			 boolean doArrow, int dir) {
	int i;
	if (dir == -1) {
	    int x3 = x1;
	    int y3 = y1;
	    x1 = x2; y1 = y2;
	    x2 = x3; y2 = y3;
	}
	int x0 = x1;
	int y0 = y1;
	n *= 3;
	for (i = 1; i <= n; i++) {
	    int x = (x2-x1)*i/n + x1;
	    int y = (y2-y1)*i/n + y1;
	    g.setColor(Color.yellow);
	    if (i == n && doArrow && reverse == 1)
		drawCurrentArrow(g, x0, y0, x, y);
	    else if (i == 1 && doArrow && reverse == -1)
		drawCurrentArrow(g, x0, y0, x, y);
	    else {
		g.setColor(getCurrentColor(i));
		g.drawLine(x0, y0, x, y);
	    }
	    x0 = x; y0 = y;
	}
    }

    Color getCurrentColor(int i) {
	return (((currentStep/2+400-i) & 4) > 0) ?
	    Color.yellow : Color.darkGray;
    }
    
    void drawSphere(Graphics g, double r, boolean back) {
	int i;
	int ct = 10;
	for (i = 0; i != ct; i++) {
	    double th1 = pi*2*i/ct;
	    double th2 = pi*2*(i+1)/ct;
	    double sinth1 = r*java.lang.Math.sin(th1);
	    double costh1 = r*java.lang.Math.cos(th1);
	    double sinth2 = r*java.lang.Math.sin(th2);
	    double costh2 = r*java.lang.Math.cos(th2);
	    if (backFacing(costh1, sinth1, 0, costh1, sinth1, 0) == back) {
		map3d(costh1, sinth1, 0, xpoints, ypoints, 0);
		map3d(costh2, sinth2, 0, xpoints, ypoints, 1);
		g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	    if (backFacing(0, costh1, sinth1, 0, costh1, sinth1) == back) {
		map3d(0, costh1, sinth1, xpoints, ypoints, 0);
		map3d(0, costh2, sinth2, xpoints, ypoints, 1);
		g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	    if (backFacing(costh1, 0, sinth1, costh1, 0, sinth1) == back) {
		map3d(costh1, 0, sinth1, xpoints, ypoints, 0);
		map3d(costh2, 0, sinth2, xpoints, ypoints, 1);
		g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	}
    }

    void fillSphere(Graphics g, double r, double xoff) {
	int i, j;
	int ct = 20;
	for (i = 0; i != ct; i++) {
	    double th1 = pi*i/ct;
	    double th2 = pi*(i+1)/ct;
	    double costh1 = r*java.lang.Math.cos(th1);
	    double sinth1 = r*java.lang.Math.sin(th1);
	    double costh2 = r*java.lang.Math.cos(th2);
	    double sinth2 = r*java.lang.Math.sin(th2);
	    double cosph1 = 1, sinph1 = 0;
	    for (j = 0; j != ct; j++) {
		double ph2 = 2*pi*(j+1)/ct;
		double cosph2 = java.lang.Math.cos(ph2);
		double sinph2 = java.lang.Math.sin(ph2);
		double x1 = sinth1*cosph1;
		double y1 = sinth1*sinph1;
		double z1 = costh1;
		double x = cameraPos[0]-(x1+xoff);
		double y = cameraPos[1]-y1;
		double z = cameraPos[2]-z1;
		double d = x*x1+y*y1+z*z1;
		if (d > 0) {
		    int dd = (int) (d/r*40);
		    if (dd > 255)
			dd = 255;
		    g.setColor(new Color(dd, dd, 0));
		    map3d(xoff+x1, y1, z1, xpoints, ypoints, 0);
		    map3d(xoff+sinth1*cosph2, sinth1*sinph2, costh1,
			  xpoints, ypoints, 1);
		    map3d(xoff+sinth2*cosph2, sinth2*sinph2, costh2,
			  xpoints, ypoints, 2);
		    map3d(xoff+sinth2*cosph1, sinth2*sinph1, costh2,
			  xpoints, ypoints, 3);
		    g.fillPolygon(xpoints, ypoints, 4);
		}
		cosph1 = cosph2; sinph1 = sinph2;
	    }
	}
    }

    void drawCylinder(Graphics g, double r, double xoff, boolean back) {
	int i;
	int ct = 10;
	for (i = 0; i != ct; i++) {
	    double th1 = pi*2*i/ct;
	    double th2 = pi*2*(i+1)/ct;
	    double sinth1 = r*java.lang.Math.sin(th1);
	    double costh1 = r*java.lang.Math.cos(th1);
	    double sinth2 = r*java.lang.Math.sin(th2);
	    double costh2 = r*java.lang.Math.cos(th2);
	    if (backFacing(costh1, sinth1, 0, costh1, sinth1, 0) == back) {
		map3d(xoff+costh1, sinth1, -1, xpoints, ypoints, 0);
		map3d(xoff+costh2, sinth2, -1, xpoints, ypoints, 1);
		map3d(xoff+costh2, sinth2, +1, xpoints, ypoints, 2);
		map3d(xoff+costh1, sinth1, +1, xpoints, ypoints, 3);
		g.drawPolygon(xpoints, ypoints, 4);
	    }
	}
    }

    void setFaceColor(Graphics g, double d) {
	int dd = 32+(int) (d*40);
	if (dd > 255)
	    dd = 255;
	g.setColor(new Color(dd, dd, 0));
    }

    void fillCylinder(Graphics g, double r, double xoff) {
	int i;
	int ct = 30;
	int sidepoints[][];
	sidepoints = new int[4][ct];
	for (i = 0; i != ct; i++) {
	    double th1 = pi*2*i/ct;
	    double th2 = pi*2*(i+1)/ct;
	    double sinth1 = r*java.lang.Math.sin(th1);
	    double costh1 = r*java.lang.Math.cos(th1);
	    double sinth2 = r*java.lang.Math.sin(th2);
	    double costh2 = r*java.lang.Math.cos(th2);
	    double x = cameraPos[0]-(xoff+costh1);
	    double y = cameraPos[1]-sinth1;
	    double d = x*costh1+y*sinth1;
	    if (d > 0)
		setFaceColor(g, d/r);
	    map3d(xoff+costh1, sinth1, -1, xpoints, ypoints, 0);
	    map3d(xoff+costh2, sinth2, -1, xpoints, ypoints, 1);
	    map3d(xoff+costh2, sinth2, +1, xpoints, ypoints, 2);
	    map3d(xoff+costh1, sinth1, +1, xpoints, ypoints, 3);
	    sidepoints[0][i] = xpoints[0];
	    sidepoints[1][i] = ypoints[0];
	    sidepoints[2][i] = xpoints[3];
	    sidepoints[3][i] = ypoints[3];
	    if (d > 0)
		g.fillPolygon(xpoints, ypoints, 4);
	}
	if (!backFacing(0, 0, 1, 0, 0, 1)) {
	    setFaceColor(g, cameraPos[2]);
	    g.fillPolygon(sidepoints[2], sidepoints[3], ct);
	} else if (!backFacing(0, 0, -1, 0, 0, -1)) {
	    setFaceColor(g, -cameraPos[2]);
	    g.fillPolygon(sidepoints[0], sidepoints[1], ct);
	}
    }

    void drawAxes(Graphics g) {
	g.setColor(Color.white);
	map3d(0, 0, 0, xpoints, ypoints, 0, viewAxes);
	map3d(1, 0, 0, xpoints, ypoints, 1, viewAxes);
	drawArrow(g, "x", xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	map3d(0, 1, 0, xpoints, ypoints, 1, viewAxes);
	drawArrow(g, "y", xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	map3d(0, 0, 1, xpoints, ypoints, 1, viewAxes);
	drawArrow(g, "z", xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
    }

    void drawViewPaper(Graphics g) {
	int i, j;
	int ct = vecDensityBar.getValue();
	ct = 24+(ct*56/64);
	double z = sliceval;
	double pos[] = new double[3];
	double field[] = new double[3];
	int slice = sliceChooser.getSelectedIndex()-SLICE_X;
	if (slice < 0)
	    slice = 0;
	int coord1 = (slice == 0) ? 1 : 0;
	int coord2 = (slice == 2) ? 1 : 2;
	for (i = 0; i != ct; i++) {
	    double x1 = i*2./ct - 1;
	    double x2 = (i+1.)*2/ct - 1;
	    for (j = 0; j != ct; j++) {
		double y1 = j*2./ct - 1;
		double y2 = (j+1.)*2/ct - 1;
		pos[coord1] = x1;
		pos[coord2] = y1;
		pos[slice] = z;
		curfunc.getField(field, pos);
		// paper is dark when field is perpendicular, light when
		// it is parallel
		double prp = field[slice] < 0 ? -field[slice] : field[slice];
		double par = java.lang.Math.sqrt(field[coord1]*field[coord1]+
						 field[coord2]*field[coord2]);
		int dd = (int) ((par/2-prp)*strengthBar.getValue()*20000.+128);
		if (dd < 0)
		    dd = 0;
		if (dd > 255)
		    dd = 255;
		g.setColor(new Color(0, dd, 0));
		map3d(pos[0], pos[1], pos[2], xpoints, ypoints, 0);
		pos[coord1] = x2;
		map3d(pos[0], pos[1], pos[2], xpoints, ypoints, 1);
		pos[coord2] = y2;
		map3d(pos[0], pos[1], pos[2], xpoints, ypoints, 2);
		pos[coord1] = x1;
		map3d(pos[0], pos[1], pos[2], xpoints, ypoints, 3);
		g.fillPolygon(xpoints, ypoints, 4);
	    }
	}
    }

    void drawVectors(Graphics g) {
	int x, y, z;
	DrawData dd = new DrawData();
	dd.mult = strengthBar.getValue() * 80.;
	dd.g = g;
	dd.field = new double[3];
	dd.vv = new double[3];
	vectorSpacing = vecDensityBar.getValue();
	int slice = sliceChooser.getSelectedIndex();
	boolean sliced = (slice > 0);
	double vec[] = new double[3];

	if (vectors == null && sliced)
	    vectors = new FieldVector[vectorSpacing*vectorSpacing];
	vecCount = 0;

	if (!sliced) {
	    vectorSpacing /= 2;
	    if (vectors == null)
		vectors =
		    new FieldVector[vectorSpacing*vectorSpacing*vectorSpacing];
	    for (x = 0; x != vectorSpacing; x++) {
		vec[0] = x*(2.0/(vectorSpacing-1))-1;
		for (y = 0; y != vectorSpacing; y++) {
		    vec[1] = y*(2.0/(vectorSpacing-1))-1;
		    for (z = 0; z != vectorSpacing; z++) {
			vec[2] = z*(2.0/(vectorSpacing-1))-1;
			drawVector(dd, vec);
		    }
		}
	    }
	} else {
	    int coord1 = (slice == SLICE_X) ? 1 : 0;
	    int coord2 = (slice == SLICE_Z) ? 1 : 2;
	    int slicecoord = slice-SLICE_X;
	    vec[slicecoord] = sliceval;
	    for (x = 0; x != vectorSpacing; x++) {
		vec[coord1] = x*(2.0/(vectorSpacing-1))-1;
		for (y = 0; y != vectorSpacing; y++) {
		    vec[coord2] = y*(2.0/(vectorSpacing-1))-1;
		    drawVector(dd, vec);
		}
	    }
	}
	curfunc.render(g);
    }

    void genLines() {
	if (vectors != null)
	    return;
	partMult = fieldStrength = 10;
	int i;
	vecCount = 0;
	
	int lineGridSize = lineDensityBar.getValue();
	if (lineGridSize < 3)
	    lineGridSize = 3;
	if (lineGridSize > 16)
	    lineGridSize = 16;
	int slice = sliceChooser.getSelectedIndex();
	boolean sliced = (slice > 0);
	if (sliced)
	    lineGridSize *= 2;
	int ct = (sliced) ? 30*lineGridSize*lineGridSize :
	    30*lineGridSize*lineGridSize*lineGridSize;
	vectors = new FieldVector[ct];
	double brightmult = 160*strengthBar.getValue();

	boolean lineGrid[][][] =
	    new boolean[lineGridSize][lineGridSize][lineGridSize];
	double lineGridMult = lineGridSize/2.;
	if (sliced) {
	    int j, k;
	    int gp = (int) ((sliceval+1)*lineGridMult);
	    for (i = 0; i != lineGridSize; i++)
		for (j = 0; j != lineGridSize; j++)
		    for (k = 0; k != lineGridSize; k++) {
			switch (slice) {
			case SLICE_X: lineGrid[i][j][k] = i!=gp; break;
			case SLICE_Y: lineGrid[i][j][k] = j!=gp; break;
			case SLICE_Z: lineGrid[i][j][k] = k!=gp; break;
			}
		    }
	}

	double origp[] = new double[3];
	double field[] = new double[3];
	Particle p = new Particle();
	p.lifetime = -1;
	p.stepsize = 10;
	int dir = -1;
	int segs = 0;
	double lastdist = 0;
	for (i = 0; i != ct; i++) {
	    if (p.lifetime < 0) {
		p.lifetime = 1;
		p.stepsize = 10;
		segs = 0;
		lastdist = 0;
		if (dir == 1) {
		    int j;
		    for (j = 0; j != 3; j++)
			p.pos[j] = origp[j];
		    dir = -1;
		    continue;
		}
		dir = 1;
		int px = 0, py = 0, pz = 0;
		while (true) {
		    if (!lineGrid[px][py][pz])
			break;
		    if (++px < lineGridSize)
			continue;
		    px = 0;
		    if (++py < lineGridSize)
			continue;
		    py = 0;
		    if (++pz < lineGridSize)
			continue;
		    break;
		}
		if (pz == lineGridSize)
		    break;
		lineGrid[px][py][pz] = true;
		double offs = .5/lineGridMult;
		origp[0] = p.pos[0] = px/lineGridMult-1+offs;
		origp[1] = p.pos[1] = py/lineGridMult-1+offs;
		origp[2] = p.pos[2] = pz/lineGridMult-1+offs;
		if (sliced)
		    origp[slice-SLICE_X] = p.pos[slice-SLICE_X] = sliceval;
	    }

	    FieldVector fv = vectors[vecCount];
	    if (fv == null) {
		fv = vectors[vecCount] = new FieldVector();
		fv.p1 = new double[3];
		fv.p2 = new double[3];
	    }
	    vecCount++;
	    fv.p1[0] = p.pos[0]; fv.p1[1] = p.pos[1]; fv.p1[2] = p.pos[2];
	    double x[] = p.pos;
	    lineSegment(p, dir);
	    //System.out.print(x[0] + " " + x[1] + " " + x[2] + "\n");
	    if (p.lifetime < 0) {
		vecCount--;
		continue;
	    }
	    int gx = (int) ((x[0]+1)*lineGridMult);
	    int gy = (int) ((x[1]+1)*lineGridMult);
	    int gz = (int) ((x[2]+1)*lineGridMult);
	    if (!lineGrid[gx][gy][gz])
		segs--;
	    lineGrid[gx][gy][gz] = true;
	    fv.p2[0] = p.pos[0]; fv.p2[1] = p.pos[1]; fv.p2[2] = p.pos[2];
	    
	    double dn = brightmult*p.phi;
	    if (dn > 2)
		dn = 2;
	    fv.col = (int) (dn*255);

	    double d2 = dist2(origp, x);
	    if (d2 > lastdist)
		lastdist = d2;
	    else
		segs++;
	    if (segs > 10 || d2 < .001)
		p.lifetime = -1;
	}
	//System.out.print("vc " + vecCount + " " + ct + "\n");
    }

    void drawLines(Graphics g) {
	int i;
	for (i = 0; i != vecCount; i++) {
	    FieldVector fv = vectors[i];
	    double x[] = fv.p1;
	    map3d(x[0], x[1], x[2], xpoints, ypoints, 0);
	    int vp1 = curfunc.getViewPri(cameraPos, x);
	    x = fv.p2;
	    map3d(x[0], x[1], x[2], xpoints, ypoints, 1);

	    fv.sx1 = xpoints[0];
	    fv.sy1 = ypoints[0];
	    fv.sx2 = xpoints[1];
	    fv.sy2 = ypoints[1];
	    int vp2 = curfunc.getViewPri(cameraPos, x);
	    fv.viewPri = (vp1 > vp2) ? vp1 : vp2;
	}
	curfunc.render(g);
    }

#ifndef BUILD_M
    double potfield[];
    class EquipPoint {
	double pos[];
	double pot;
	EquipPoint() { pos = new double[3]; }
	EquipPoint(EquipPoint a, EquipPoint b) {
	    pos = new double[3];
	    int i;
	    for (i = 0; i != 3; i++)
		pos[i] = .5*(a.pos[i] + b.pos[i]);
	    curfunc.getField(potfield, pos);
	    pot = reverse*potfield[0];
	}
	void set(int cx, int cy, int cz, double x, double y, double z) {
	    pos[cx] = x; pos[cy] = y; pos[cz] = z;
	}
	boolean valid() {
	    return !(Double.isNaN(pot) || Double.isInfinite(pot));
	}
	boolean inRange() {
	    return (pot >= -2 && pot <= 2);
	}
	void setPot(double p) { pot = p; }
    }

    boolean canSubdivide(EquipPoint a, EquipPoint b) {
	return dist2(a.pos, b.pos) > .04*.04;
    }

    static final int maxVectors = 10000;
    void genEquips() {
	if (vectors != null)
	    return;
	partMult = fieldStrength = 10;
	vecCount = 0;
	int slice = sliceChooser.getSelectedIndex();
	vectors = new FieldVector[maxVectors];
	potfield = new double[3];

	EquipPoint eps[] = new EquipPoint[4];
	int i;
	for (i = 0; i != 4; i++)
	    eps[i] = new EquipPoint();
	if (slice == SLICE_NONE) {
	    int steps = 3;
	    for (i = -steps; i <= steps; i++)
		genEquipPlane(eps, i/(double) steps, SLICE_X);
	    for (i = -steps; i <= steps; i++)
		genEquipPlane(eps, i/(double) steps, SLICE_Y);
	    for (i = -steps; i <= steps; i++)
		genEquipPlane(eps, i/(double) steps, SLICE_Z);
	} else
	    genEquipPlane(eps, sliceval, slice);
	//System.out.print("vc " + vecCount + "\n");
    }

    void genEquipPlane(EquipPoint eps[], double z, int slice) {
	int i, j;
	int coord1 = (slice == SLICE_X) ? 1 : 0;
	int coord2 = (slice == SLICE_Z) ? 1 : 2;
	slice -= SLICE_X;
	int grid = (sliceChooser.getSelectedIndex() == SLICE_NONE) ? 12 : 24;
	double gridmult = 2./grid;
	double pots[][] = new double[grid+1][grid+1];
	for (i = 0; i <= grid; i++)
	    for (j = 0; j <= grid; j++) {
		double x1 = i*gridmult-1;
		double y1 = j*gridmult-1;
		eps[0].set(coord1, coord2, slice, x1, y1, z);
		curfunc.getField(potfield, eps[0].pos);
		pots[i][j] = reverse*potfield[0];
	    }
	for (i = 0; i != grid; i++)
	    for (j = 0; j != grid; j++) {
		double x1 = i*gridmult-1;
		double y1 = j*gridmult-1;
		double x2 = (i+1)*gridmult-1;
		double y2 = (j+1)*gridmult-1;
		eps[0].set(coord1, coord2, slice, x1, y1, z);
		eps[1].set(coord1, coord2, slice, x2, y1, z);
		eps[2].set(coord1, coord2, slice, x1, y2, z);
		eps[3].set(coord1, coord2, slice, x2, y2, z);
		eps[0].setPot(pots[i  ][j  ]);
		eps[1].setPot(pots[i+1][j  ]);
		eps[2].setPot(pots[i  ][j+1]);
		eps[3].setPot(pots[i+1][j+1]);
		tryEdges(eps[0], eps[1], eps[2], eps[3]);
	    }
    }

    double max(double a, double b) { return a > b ? a : b; }
    double min(double a, double b) { return a < b ? a : b; }

    boolean shouldSubdivide(EquipPoint ep1, EquipPoint ep2, EquipPoint ep3,
			    EquipPoint ep4) {
	if (!ep1.inRange())
	    return true;
	if (!ep2.inRange())
	    return true;
	if (!ep3.inRange())
	    return true;
	if (!ep4.inRange())
	    return true;
	double pmin = min(min(ep1.pot, ep2.pot), min(ep3.pot, ep4.pot));
	double pmax = max(max(ep1.pot, ep2.pot), max(ep3.pot, ep4.pot));
	return (pmax-pmin) > .3;
    }

    void tryEdges(EquipPoint ep1, EquipPoint ep2, EquipPoint ep3,
		  EquipPoint ep4) {
	if (shouldSubdivide(ep1, ep2, ep3, ep4) && canSubdivide(ep1, ep2)) {
	    EquipPoint ep12 = new EquipPoint(ep1,  ep2);
	    EquipPoint ep13 = new EquipPoint(ep1,  ep3);
	    EquipPoint ep24 = new EquipPoint(ep2,  ep4);
	    EquipPoint ep34 = new EquipPoint(ep3,  ep4);
	    EquipPoint epc  = new EquipPoint(ep12, ep34);
	    tryEdges(ep1, ep12, ep13, epc);
	    tryEdges(ep12, ep2, epc, ep24);
	    tryEdges(ep13, epc, ep3, ep34);
	    tryEdges(epc, ep24, ep34, ep4);
	    return;
	}
	tryEdge(ep1, ep2, ep3, ep4);
	tryEdge(ep1, ep2, ep1, ep3);
	tryEdge(ep1, ep2, ep2, ep4);
	tryEdge(ep1, ep3, ep2, ep4);
	tryEdge(ep1, ep3, ep3, ep4);
	tryEdge(ep2, ep4, ep3, ep4);
    }

    boolean spanning(EquipPoint ep1, EquipPoint ep2, double pval) {
	if (ep1.pot == ep2.pot)
	    return false;
	if (!(ep1.valid() && ep2.valid()))
	    return false;
	return !((ep1.pot < pval && ep2.pot < pval) ||
		 (ep1.pot > pval && ep2.pot > pval));
    }

    void interpPoint(EquipPoint ep1, EquipPoint ep2, double pval,
		     double pos[]) {
	double interp2 = (pval-ep1.pot)/(ep2.pot-ep1.pot);
	double interp1 = 1-interp2;
	//System.out.print("I " + interp1 + " " + interp2 + "\n");
	//System.out.print(ep1.pot + " " + ep2.pot + " " + Double.isNaN(ep1.pot) + "\n");
	int i;
	for (i = 0; i != 3; i++)
	    pos[i] = ep1.pos[i]*interp1+ep2.pos[i]*interp2;
	//System.out.print("> " + pos[0] + " " + pos[1] + " " + pos[2] + "\n");
    }

    void tryEdge(EquipPoint ep1, EquipPoint ep2,
		 EquipPoint ep3, EquipPoint ep4) {
	int i;
	if (sliceChooser.getSelectedIndex() == SLICE_NONE) {
	    tryEdge(ep1, ep2, ep3, ep4, (potentialBar.getValue()-500)/500.);
	} else {
	    for (i = -20; i <= 20; i++)
		tryEdge(ep1, ep2, ep3, ep4, i/20.);
	}
    }

    void tryEdge(EquipPoint ep1, EquipPoint ep2,
		 EquipPoint ep3, EquipPoint ep4, double pval) {
	if (!(spanning(ep1, ep2, pval) && spanning(ep3, ep4, pval)))
	    return;

	if (vecCount == maxVectors)
	    return;
	FieldVector fv = vectors[vecCount];
	if (fv == null) {
	    fv = vectors[vecCount] = new FieldVector();
	    fv.p1 = new double[3];
	    fv.p2 = new double[3];
	}
	vecCount++;
	interpPoint(ep1, ep2, pval, fv.p1);
	interpPoint(ep3, ep4, pval, fv.p2);
	fv.col = 255+((int) (255*pval));
    }
#endif /* BUILD_M */

    // draw the appropriate field vector at xx,yy,zz
    void drawVector(DrawData dd, double vec[]) {
	double field[] = dd.field;

	// calculate field vector
	curfunc.getField(field, vec);
	double dn = java.lang.Math.sqrt(field[0]*field[0]+field[1]*field[1]+
					field[2]*field[2]);
	double dnr = dn*reverse;
	if (dn > 0) {
	    field[0] /= dnr;
	    field[1] /= dnr;
	    field[2] /= dnr;
	}
	dn *= dd.mult;
	if (dn > 2)
	    dn = 2;
	int col = (int) (dn*255);
	double sw2 = 1./(vectorSpacing-1);
	map3d(vec[0], vec[1], vec[2], xpoints, ypoints, 0);
	double vv[] = dd.vv;
	vv[0] = vec[0] + sw2*field[0];
	vv[1] = vec[1] + sw2*field[1];
	vv[2] = vec[2] + sw2*field[2];
	map3d(vv[0], vv[1], vv[2], xpoints, ypoints, 1);
	FieldVector fv = vectors[vecCount];
	if (fv == null)
	    fv = vectors[vecCount] = new FieldVector();
	fv.sx1 = xpoints[0];
	fv.sy1 = ypoints[0];
	fv.sx2 = xpoints[1];
	fv.sy2 = ypoints[1];
	fv.col = col;
	vecCount++;
	int vp1 = curfunc.getViewPri(cameraPos, vec);
	if (!curfunc.noSplitFieldVectors())
	    fv.viewPri = vp1;
	else {
	    int vp2 = curfunc.getViewPri(cameraPos, vv);
	    fv.viewPri = (vp1 == vp2) ? vp1 : -1;
	}
    }

    void drawParticles(Graphics g) {
	int i;
	int pcount = getParticleCount();
	for (i = 0; i < pcount; i++) {
	    Particle pt = particles[i];
	    pt.viewPri = curfunc.getViewPri(cameraPos, pt.pos);
	}
	curfunc.render(g);
    }

    void moveParticles() {
	fieldStrength = strengthBar.getValue();
	int bestd = 0;
	int i;
	int pcount = getParticleCount();
	int slice = sliceChooser.getSelectedIndex();
	boolean sliced = (slice > 0);
	partMult = fieldStrength * reverse * timeStep;
	for (i = 0; i != pcount; i++) {
	    Particle pt = particles[i];
	    removeFromDensityGroup(pt);
	    moveParticle(pt);
	    double x[] = pt.pos;
	    if (!(x[0] >= -1 && x[0] < 1 &&
		  x[1] >= -1 && x[1] < 1 &&
		  x[2] >= -1 && x[2] < 1) ||
		(pt.lifetime -= timeStep) < 0) {
		positionParticle(pt);
	    }
	    if (sliced)
		x[slice-SLICE_X] = sliceval;
	    int d = addToDensityGroup(pt);
	    if (d > bestd)
		bestd = d;
	}
	boolean withforce =
	    (dispChooser.getSelectedIndex() == DISP_PART_FORCE);
	int maxd =  (10*getParticleCount()/(densitygridsize*densitygridsize*
					    densitygridsize));
	if (sliced)
	    maxd = 4*getParticleCount()/(densitygridsize*densitygridsize);
	if (!withforce && curfunc.redistribute() && bestd > maxd)
	    redistribute(bestd);
    }

    static int frames = 0;
    static int framerate = 0;
    static long firsttime = 0;

    // draw the cube containing the particles.  if drawAll is false then
    // we just draw faces that are facing the camera.  This routine draws
    // each edge twice which is unnecessary, but easier.
    void drawCube(Graphics g, boolean drawAll) {
	int i;
	int slice = sliceChooser.getSelectedIndex();
	int sp = (drawAll) ? 0 : 8;
	for (i = 0; i != 6; i++) {
	    // calculate normal of ith face
	    int nx = (i == 0) ? -1 : (i == 1) ? 1 : 0;
	    int ny = (i == 2) ? -1 : (i == 3) ? 1 : 0;
	    int nz = (i == 4) ? -1 : (i == 5) ? 1 : 0;
	    // if face is not facing camera, don't draw it
	    if (!drawAll && backFacing(nx, ny, nz, nx, ny, nz))
		continue;
	    double pts[];
	    pts = new double[3];
	    int n;
	    for (n = 0; n != 4; n++) {
		computeFace(i, n, pts);
		map3d(pts[0], pts[1], pts[2], xpoints, ypoints, n);
	    }
	    g.setColor(Color.gray);
	    g.drawPolygon(xpoints, ypoints, 4);
	    if (slice != SLICE_NONE && i/2 != slice-SLICE_X) {
		if (selectedSlice)
		    g.setColor(Color.yellow);
		int coord1 = (slice == SLICE_X) ? 1 : 0;
		int coord2 = (slice == SLICE_Z) ? 1 : 2;
		computeFace(i, 0, pts);
		pts[slice-SLICE_X] = sliceval;
		map3d(pts[0], pts[1], pts[2],
		      slicerPoints[0], slicerPoints[1], sp);
		computeFace(i, 2, pts);
		pts[slice-SLICE_X] = sliceval;
		map3d(pts[0], pts[1], pts[2],
		      slicerPoints[0], slicerPoints[1], sp+1);
		g.drawLine(slicerPoints[0][sp  ], slicerPoints[1][sp],
			   slicerPoints[0][sp+1], slicerPoints[1][sp+1]);
		if (drawAll) {
		    // we don't keep track of the sliceFaces and slicerPoints
		    // unless drawAll is true, because if it's false then
		    // we're not drawing all the faces of the cube and the
		    // numbering will get screwed up.  So if drawAll is
		    // false we just point sp to scratch space and don't
		    // bother storing the sliceFaces.
		    sliceFaces[sp/2][0] = nx;
		    sliceFaces[sp/2][1] = ny;
		    sliceFaces[sp/2][2] = nz;
		    sp += 2;
		}
	    }
	}
    }

    // generate the nth vertex of the bth cube face
    void computeFace(int b, int n, double pts[]) {
	// One of the 3 coordinates (determined by a) is constant.
	// When b=0, x=-1; b=1, x=+1; b=2, y=-1; b=3, y=+1; etc
	int a = b >> 1;
	pts[a] = ((b & 1) == 0) ? -1 : 1;

	// fill in the other 2 coordinates with one of the following
	// (depending on n): -1,-1; +1,-1; +1,+1; -1,+1
	int i;
	for (i = 0; i != 3; i++) {
	    if (i == a) continue;
	    pts[i] = (((n>>1)^(n&1)) == 0) ? -1 : 1;
	    n >>= 1;
	}
    }

    void renderItems(Graphics g, int pri) {
	g.setColor(Color.white);
	int disp = dispChooser.getSelectedIndex();
	if (disp == DISP_VECTORS || disp == DISP_VECTORS_A) {
	    int i;
	    for (i = 0; i != vecCount; i++) {
		FieldVector fv = vectors[i];
		if (fv.viewPri != pri)
		    continue;
		g.setColor(fieldColors[fv.col]);
		drawArrow(g, null, fv.sx1, fv.sy1, fv.sx2, fv.sy2, 2);
	    }
	    return;
	}
	if (disp == DISP_LINES || disp == DISP_EQUIPS) {
	    int i;
	    g.setColor(Color.white);
	    Color colvec[] = (disp == DISP_EQUIPS) ? equipColors : fieldColors;
	    for (i = 0; i != vecCount; i++) {
		FieldVector fv = vectors[i];
		if (fv.viewPri != pri)
		    continue;
		if (fv.sx1 == fv.sx2 && fv.sy1 == fv.sy2)
		    continue;
		g.setColor(colvec[fv.col]);
		g.drawLine(fv.sx1, fv.sy1, fv.sx2, fv.sy2);
	    }
	    return;
	}
	int pcount = getParticleCount();
	int i;
	wooft += .3;
	for (i = 0; i < pcount; i++) {
	    Particle p = particles[i];
	    if (p.viewPri != pri)
		continue;
	    double pos[] = p.pos;
	    map3d(pos[0], pos[1], pos[2], xpoints, ypoints, 0);
	    if (xpoints[0] < 0 || xpoints[0] >= winSize.width ||
		ypoints[0] < 0 || ypoints[0] >= winSize.height)
		continue;
	    if (disp == DISP_PART_MAG) {
		double cosph = java.lang.Math.cos(p.phi);
		double sinph = java.lang.Math.sin(p.phi);
		double costh = java.lang.Math.cos(p.theta);
		double sinth = java.lang.Math.sin(p.theta);
		double al = .08;
		double rhatx = sinth*cosph*al;
		double rhaty = sinth*sinph*al;
		double rhatz = costh*al;
		map3d(pos[0]+rhatx, pos[1]+rhaty, pos[2]+rhatz,
		      xpoints, ypoints, 1);
		drawArrow(g, null, xpoints[0], ypoints[0],
			  xpoints[1], ypoints[1], 2);
	    } else
		g.fillRect(xpoints[0], ypoints[0]-1, 2, 2);
	}
    }

	double wooft = 0;

    void drawPlane(Graphics g, double sizex, double sizey, double z) {
	g.setColor(darkYellow);
	map3d(-sizex, -sizey, z, xpoints, ypoints, 0);
	map3d(+sizex, -sizey, z, xpoints, ypoints, 1);
	map3d(+sizex, +sizey, z, xpoints, ypoints, 2);
	map3d(-sizex, +sizey, z, xpoints, ypoints, 3);
	g.fillPolygon(xpoints, ypoints, 4);
    }

    void drawArrow(Graphics g, String text, int x1, int y1, int x2, int y2) {
	drawArrow(g, text, x1, y1, x2, y2, 5);
    }

    void drawArrow(Graphics g, String text,
		   int x1, int y1, int x2, int y2, int as) {
	g.drawLine(x1, y1, x2, y2);
	double l = java.lang.Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
	if (l > as/2) {  // was as
	    double hatx = (x2-x1)/l;
	    double haty = (y2-y1)/l;
	    g.drawLine(x2, y2,
		       (int) (haty*as-hatx*as+x2),
		       (int) (-hatx*as-haty*as+y2));
	    g.drawLine(x2, y2,
		       (int) (-haty*as-hatx*as+x2),
		       (int) (hatx*as-haty*as+y2));
	    if (text != null)
		g.drawString(text, (int) (x2+hatx*10), (int) (y2+haty*10));
	}
    }

    boolean backFacing(double px, double py, double pz,
		       double nx, double ny, double nz) {
	double x = cameraPos[0]-px;
	double y = cameraPos[1]-py;
	double z = cameraPos[2]-pz;
	double d = x*nx+y*ny+z*nz;
	return d <= 0;
    }

    int intersectSphere(double cp[], double ptx, double pty, double ptz,
			double r) {
	return intersectSphere(cp, ptx, pty, ptz, 0, 0, 0, r);
    }

    // calculate intersection of a ray starting at the camera and going
    // through point (ptx,pty,ptz) with a sphere of radius 2 at the origin.
    // Returns 0 if there is no intersection before the ray hits the point,
    // 1 if the point is inside the sphere, and 2 if the point is behind
    // the sphere.
    int intersectSphere(double cp[], double ptx, double pty, double ptz,
			double sx, double sy, double sz, double r) {
	double vx = ptx-cp[0];
	double vy = pty-cp[1];
	double vz = ptz-cp[2];
	double qpx = cp[0]-sx;
	double qpy = cp[1]-sy;
	double qpz = cp[2]-sz;
	double a = vx*vx+vy*vy+vz*vz;
	double b = 2*(vx*qpx + vy*qpy + vz*qpz);
	double c = qpx*qpx + qpy*qpy + qpz*qpz - r*r;
	double discrim = b*b-4*a*c;
	if (discrim < 0)
	    return 0;
	discrim = java.lang.Math.sqrt(discrim);
	double b1 = (-b-discrim)/(2*a);
	double b2 = (-b+discrim)/(2*a);
	if (b1 < 1 && inViewBox(b1, cp, vx, vy, vz))
	    return (b2 < 1) ? 2 : 1;
	else
	    return 0;
    }

    // calculate intersection of a ray starting at the camera and going
    // through point (ptx,pty,ptz) with the plane z=a.  Returns 0 if there
    // is no intersection before the ray hits the point; otherwise 2.
    double intersectZPlane(double cp[], double a,
			   double ptx, double pty, double ptz) {
	double vx = ptx-cp[0];
	double vy = pty-cp[1];
	double vz = ptz-cp[2];
	double t = intersectionDistance = -(cp[2]+a)/vz;
	if (t > 1)
	    return 0;
	if (!inViewBox(t, cp, vx, vy, vz))
	    return 0;
	return 2;
    }

    // make sure the intersection is inside the viewing box
    boolean inViewBox(double t, double cp[], double vx, double vy, double vz) {
	if (t < 0)
	    return false;
	double ix = intersection[0] = cp[0]+vx*t;
	double iy = intersection[1] = cp[1]+vy*t;
	double iz = intersection[2] = cp[2]+vz*t;
	//System.out.print("ivb " + t + " " + ix + " " + iy + " " + iz + "\n");
	if (ix < -1 || ix > 1 || iy < -1 || iy > 1 ||
	    iz < -1 || iz > 1)
	    return false;
	return true;
    }

    int intersectCylinder(double cp[], double ptx, double pty, double ptz,
			  double r, boolean vbTest) {
	return intersectCylinder(cp, ptx, pty, ptz, 0, 0, r, vbTest);
    }

    // calculate intersection of a ray starting at the camera and
    // going through point (ptx,pty,ptz) with a cylinder of radius r
    // centered at the z axis.  Returns 0 if there is no intersection
    // before the ray hits the point, 1 if the point is inside the
    // cylinder, and 2 if the point is behind the cylinder.
    int intersectCylinder(double cp[], double ptx, double pty, double ptz,
			  double sx, double sy, double r, boolean vbTest) {
	double vx = ptx-cp[0];
	double vy = pty-cp[1];
	double qpx = cp[0]-sx;
	double qpy = cp[1]-sy;
	double a = vx*vx+vy*vy;
	double b = 2*(vx*qpx + vy*qpy);
	double c = qpx*qpx + qpy*qpy - r*r;
	double discrim = b*b-4*a*c;
	if (discrim < 0)
	    return 0;
	discrim = java.lang.Math.sqrt(discrim);
	double b1 = (-b-discrim)/(2*a);
	double b2 = (-b+discrim)/(2*a);
	//System.out.print(b1 + " " +b2 + "\n");

	// test if first intersection is behind point
	if (b1 > 1)
	    return 0;

	// if vbTest is true, check if intersection is inside box
	if (!vbTest || inViewBox(b1, cp, vx, vy, ptz-cp[2]))
	    // if the second intersection is between camera and point,
	    // the point is behind the cylinder.  Otherwise it's inside.
	    return (b2 < 1) ? 2 : 1;

	// If the second intersection is between camera and point,
	// the point is inside the cylinder.
	if (b2 > 1)
	    return 2;

	// If the second intersection is in the view box, the point
	// is behind the cylinder.
	if (inViewBox(b2, cp, vx, vy, ptz-cp[2]))
	    return 2;

	// otherwise there is no valid intersection.
	return 0;
    }

    int rediscount;
    void redistribute(int mostd) {
	if (mostd < 5)
	    return;
	rediscount++;
	int maxd = (10*getParticleCount()/
		    (densitygridsize*densitygridsize*densitygridsize));
	int i;
	int pn = 0;
	int pcount = getParticleCount();
	for (i = rediscount % 4; i < pcount; i+=4) {
	    Particle p = particles[i];
	    int a = (int)((p.pos[0]+1)*(densitygridsize/2));
	    int b = (int)((p.pos[1]+1)*(densitygridsize/2));
	    int c = (int)((p.pos[2]+1)*(densitygridsize/2));
	    if (density[a][b][c] <= maxd)
		continue;
	    p.lifetime = -1;
	    pn++;
	}
	//System.out.print("redist " + mostd + " " + pn + "\n");
    }

    double distance(Particle p) {
	return distance(p.pos[0], p.pos[1], p.pos[2]);
    }

    double distance(double y[]) {
	return distance(y[0], y[1], y[2]);
    }

    double distance(double x, double y, double z) {
	return java.lang.Math.sqrt(x*x+y*y+z*z+.000000001);
    }

    double distance(double x, double y) {
	return java.lang.Math.sqrt(x*x+y*y+.000000001);
    }

    void rotateParticleAdd(double result[], double y[], double mult,
			double cx, double cy) { 
	result[0] += -mult*(y[1]-cy);
	result[1] +=  mult*(y[0]-cx);
	result[2] += 0;
    }

    void rotateParticle(double result[], double y[], double mult) {
	result[0] = -mult*y[1];
	result[1] =  mult*y[0];
	result[2] = 0;
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) {
	cv.repaint(pause);
    }

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(pause);
    }
    public void actionPerformed(ActionEvent e) {
	vectors = null;
	if (e.getSource() == resetButton)
	    resetParticles();
	if (e.getSource() == kickButton)
	    kickParticles();
	if (e.getSource() == infoButton) {
	    String s = curfunc.getClass().getName();
	    try {
		s = s.substring(s.lastIndexOf('.')+1);
		applet.getAppletContext().showDocument(
			new URL(applet.getCodeBase(),
				"functions.html" + '#' + s),
			"functionHelp");
	    } catch (Exception ex) {
	    }
	}
	curfunc.actionPerformed();
    }

    public boolean handleEvent(Event ev) {
	if (ev.id == Event.WINDOW_DESTROY) {
	    destroyFrame();
	    return true;
	}
	return super.handleEvent(ev);
    }
    
    void destroyFrame() {
        if (applet == null)
            dispose();
        else
            applet.destroyFrame();
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	vectors = null;
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
	if (e.getSource() == partCountBar)
	    resetDensityGroups();
	cv.repaint(pause);
    }

    public void mouseDragged(MouseEvent e) {
	dragging = true;
	oldDragX = dragX;
	oldDragY = dragY;
	dragX = e.getX(); dragY = e.getY();
	int mode = modeChooser.getSelectedIndex();
	if (selectedSlice)
	    mode = MODE_SLICE;
	if (mode == MODE_ANGLE) {
	    int xo = oldDragX-dragX;
	    int yo = oldDragY-dragY;
	    rotate(lastXRot = xo/40., lastYRot = -yo/40.);
	    double lr = Math.sqrt(lastXRot*lastXRot + lastYRot*lastYRot);
	    if (lr > .06) {
		lr /= .06;
		lastXRot /= lr;
		lastYRot /= lr;
	    }
	    cv.repaint(pause);
	} else if (mode == MODE_ZOOM) {
	    int xo = dragX-dragStartX;
	    zoom = dragZoomStart + xo/20.;
	    if (zoom < .1)
		zoom = .1;
	    cv.repaint(pause);
	} else if (mode == MODE_SLICE) {
	    double x3[] = new double[3];
	    unmap3d(x3, dragX, dragY, sliceFace, sliceFace, viewMain);
	    switch (sliceChooser.getSelectedIndex()) {
	    case SLICE_X: sliceval = x3[0]; break;
	    case SLICE_Y: sliceval = x3[1]; break;
	    case SLICE_Z: sliceval = x3[2]; break;
	    }
	    // Avoid -1 because it causes particles to drift out of the
	    // cube too easily.  Avoid +1 because of that and because it is
	    // not included in any density group.
	    if (sliceval < -.99)
		sliceval = -.99;
	    if (sliceval > .99)
		sliceval = .99;
	    resetDensityGroups();
	    //System.out.print(sliceval + "\n");
	    cv.repaint(pause);
	    vectors = null;
	}
	
    }

    boolean csInRange(int x, int xa, int xb) {
	if (xa < xb)
	    return x >= xa-5 && x <= xb+5;
	return x >= xb-5 && x <= xa+5;
    }

    void checkSlice(int x, int y) {
	if (sliceChooser.getSelectedIndex() == SLICE_NONE) {
	    selectedSlice = false;
	    return;
	}
	int n;
	selectedSlice = false;
	for (n = 0; n != 8; n += 2) {
	    int xa = slicerPoints[0][n];
	    int xb = slicerPoints[0][n+1];
	    int ya = slicerPoints[1][n];
	    int yb = slicerPoints[1][n+1];
	    if (!csInRange(x, xa, xb) || !csInRange(y, ya, yb))
		continue;

	    double d;
	    if (xa == xb)
		d = java.lang.Math.abs(x-xa);
	    else {
		// write line as y=a+bx
		double b = (yb-ya)/(double) (xb-xa);
		double a = ya-b*xa;
		
		// solve for distance
		double d1 = y-(a+b*x);
		if (d1 < 0)
		    d1 = -d1;
		d = d1/java.lang.Math.sqrt(1+b*b);
	    }
	    if (d < 6) {
		selectedSlice = true;
		sliceFace = sliceFaces[n/2];
		break;
	    }
	}
    }

    public void mouseMoved(MouseEvent e) {
	dragX = e.getX(); dragY = e.getY();
	dragStartX = dragX;
	dragStartY = dragY;
	dragZoomStart = zoom;
	boolean ss = selectedSlice;
	checkSlice(dragX, dragY);
	if (ss != selectedSlice)
	    cv.repaint(pause);
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	mouseDown = true;
    }
    public void mouseReleased(MouseEvent e) {
	mouseDown = false;
    }

    void dispChooserChanged() {
	int disp = dispChooser.getSelectedIndex();
	showA = (disp == DISP_PART_VELOC_A || disp == DISP_VECTORS_A);
	getPot = (disp == DISP_EQUIPS);
	if (disp == DISP_PART_FORCE)
	    kickButton.enable();
	else
	    kickButton.disable();
	potentialLabel.hide();
	potentialBar.hide();
	vecDensityLabel.hide();
	vecDensityBar.hide();
	lineDensityLabel.hide();
	lineDensityBar.hide();
	partCountLabel.hide();
	partCountBar.hide();
	strengthLabel.show();
	strengthBar.show();
	if (disp == DISP_VECTORS || disp == DISP_VECTORS_A ||
	    disp == DISP_VIEW_PAPER) {
	    vecDensityLabel.show();
	    vecDensityBar.show();
	} else if (disp == DISP_LINES) {
	    lineDensityLabel.show();
	    lineDensityBar.show();
	} else if (disp == DISP_EQUIPS) {
	    potentialLabel.show();
	    potentialBar.show();
	} else {
	    partCountLabel.show();
	    partCountBar.show();
	}
	vecDensityLabel.setText(disp == DISP_VIEW_PAPER ?
				"Resolution" : "Vector Density");
	if (disp == DISP_EQUIPS) {
	    strengthLabel.hide();
	    strengthBar.hide();
	}
	if ((disp == DISP_VIEW_PAPER || disp == DISP_EQUIPS) &&
	      sliceChooser.getSelectedIndex() == SLICE_NONE) {
	    sliceChooser.select(curfunc.getBestSlice());
	    potentialBar.disable();
	}
	validate();
	resetParticles();
    }

    public void itemStateChanged(ItemEvent e) {
	vectors = null;
	cv.repaint(pause);
	reverse = (reverseCheck.getState()) ? -1 : 1;
	if (e.getItemSelectable() == dispChooser) {
	    dispChooserChanged();
	    resetParticles();
	}
	if (e.getItemSelectable() == sliceChooser) {
	    resetParticles();
	    if (modeChooser.getSelectedIndex() == MODE_SLICE)
		modeChooser.select(MODE_ANGLE);
	    if (sliceChooser.getSelectedIndex() == SLICE_NONE)
		potentialBar.enable();
	    else
		potentialBar.disable();
	}
	if (e.getStateChange() != ItemEvent.SELECTED)
	    return;
	if (e.getItemSelectable() == functionChooser)
	    functionChanged();
    }

    void functionChanged() {
	reverse = 1;
	reverseCheck.setState(false);
	parseError = false;
	curfunc = (VecFunction)
	    functionList.elementAt(functionChooser.getSelectedIndex());
	int i;
	for (i = 0; i != 3; i++) {
	    auxBars[i].label.hide();
	    auxBars[i].bar.hide();
	    textFields[i].hide();
	    if (textFieldLabel != null)
		textFieldLabel.hide();
	}
	strengthBar.setValue(20);
	int x = dispChooser.getSelectedIndex();
	setupDispChooser(!curfunc.nonGradient());
	try {
	    dispChooser.select(x);
	} catch (Exception e) { }
	curfunc.setup();
	sliceChooser.select(SLICE_NONE);
	validate();
	resetParticles();
	dispChooserChanged();
    }

    void setupDispChooser(boolean potential) {
	dispChooser.removeAll();
	dispChooser.add("Display: Particles (Vel.)");
#ifdef BUILD_M
	dispChooser.add("Display: Parts (A Field, Vel.)");
	dispChooser.add("Display: Field Vectors");
	dispChooser.add("Display: Field Vectors (A)");
#else
	dispChooser.add("Display: Particles (Force)");
	dispChooser.add("Display: Field Vectors");
#endif
#ifdef BUILD_V
	dispChooser.add("Display: Streamlines");
#else
	dispChooser.add("Display: Field Lines");
#endif
#ifdef BUILD_M
	dispChooser.add("Display: Parts (Magnetic)");
	dispChooser.add("Display: Mag View Film");
#else
	if (potential) {
	    dispChooser.add("Display: Equipotentials");
	}
#endif
    }

    void setupBar(int n, String text, int val) {
	auxBars[n].label.setText(text);
	auxBars[n].label.show();
	auxBars[n].bar.setValue(val);
	auxBars[n].bar.show();
    }

    boolean useMagnetMove() {
	int disp = dispChooser.getSelectedIndex();
	return (disp == DISP_PART_MAG);
    }

#ifdef BUILD_M
    MagnetState mstates[];

    void magneticMoveParticle(Particle p) {
	int i;
	if (mstates == null) {
	    mstates = new MagnetState[3];
	    for (i = 0; i != 3; i++)
		mstates[i] = new MagnetState();
	}
	MagnetState ms     = mstates[0];
	MagnetState mshalf = mstates[1];
	MagnetState oldms  = mstates[2];
	for (i = 0; i != 3; i++) {
	    ms.pos[i] = p.pos[i];
	    ms.vel[i] = p.vel[i];
	    ms.theta  = p.theta;
	    ms.thetav = p.thetav;
	    ms.phi    = p.phi;
	    ms.phiv   = p.phiv;
	}
	mshalf.copy(ms);
	oldms.copy(ms);
	
	double h = 1;
	final double minh = .01;
	final double maxh = 1;
	final double E = .1;
	int steps = 0;
	boolean adapt = curfunc.useAdaptiveRungeKutta() &&
	    curfunc.useRungeKutta();
	boundCheck = false;

	double t = 0;
	while (t < 1) {
	    // estimate one full step
	    magnetMove(ms, h);
	    if (boundCheck) {
		p.pos[0] = -100;
		return;
	    }
	    if (curfunc.checkBounds(ms.pos, oldms.pos)) {
		p.pos[0] = -100;
		return;
	    }
	    
	    if (!adapt)
		break;

	    // estimate two half steps
	    magnetMove(mshalf, h*.5);
	    magnetMove(mshalf, h*.5);
	    
	    // estimate the local error
	    double localError =
		java.lang.Math.abs(ms.pos[0] - mshalf.pos[0]) +
		java.lang.Math.abs(ms.pos[1] - mshalf.pos[1]) +
		java.lang.Math.abs(ms.pos[2] - mshalf.pos[2]) +
		java.lang.Math.abs(ms.theta  - mshalf.theta) +
		java.lang.Math.abs(ms.phi    - mshalf.phi);
	    
	    if (localError > E && h > minh) {
		h *= 0.75; // decrease the step size
		if (h < minh)
		    h = minh;
		ms.copy(oldms);
		continue;
	    } else if (localError < (E * 0.5)) {
		h *= 1.25; // increase the step size
		if (h > maxh)
		    h = maxh;
	    }
	    
	    mshalf.copy(ms);
	    t += h;
	    steps++;
	}

	/*if (steps > 1)
	  System.out.print(steps + "\n");*/
	for (i = 0; i != 3; i++) {
	    p.pos[i]  = ms.pos[i];
	    p.vel[i]  = ms.vel[i];
	    p.theta   = ms.theta;
	    p.thetav  = ms.thetav;
	    p.phi     = ms.phi;
	    p.phiv    = ms.phiv;
	}
    }

    void magnetMove(MagnetState ms, double stepsize) {
	double cosph = java.lang.Math.cos(ms.phi);
	double sinph = java.lang.Math.sin(ms.phi);
	double costh = java.lang.Math.cos(ms.theta);
	double sinth = java.lang.Math.sin(ms.theta);
	// rhat  = (sinth*cosph, sinth*sinph, costh)
	// thhat = (costh*cosph, costh*sinph, -sinth)
	// phhat = (-sinph, cosph, 0)
	// These three vectors are always perpendicular and rhat X
	// thhat = phhat.  The particle's arrow points in the
	// direction of rhat, so the points p.pos + thhat, p.pos +
	// phhat, p.pos - tthat, p.pos - phhat are the four points we
	// sample on the current loop (if we pretend that each
	// particle has a current loop which goes around the arrow)
	double thhat[]  = new double[3];
	double phhat[]  = new double[3];
	double thhatn[] = new double[3];
	double phhatn[] = new double[3];
	double force[]  = new double[3];
	double torque[]  = new double[3];
	thhat[0] = costh*cosph; thhat[1] = costh*sinph; thhat[2] = -sinth;
	phhat[0] = -sinph;      phhat[1] = cosph;       phhat[2] = 0;
	int i;
	for (i = 0; i != 3; i++) {
	    thhatn[i] = -thhat[i];
	    phhatn[i] = -phhat[i];
	    force[i] = torque[i] = 0;
	}
	getMagForce(ms.pos, thhat,  phhat,  force, torque);
	getMagForce(ms.pos, phhat,  thhatn, force, torque);
	getMagForce(ms.pos, thhatn, phhatn, force, torque);
	getMagForce(ms.pos, phhatn, thhat,  force, torque);
	for (i = 0; i != 3; i++) {
	    ms.vel[i] += force[i]*stepsize;
	    ms.pos[i] += ms.vel[i]*stepsize;
	}
	// turning about the phhat axis causes theta to change.
	ms.thetav += dot(torque, phhat)*1000*stepsize;
	// turning about the z axis causes phi to change.
	ms.phiv   += torque[2]*1000*stepsize; // torque . zhat

	// heavily damp the oscillation of the magnets
	ms.thetav *= java.lang.Math.exp(-.2*stepsize);
	ms.phiv   *= java.lang.Math.exp(-.2*stepsize);

	ms.theta  += ms.thetav*stepsize;
	ms.phi    += ms.phiv*stepsize;
    }

    void getMagForce(double pos[], double off[], double j[], double f[],
		     double torque[]) {
	int i;
	double offs[] = new double[3];
	for (i = 0; i != 3; i++) {
	    offs[i] = off[i] * .02;
	    rk_yn[i] = pos[i] + offs[i];
	}
	curfunc.getField(rk_k1, rk_yn);
	double fmult = reverse * strengthBar.getValue();
	for (i = 0; i != 3; i++)
	    rk_k1[i] *= fmult;
	double newf[] = new double[3];
	double newtorque[] = new double[3];
	cross(newf, j, rk_k1);
	cross(newtorque, offs, newf);
	for (i = 0; i != 3; i++) {
	    f[i] += newf[i];
	    torque[i] += newtorque[i];
	}
    }

    class MagnetState {
	MagnetState() { pos = new double[3]; vel = new double[3]; }
	double pos[], vel[], theta, phi, thetav, phiv;
	void copy(MagnetState ms) {
	    int i;
	    for (i = 0; i != 3; i++) {
		pos[i]  = ms.pos[i];
		vel[i]  = ms.vel[i];
		theta   = ms.theta;
		thetav  = ms.thetav;
		phi     = ms.phi;
		phiv    = ms.phiv;
	    }
	}
    };
#endif

    void cross(double res[], double v1[], double v2[]) {
	res[0] = v1[1]*v2[2] - v1[2]*v2[1];
	res[1] = v1[2]*v2[0] - v1[0]*v2[2];
	res[2] = v1[0]*v2[1] - v1[1]*v2[0];
    }

    double dot(double v1[], double v2[]) {
	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
    }

    boolean boundCheck;
    double oldY[];
    double rk_k1[] = new double[6];
    double rk_k2[] = new double[6];
    double rk_k3[] = new double[6];
    double rk_k4[] = new double[6];
    double rk_yn[] = new double[6];

    void rk(int order, double x, double Y[], double stepsize) {
	int i;

	// x is not used...

	if (order == 3) {
	    // velocity-based motion
	    double fmult = stepsize * partMult;
	    for (i = 0; i != order; i++)
		rk_yn[i] = Y[i];
	    curfunc.getField(rk_k1, rk_yn);
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + 0.5*fmult*rk_k1[i]);
	    curfunc.getField(rk_k2, rk_yn);
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + 0.5*fmult*rk_k2[i]);
	    curfunc.getField(rk_k3, rk_yn);
	    
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + fmult*rk_k3[i]);
	    curfunc.getField(rk_k4, rk_yn);
	    for (i = 0; i != order; i++)
		Y[i] = Y[i] + fmult*(rk_k1[i]+2*(rk_k2[i]+rk_k3[i])+rk_k4[i])/6;
	} else {
	    // force-based (could share more code with above, but this is
	    // called a lot so we want it to be fast)
	    double fmult = stepsize * partMult;
	    for (i = 0; i != order; i++)
		rk_yn[i] = Y[i];
	    getForceField(rk_k1, rk_yn, stepsize, fmult);
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + 0.5*rk_k1[i]);
	    getForceField(rk_k2, rk_yn, stepsize, fmult);
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + 0.5*rk_k2[i]);
	    getForceField(rk_k3, rk_yn, stepsize, fmult);
	    
	    for (i = 0; i != order; i++)
		rk_yn[i] = (Y[i] + rk_k3[i]);
	    getForceField(rk_k4, rk_yn, stepsize, fmult);
	    for (i = 0; i != order; i++)
		Y[i] = Y[i] + (rk_k1[i]+2*(rk_k2[i]+rk_k3[i])+rk_k4[i])/6;
	}
    }

    void getForceField(double result[], double y[],
		       double stepsize, double fmult) {
	// get the field vector for the current function
	curfunc.getField(result, y);
	
	// the field data has been written into result[0:2] where it will
	// directly influence the position (y[0:2]), but we want it to
	// influence the velocity (y[3:5]), so we move it.  In the
	// process, we multiply by fmult (to get the reverse button
	// and field strength slider to work).  It would be nice if we
	// could just say getField(result+3, y), but that's java for you.
	int i;
	for (i = 0; i != 3; i++)
	    result[i+3] = .1*fmult*result[i];

	// here we fill in result[0:2] so that the particle position will
	// change according to the velocity.
	for (i = 0; i != 3; i++)
	    result[i] = stepsize*timeStep*rk_yn[i+3];
    }

    double rk_Y[] = new double[6];
    double rk_Yhalf[] = new double[6];
    double rk_oldY[] = new double[6];
    double ls_fieldavg[] = new double[3];

    void moveParticle(Particle p)
    {
	int disp = dispChooser.getSelectedIndex();
#ifdef BUILD_M
	if (disp == DISP_PART_MAG) {
	    magneticMoveParticle(p);
	    return;
	}
#endif

	int numIter=0;
	double maxh=1;
	double error=0.0, E = .001, localError;
	boolean useForce = (disp == DISP_PART_FORCE);
	int order = useForce ? 6 : 3;
	double Y[] = rk_Y;
	double Yhalf[] = rk_Yhalf;
	oldY = rk_oldY;
	int i;

	for (i = 0; i != 3; i++)
	    oldY[i] = Y[i] = Yhalf[i] = p.pos[i];
	if (useForce)
	    for (i = 0; i != 3; i++)
		Y[i+3] = Yhalf[i+3] = p.vel[i];
	double t = 0;

	if (!curfunc.useRungeKutta()) {
	    boundCheck = false;
	    curfunc.getField(Yhalf, Y);
	    if (boundCheck && (!useForce || curfunc.checkBoundsWithForce())) {
		p.pos[0] = -100;
		return;
	    }
	    double fmult = partMult;
	    if (useForce) {
		fmult *= .1;
		for (i = 0; i != 3; i++) {
		    p.vel[i] += fmult*Yhalf[i];
		    p.pos[i] += timeStep*p.vel[i];
		}
	    } else {
		for (i = 0; i != 3; i++)
		    p.pos[i] += fmult*Yhalf[i];
	    }
	    for (i = 0; i != 3; i++)
		Y[i] = p.pos[i];
	    if (curfunc.checkBounds(Y, oldY))
		p.pos[0] = -100;
	    return;
	}

	boolean adapt = curfunc.useAdaptiveRungeKutta();
	double h = (adapt) ? p.stepsize : 1;

	int steps = 0;
	double minh = .0001;
	while (t >= 0 && t < 1) {
	    if (t+h > 1)
		h = 1-t;

	    boundCheck = false;

	    // estimate one full step
	    rk(order, 0, Y, h);
	    
	    // bail out after one iteration for some slow fields
	    if (!adapt)
		break;

	    // estimate two half steps
	    rk(order, 0, Yhalf, h*0.5);
	    rk(order, 0, Yhalf, h*0.5);

	    if (boundCheck && (!useForce || curfunc.checkBoundsWithForce())) {
		p.pos[0] = -100;
		return;
	    }

	    // estimate the local error
	    localError = java.lang.Math.abs(Y[0] - Yhalf[0]) +
		java.lang.Math.abs(Y[1] - Yhalf[1]) +
		java.lang.Math.abs(Y[2] - Yhalf[2]);
	    
	    if (localError > E && h > minh) {
		//System.out.print(E + " " + t + " " + localError + " " + h + "\n");
		h *= 0.75; // decrease the step size
		if (h < minh)
		    h = minh;
		for (i = 0; i != order; i++)
		    Y[i] = Yhalf[i] = oldY[i]; 
		continue;
	    } else if (localError < (E * 0.5)) {
		h *= 1.25; // increase the step size
		if (h > maxh)
		    h = maxh;
	    }
	    
	    for (i = 0; i != order; i++)
		oldY[i] = Yhalf[i] = Y[i]; 

	    t += h;
	    steps++;
	}
	
	if (boundCheck && (!useForce || curfunc.checkBoundsWithForce())) {
	    p.pos[0] = -100;
	    return;
	}
	
	p.stepsize = h;
	for (i = 0; i != 3; i++)
	    p.pos[i] = Y[i];
	if (useForce) {
	    for (i = 0; i != 3; i++)
		p.vel[i] = Y[i+3];
	}
    }

    double dist2(double a[], double b[]) {
	double c0 = a[0]-b[0];
	double c1 = a[1]-b[1];
	double c2 = a[2]-b[2];
	return c0*c0+c1*c1+c2*c2;
    }

    void lineSegment(Particle p, int dir)
    {
	int numIter=0;
	double maxh=20;
	double error=0.0, E = .001, localError;
	int order = 3;
	double Y[] = rk_Y;
	double Yhalf[] = rk_Yhalf;
	oldY = rk_oldY;
	int i;
	int slice = sliceChooser.getSelectedIndex();
	boolean sliced = (slice > 0);
	slice -= SLICE_X;

	for (i = 0; i != 3; i++)
	    oldY[i] = Y[i] = Yhalf[i] = p.pos[i];
	double h = p.stepsize;
	ls_fieldavg[0] = ls_fieldavg[1] = ls_fieldavg[2] = 0;

	int steps = 0;
	double minh = .1;
	double segSize2min = .04*.04;
	double segSize2max = .08*.08;
	double lastd = 0;
	int avgct = 0;
	while (true) {
	    boundCheck = false;
	    steps++;
	    if (steps > 100) {
		//System.out.print("maxsteps\n");
		p.lifetime = -1;
		break;
	    }
	    //System.out.print(h + " " + boundCheck + "/\n");

	    // estimate one full step
	    rk(order, 0, Y, dir*h);

	    // estimate two half steps
	    rk(order, 0, Yhalf, dir*h*0.5);
	    rk(order, 0, Yhalf, dir*h*0.5);

	    if (sliced)
		Y[slice] = Yhalf[slice] = sliceval;

	    //System.out.print(h + " " + boundCheck + "\n");
	    if (boundCheck) {
		for (i = 0; i != order; i++)
		    Y[i] = Yhalf[i] = oldY[i]; 
		h /= 2;
		if (h < minh) {
		    p.lifetime = -1;
		    break;
		}
		continue;
	    }
	    if (Y[0] < -1 || Y[0] >= .999 ||
		Y[1] < -1 || Y[1] >= .999 ||
		Y[2] < -1 || Y[2] >= .999) {
		for (i = 0; i != order; i++)
		    Y[i] = Yhalf[i] = oldY[i]; 
		h /= 2;
		if (h < minh) {
		    //System.out.print("bound1\n");
		    p.lifetime = -1;
		    break;
		}
		continue;
	    }

	    // estimate the local error
	    localError = java.lang.Math.abs(Y[0] - Yhalf[0]) +
		java.lang.Math.abs(Y[1] - Yhalf[1]) +
		java.lang.Math.abs(Y[2] - Yhalf[2]);
	    
	    if (localError > E && h > minh) {
		h *= 0.75; // decrease the step size
		if (h < minh)
		    h = minh;
		for (i = 0; i != order; i++)
		    Y[i] = Yhalf[i] = oldY[i]; 
		continue;
	    } else if (localError < (E * 0.5)) {
		h *= 1.25; // increase the step size
		if (h > maxh)
		    h = maxh;
	    }
	    
	    double d = dist2(p.pos, Y);
	    if (!(d-lastd > 1e-10)) {
		// we're not getting anywhere!
		//System.out.print("nga " + d + " " + lastd + "\n");
		p.lifetime = -1;
		break;
	    }
	    if (d > segSize2max) {
		h /= 2;
		if (h < minh)
		    break;
		for (i = 0; i != order; i++)
		    Y[i] = Yhalf[i] = oldY[i]; 
		continue;
	    }
	    ls_fieldavg[0] += rk_k1[0];
	    ls_fieldavg[1] += rk_k1[1];
	    ls_fieldavg[2] += rk_k1[2];
	    avgct++;

	    if (d > segSize2min)
		break;
	    lastd = d;

	    for (i = 0; i != order; i++)
		oldY[i] = Yhalf[i] = Y[i]; 
	}

	//System.out.print(steps + " ss\n");
	p.stepsize = h;
	for (i = 0; i != 3; i++)
	    p.pos[i] = Y[i];
	p.phi = java.lang.Math.sqrt(ls_fieldavg[0]*ls_fieldavg[0]+
				    ls_fieldavg[1]*ls_fieldavg[1]+
				    ls_fieldavg[2]*ls_fieldavg[2])/avgct;
    }
    
    abstract class VecFunction {
	abstract String getName();
	abstract VecFunction createNext();
	boolean nonGradient() { return false; }
	boolean useRungeKutta() { return true; }
	boolean useAdaptiveRungeKutta() { return true; }
	boolean checkBoundsWithForce() { return true; }
	boolean noSplitFieldVectors() { return true; }
	int getViewPri(double cameraPos[], double pos[]) {
	    return 0;
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	}
	boolean checkBounds(double y[], double oldY[]) { return false; }
	abstract void getField(double result[], double y[]);
	boolean redistribute() { return true; }
	void setup() {}
	void setupFrame() {}
	void finishFrame() {}
	void actionPerformed() {}

	int getBestSlice() {
	    double y[] = new double[3];
	    double r1[] = new double[3];
	    double r2[] = new double[3];
	    double r3[] = new double[3];
	    y[0] = y[1] = y[2] = .9;
	    curfunc.getField(r1, y);
	    y[0] = .91;
	    curfunc.getField(r2, y);
	    y[0] = .9; y[1] = .91;
	    curfunc.getField(r3, y);
	    if (r1[0] == r2[0] && r1[1] == r2[1] && r1[2] == r2[2])
		return SLICE_X;
	    if (r1[0] == r3[0] && r1[1] == r3[1] && r1[2] == r3[2])
		return SLICE_Y;
	    return SLICE_Z;
	}

	void renderSphere(Graphics g, double sz) {
	    // draw particles behind sphere
	    renderItems(g, 2);

	    // draw back lines of sphere
	    g.setColor(darkYellow);
	    drawSphere(g, sz, true);

	    // draw parts inside sphere
	    renderItems(g, 1);

	    // draw front lines of sphere
	    g.setColor(darkYellow);
	    map3d(0, 0, 0, xpoints, ypoints, 0);
	    int r = (int) (getScalingFactor(0, 0, 0) * sz);
	    g.drawOval(xpoints[0]-r, ypoints[0]-r, r*2, r*2);
	    drawSphere(g, sz, false);

	    // draw rest of particles
	    renderItems(g, 0);
	}
    };

    class InverseSquaredRadial extends VecFunction {
	String getName() { return BUILD_CASE_EMV("point charge",
						 null, "1/r^2 single"); }
	void getField(double result[], double y[]) {
	    double r = distance(y);
	    if (r < chargeSize)
		boundCheck = true;
	    if (getPot) {
		result[0] = -.1/r;
		return;
	    }
	    double r3 = r*r*r;
	    double q = .0003/r3;
	    result[0] = -y[0]*q;
	    result[1] = -y[1]*q;
	    result[2] = -y[2]*q;
	}

	static final double chargeSize = .06;

	void drawCharge(Graphics g, double x, double y, double z) {
	    drawCharge(g, x, y, z, 0);
	}

	void drawCharge(Graphics g, double x, double y, double z, int dir) {
	    map3d(x, y, z,    xpoints, ypoints, 0);
	    map3d(x, y, z+.3*dir*reverse, xpoints, ypoints, 1);
	    g.setColor(darkYellow);
	    int r = (int) (getScalingFactor(x, y, z) * chargeSize);
	    g.fillOval(xpoints[0]-r, ypoints[0]-r, r*2, r*2);
	    if (dir != 0)
		drawArrow(g, null, xpoints[0], ypoints[0],
			  xpoints[1], ypoints[1], 5);
	}

	void render(Graphics g) {
	    drawCharge(g, 0, 0, 0);
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    int i;
	    i = intersectSphere(cameraPos, x[0], x[1], x[2], chargeSize);
	    if (i == 0)
		return 1;
	    if (i == 1)
		return -1;
	    return 0;
	}
	VecFunction createNext() { return new InverseSquaredRadialDouble(); }
    };

    class InverseSquaredRadialDouble extends InverseSquaredRadial {
	String getName() { return BUILD_CASE_EMV("point charge double", null,
						 "1/r^2 double"); }
	double sign2;
	int getBestSlice() { return SLICE_Y; }
	void getField(double result[], double y[]) {
	    double sep = aux1Bar.getValue()/100.;
	    double xx1 = y[0]-sep;
	    double xx2 = y[0]+sep;
	    double r1 = distance(xx1, y[1], y[2]);
	    if (r1 < chargeSize)
		boundCheck = true;
	    double r2 = distance(xx2, y[1], y[2]);
	    if (r2 < chargeSize)
		boundCheck = true;
	    if (getPot) {
		result[0] = -.05/r1 - .05*sign2/r2;
		if (sign2 == -1)
		    result[0] *= 2;
		return;
	    }
	    double q = .0003;
	    double rq1 = q/(r1*r1*r1);
	    double rq2 = q/(r2*r2*r2) * sign2;
	    result[0] = -xx1 *rq1-xx2 *rq2;
	    result[1] = -y[1]*rq1-y[1]*rq2;
	    result[2] = -y[2]*rq1-y[2]*rq2;
	}
	void setup() {
	    setXZView();
	    sign2 = 1;
	    setupBar(0, "Charge Separation", 30);
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    drawCharge(g, +sep, 0, 0);
	    drawCharge(g, -sep, 0, 0);
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double sep = aux1Bar.getValue()/100.;
	    if (intersectSphere(cameraPos, x[0], x[1], x[2],
				+sep, 0, 0, chargeSize)== 0 &&
		intersectSphere(cameraPos, x[0], x[1], x[2],
				-sep, 0, 0, chargeSize) == 0)
		return 1;
	    return 0;
	}
	VecFunction createNext() { return
	    BUILD_CASE_EMV(new InverseSquaredRadialDipole(),
			   null, new InverseRadial()); }
    };

#ifdef BUILD_E
    class InverseSquaredRadialDipole extends InverseSquaredRadialDouble {
	String getName() { return "dipole"; }
	void setup() {
	    super.setup();
	    sign2 = -1;
	}
	VecFunction createNext() { return new InverseSquaredRadialQuad(); }
    };
    class InverseSquaredRadialQuad extends InverseSquaredRadial {
	String getName() { return "quadrupole"; }
	void getField(double result[], double y[]) {
	    double sep = aux1Bar.getValue()/100.;
	    double xx1 = y[0] - sep;
	    double xx2 = y[0] + sep;
	    double yy1 = y[1] - sep;
	    double yy2 = y[1] + sep;
	    double zz  = y[2];
	    double r1 = distance(xx1, yy1, zz);
	    double r2 = distance(xx2, yy1, zz);
	    double r3 = distance(xx1, yy2, zz);
	    double r4 = distance(xx2, yy2, zz);
	    if (r1 < chargeSize || r2 < chargeSize ||
		r3 < chargeSize || r4 < chargeSize)
		boundCheck = true;
	    if (getPot) {
		result[0] = .05*(-1/r1+1/r2+1/r3-1/r4);
		return;
	    }
	    double q = .0003;
	    double rq1 = q/(r1*r1*r1);
	    double rq2 = q/(r2*r2*r2);
	    double rq3 = q/(r3*r3*r3);
	    double rq4 = q/(r4*r4*r4);
	    result[0] = -xx1*rq1-xx2*rq4+xx2*rq2+xx1*rq3;
	    result[1] = -yy1*rq1-yy2*rq4+yy1*rq2+yy2*rq3;
	    result[2] = -zz *rq1-zz *rq4+zz *rq2+zz *rq3;
	}
	void setup() {
	    super.setup();
	    setupBar(0, "Charge Separation", 30);
	    setXYViewExact();
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    int i, j;
	    for (i = -1; i <= 1; i += 2)
		for (j = -1; j <= 1; j += 2)
		    drawCharge(g, i*sep, j*sep, 0);
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double sep = aux1Bar.getValue()/100.;
	    int i, j;
	    for (i = -1; i <= 1; i += 2)
		for (j = -1; j <= 1; j += 2)
		    if (intersectSphere(cameraPos, x[0], x[1], x[2],
					i*sep, j*sep, 0, .06) != 0)
			return 0;
	    return 1;
	}
	VecFunction createNext() { return new InverseRadial(); }
    };
#endif
    class InverseRadial extends VecFunction {
	double lineLen;
	String getName() { return BUILD_CASE_EMV("charged line",
						 null, "1/r single line"); }
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1], 0);
	    if (r < lineWidth)
		boundCheck = true;
	    if (getPot) {
		result[0] = .4*java.lang.Math.log(r+1e-20);
		return;
	    }
	    double r2 = r*r;
	    result[0] = -.0003*y[0]/r2;
	    result[1] = -.0003*y[1]/r2;
	    result[2] = 0;
	}
	void setup() {
	    setXZView();
	    lineLen = 1;
	}
	void render(Graphics g) {
	    g.setColor(darkYellow);
	    map3d(0, 0, -lineLen, xpoints, ypoints, 0);
	    map3d(0, 0, +lineLen, xpoints, ypoints, 1);
	    g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectCylinder(cameraPos, x[0], x[1], x[2], lineWidth,
				  true) == 0)
		return 1;
	    if (intersection[2] >= -lineLen && intersection[2] <= lineLen)
		return 0;
	    return 1;
	}
	VecFunction createNext() { return new InverseRadialDouble(); }
    };

    class InverseRadialDouble extends VecFunction {
	InverseRadialDouble() { sign = 1; }
	String getName() { return BUILD_CASE_EMV("line charge double",
						 null, "1/r double lines"); }
	double sign;
	void getField(double result[], double y[]) {
	    double sep = aux1Bar.getValue()/100.;
	    double xx1 = y[0] - sep;
	    double xx2 = y[0] + sep;
	    double r1 = distance(xx1, y[1]);
	    double r2 = distance(xx2, y[1]);
	    if (r1 < lineWidth || r2 < lineWidth)
		boundCheck = true;
	    if (getPot) {
		result[0] = .2*(java.lang.Math.log(r1+1e-20)+
				sign*java.lang.Math.log(r2+1e-20));
		return;
	    }
	    double q = .0003;
	    double r1s = 1/(r1*r1);
	    double r2s = 1/(r2*r2*sign);
	    result[0] = q*(-xx1 *r1s-xx2 *r2s);
	    result[1] = q*(-y[1]*r1s-y[1]*r2s);
	    result[2] = 0;
	}
	void setup() {
	    setupBar(0, "Line Separation", 30);
	    setXZView();
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    g.setColor(darkYellow);
	    int i;
	    for (i = -1; i <= 1; i += 2) {
		map3d(sep*i, 0, -1, xpoints, ypoints, 0);
		map3d(sep*i, 0, +1, xpoints, ypoints, 1);
		g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    int i;
	    double sep = aux1Bar.getValue()/100.;
	    for (i = -1; i <= 1; i += 2) {
		if (intersectCylinder(cameraPos, x[0], x[1], x[2],
				      i*sep, 0, lineWidth, true) != 0)
		    return 0;
	    }
	    return 1;
	}
	VecFunction createNext() { return
	    BUILD_CASE_EMV(new InverseRadialDipole(), null,
			   new InverseRotational()); }
    };

#ifdef BUILD_E
    class InverseRadialDipole extends InverseRadialDouble {
	InverseRadialDipole() { sign = -1; }
	String getName() { return "dipole lines"; }
	VecFunction createNext() { return new InverseRadialQuad(); }
    };

    class InverseRadialQuad extends VecFunction {
	String getName() { return "quad lines"; }
	void getField(double result[], double y[]) {
	    double sep = aux1Bar.getValue()/100.;
	    double xx1 = y[0] + sep;
	    double xx2 = y[0] - sep;
	    double yy1 = y[1] + sep;
	    double yy2 = y[1] - sep;
	    double r1 = distance(xx1, yy1);
	    double r2 = distance(xx2, yy1);
	    double r3 = distance(xx1, yy2);
	    double r4 = distance(xx2, yy2);
	    if (r1 < lineWidth || r2 < lineWidth ||
		r3 < lineWidth || r4 < lineWidth)
		boundCheck = true;
	    if (getPot) {
		result[0] = .2*(+java.lang.Math.log(r1+1e-20)
				-java.lang.Math.log(r2+1e-20)
				-java.lang.Math.log(r3+1e-20)
				+java.lang.Math.log(r4+1e-20));
		return;
	    }
	    double q = .0003;
	    result[0] = q*(-xx1/(r1*r1)-xx2/(r4*r4) +xx2/(r2*r2)+xx1/(r3*r3));
	    result[1] = q*(-yy1/(r1*r1)-yy2/(r4*r4) +yy1/(r2*r2)+yy2/(r3*r3));
	    result[2] = 0;
	}
	void setup() {
	    setupBar(0, "Line Separation", 30);
	    setXYViewExact();
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    g.setColor(darkYellow);
	    int i, j;
	    for (i = -1; i <= 1; i += 2) {
		for (j = -1; j <= 1; j += 2) {
		    map3d(sep*i, sep*j, -1, xpoints, ypoints, 0);
		    map3d(sep*i, sep*j, +1, xpoints, ypoints, 1);
		    g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
		}
	    }
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    int i, j;
	    double sep = aux1Bar.getValue()/100.;
	    for (i = -1; i <= 1; i += 2) {
		for (j = -1; j <= 1; j += 2) {
		    if (intersectCylinder(cameraPos, x[0], x[1],
					  x[2], i*sep, j*sep, .01, true) != 0)
			return 0;
		}
	    }
	    return 1;
	}
	VecFunction createNext() { return new FiniteChargedLine(); }
    };

    class FiniteChargedLine extends InverseRadial {
	String getName() { return "finite line"; }
	void setup() {
	    setXZView();
	    setupBar(0, "Line Length", 30);
	}
	void setupFrame() {
	    lineLen = (aux1Bar.getValue()+1) / 101.;
	}
	void getField(double result[], double y[]) {
	    result[0] = result[1] = result[2] = 0;
	    getLineField(result, y, 0);
	}
	void getLineField(double result[], double y[], double off) {
	    double a1 = -lineLen-y[2];
	    double a2 =  lineLen-y[2];
	    double r = distance(y[0]-off, y[1]);
	    if (r < lineWidth && a1 <= 0 && a2 >= 0) 
		boundCheck = true;
	    double y2 = r*r;
	    double a12 = a1*a1;
	    double a22 = a2*a2;
	    double a12y2 = java.lang.Math.sqrt(a12+y2);
	    double a22y2 = java.lang.Math.sqrt(a22+y2);
	    if (getPot) {
		result[0] -= .2*java.lang.Math.log((a2+a22y2)/(a1+a12y2));
		return;
	    }
	    double q = .0001/lineLen;
	    double fth =
		q* (-1/(a12+y2+a1*a12y2)+1/(a22+y2+a2*a22y2));
	    result[0] += fth*(y[0]-off);
	    result[1] += fth*y[1];
	    result[2] += q*(1/a12y2 - 1/a22y2);
	}
	VecFunction createNext() { return new FiniteChargedLinePair(); }
    };

    class FiniteChargedLinePair extends FiniteChargedLine {
	double dipole;
	FiniteChargedLinePair() { dipole = 1; }
	String getName() { return "finite line pair"; }
	void setup() {
	    setXZView();
	    setupBar(0, "Line Length", 30);
	    setupBar(1, "Line Separation", 30);
	}
	void setupFrame() {
	    lineLen = (aux1Bar.getValue()+1) / 101.;
	}
	void getField(double result[], double y[]) {
	    double sep = aux2Bar.getValue()/100.;
	    result[0] = result[1] = result[2] = 0;
	    getLineField(result, y, +sep);
	    result[0] *= dipole;
	    result[1] *= dipole;
	    result[2] *= dipole;
	    getLineField(result, y, -sep);
	}
	void render(Graphics g) {
	    double sep = aux2Bar.getValue()/100.;
	    g.setColor(darkYellow);
	    int i;
	    for (i = -1; i <= 1; i += 2) {
		map3d(sep*i, 0, -lineLen, xpoints, ypoints, 0);
		map3d(sep*i, 0, +lineLen, xpoints, ypoints, 1);
		g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    int i;
	    double sep = aux2Bar.getValue()/100.;
	    for (i = -1; i <= 1; i += 2) {
		if (intersectCylinder(cameraPos, x[0], x[1], x[2],
				      i*sep, 0, lineWidth, true) != 0)
		    if (intersection[2] >= -lineLen &&
			  intersection[2] <= lineLen)
			return 0;
	    }
	    return 1;
	}
	VecFunction createNext() { return new FiniteChargedLineDipole(); }
    };

    class FiniteChargedLineDipole extends FiniteChargedLinePair {
	FiniteChargedLineDipole() { dipole = -1; }
	String getName() { return "finite line dipole"; }
	VecFunction createNext() { return new ConductingPlate(); }
    };

    class ConductingPlate extends VecFunction {
	String getName() { return "conducting plate"; }
	Complex z;
	boolean plate;
	double a;
	ConductingPlate() {
	    z = new Complex();
	    plate = true;
	}
	void setupFrame() {
	    a = (aux1Bar.getValue()+1)/100.;
	}
	void getField(double result[], double y[]) {
	    // smythe p89
	    if (y[2] >= -.02 && y[2] <= .02) {
		if ((plate  && y[0] >= -a && y[0] <= a) ||
		    (!plate && (y[0] >= a || y[0] <= -a)))
		boundCheck = true;
	    }
	    z.set(y[0]/a, y[2]/a);
	    if (y[2] < 0 && plate)
		z.b = -z.b;
	    if (getPot) {
		z.arcsin();
		result[0] = (plate) ? z.b*.6*a : -z.a*.6;
		return;
	    }
	    // here we calculate ((1-(z/a)^2)^(-1/2))/a, which is
	    // d/dz arcsin(z/a)
	    z.square();
	    z.mult(-1);
	    z.add(1);
	    z.pow(-.5);
	    z.mult(1/a);
	    if (plate) {
		// field = (Im dw/dz, Re dw/dz)
		result[2] = z.a * -.0007;
		result[0] = z.b * -.0007;
		if (y[2] < 0)
		    result[2] = -result[2];
	    } else {
		// field = (Re dw/dz, -Im dw/dz)
		result[0] = z.a * .0007;
		result[2] = -z.b * .0007;
	    }
	    result[1] = 0;
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) != 0) {
		if (intersection[0] >= -a && intersection[0] <= a)
		    return 1;
	    }
	    return 0;
	}
	void render(Graphics g) {
	    drawPlane(g, a, 1, 0);
	    renderItems(g, 0);
	}
	void setup() {
	    setupBar(0, "Plate Size", 60);
	    setXZView();
	}
	VecFunction createNext() { return new ChargedPlate(); }
    };
    class ChargedPlate extends ConductingPlate {
	Complex cz;
	ChargedPlate() { cz = new Complex(); }
	String getName() { return "charged plate"; }
	double getPot(double a1, double a2, double y) {
	    cz.set (y, -a1);
	    cz.mult(y,  a2);
	    cz.log();
	    double b1 = cz.b;
	    cz.set (y,  a1);
	    cz.mult(y, -a2);
	    cz.log();
	    double y2 = y*y;
	    return .2*(2*(a1-a2) + (b1-cz.b)*y +
		       a2*java.lang.Math.log(a2*a2+y2) -
		       a1*java.lang.Math.log(a1*a1+y2));
	}
	void getField(double result[], double y[]) {
	    if (y[2] >= -.01 && y[2] <= .01 &&
		(y[0] >= -a && y[0] <= a))
		boundCheck = true;
	    double a1 = -a-y[0];
	    double a2 =  a-y[0];
	    double y2 = y[2]*y[2];
	    if (y2 == 0)
		y2 = 1e-8;
	    if (getPot) {
		result[0] = getPot(a1, a2, y[2]);
		return;
	    }
	    double q = .0003/a;
	    result[0] = .5*q*
	        java.lang.Math.log((y2+a2*a2)/(y2+a1*a1));
	    result[1] = 0;
	    result[2] = q*
		(java.lang.Math.atan(a1/y[2])-
		 java.lang.Math.atan(a2/y[2]));
	}
	VecFunction createNext() { return new ChargedPlatePair(); }
    };

    class ChargedPlatePair extends ChargedPlate {
	String getName() { return "charged plate pair"; }
	boolean useRungeKutta() { return false; }
	void getField(double result[], double y[]) {
	    double sep = aux2Bar.getValue()/100.;
	    if ((y[2] >= -.01+sep && y[2] <= .01+sep ||
		 y[2] >= -.01-sep && y[2] <= .01-sep) &&
		y[0] >= -a && y[0] <= a)
		boundCheck = true;
	    double a1 = -a-y[0];
	    double a2 =  a-y[0];
	    double y1 = y[2]-sep;
	    double y12 = y1*y1;
	    if (y12 == 0)
		y12 = 1e-8;
	    double y2 = y[2]+sep;
	    double y22 = y2*y2;
	    if (y22 == 0)
		y22 = 1e-8;
	    if (getPot) {
		result[0] = getPot(a1, a2, y1)-
		    getPot(a1, a2, y2);
		return;
	    }

	    double q = .0003/a;
	    result[0] = .5*q*
	        (java.lang.Math.log((y12+a2*a2)/(y12+a1*a1)) -
		 java.lang.Math.log((y22+a2*a2)/(y22+a1*a1)));
	    result[1] = 0;
	    result[2] = q*
		(java.lang.Math.atan(a1/y1)
		-java.lang.Math.atan(a2/y1)
		-java.lang.Math.atan(a1/y2)
		+java.lang.Math.atan(a2/y2));
	}
	void setup() {
	    setupBar(0, "Sheet Size", 30);
	    setupBar(1, "Sheet Separation", 33);
	    setXZViewExact();
	}
	boolean checkBounds(double y[], double oldY[]) {
	    double size = aux1Bar.getValue()/100.;
	    double sep = aux2Bar.getValue()/100.;
	    // check to see if particle has passed through one of plates
	    if (y[0] >= -size && y[0] <= size) {
		if (y[2] > sep) {
		    if (oldY[2] < sep)
			return true;
		} else if (y[2] < -sep) {
		    if (oldY[2] > -sep)
			return true;
		} else if (oldY[2] > sep || oldY[2] < -sep)
		    return true;
	    }
	    return false;
	}
	void render(Graphics g) {
	    double sep = aux2Bar.getValue()/100.;
	    double size = aux1Bar.getValue()/100.;
	    int i;
	    for (i = 0; i != 2; i++) {
		double s = (i == 0) ? sep : -sep;
		drawPlane(g, size, 1, s);
	    }
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double sep = aux2Bar.getValue()/100.;
	    double size = aux1Bar.getValue()/100.;
	    if (intersectZPlane(cameraPos, +sep, x[0], x[1], x[2]) != 0) {
		//System.out.print(size + " " + intersection[0] + " " + intersection[1] + "\n");
		if (intersection[0] >= -size && intersection[0] <= size)
		    return 0;
	    }
	    if (intersectZPlane(cameraPos, -sep, x[0], x[1], x[2]) != 0) {
		//System.out.print(size + " " + intersection[0] + " " + intersection[1] + "\n");
		if (intersection[0] >= -size && intersection[0] <= size)
		    return 0;
	    }
	    return 1;
	}
	VecFunction createNext() { return new InfiniteChargedPlane(); }
    };

    class InfiniteChargedPlane extends VecFunction {
	String getName() { return "infinite plane"; }
	int getBestSlice() { return SLICE_Y; }
	void getField(double result[], double y[]) {
	    double alpha = .0003;
	    if (y[2] > -.01 && y[2] < .01)
		boundCheck = true;
	    if (getPot) {
		result[0] = java.lang.Math.abs(y[2])-1;
		return;
	    }
	    result[0] = 0;
	    result[1] = 0;
	    result[2] = (y[2] < 0) ? alpha : -alpha;
	}
	void setup() {
	    setXZView();
	}
	void render(Graphics g) {
	    renderItems(g, 1);
	    drawPlane(g, 1, 1, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) == 0)
		return 0;
	    return 1;
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new SphereAndPointCharge(); }
    };
    
    class SphereAndPointCharge extends InverseSquaredRadial {
	String getName() { return "conducting sphere + pt"; }
	double getSphereRadius() { return (aux1Bar.getValue()+1)/110.; }
	double getSeparation() { return aux2Bar.getValue()/100.; }
	double getSpherePos() { return getSeparation()/2; }
	double getPointPos() { return -getSeparation()/2-getSphereRadius(); }
	int getBestSlice() { return SLICE_Y; }
	void setup() {
	    setupBar(0, "Sphere Size", 30);
	    setupBar(1, "Separation", 50);
	    setupBar(2, "Sphere Potential", 50);
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = -.0003;
	    double a = getSphereRadius();
	    double b = getSeparation() + a;
	    double spos = getSpherePos();
	    double imageQ = -q*a/b;
	    double imagePos = spos - a*a/b;

	    // first charge at center of sphere
	    double x1 = y[0]-spos;
	    double r1 = distance(x1, y[1], y[2]);
	    if (r1 < a)
		boundCheck = true;
	    double sq = (aux3Bar.getValue()-50)*.002*a/50.;

	    // second charge at image position
	    double x2 = y[0]-imagePos;
	    double r2 = distance(x2, y[1], y[2]);

	    // third charge at point
	    double x3 = y[0]-getPointPos();
	    double r3 = distance(x3, y[1], y[2]);
	    if (r3 < chargeSize)
		boundCheck = true;

	    if (getPot) {
		result[0] = 400*(sq/r1 + imageQ/r2 + q/r3);
		return;
	    }
	    double a1 = sq/(r1*r1*r1);
	    double a2 = imageQ/(r2*r2*r2);
	    double a3 = q/(r3*r3*r3);
	    result[0] = x1*a1 + x2*a2 + x3*a3;
	    result[1] = y[1]*(a1+a2+a3);
	    result[2] = y[2]*(a1+a2+a3);
	}
	void render(Graphics g) {
	    int ic = intersectSphere(cameraPos, getPointPos()-getSpherePos(),
				     0, 0, getSphereRadius());
	    if (ic != 0)
		drawCharge(g, getPointPos(), 0, 0, 0);
	    fillSphere(g, getSphereRadius(), getSpherePos());
	    if (ic == 0)
		drawCharge(g, getPointPos(), 0, 0, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectSphere(cameraPos, x[0], x[1], x[2],
				getSpherePos(), 0, 0, getSphereRadius()) != 0)
		return -1;
	    if (intersectSphere(cameraPos, x[0], x[1], x[2],
				getPointPos(), 0, 0, chargeSize) != 0)
		return -1;
	    return 0;
	}
	VecFunction createNext() { return new ChargedSphereAndPointCharge(); }
    };

    class ChargedSphereAndPointCharge extends SphereAndPointCharge {
	String getName() { return "charged sphere + pt"; }
	void setup() {
	    setupBar(0, "Sphere Size", 30);
	    setupBar(1, "Separation", 50);
	    setupBar(2, "Sphere Charge", 50);
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = -.0003;
	    double a = getSphereRadius();
	    double b = getSeparation() + a;
	    double spos = getSpherePos();

	    // first charge at center of sphere
	    double x1 = y[0]-spos;
	    double r1 = distance(x1, y[1], y[2]);
	    if (r1 < a)
		boundCheck = true;
	    double sq = (aux3Bar.getValue()-50)*.0006/50.;

	    // second charge at point
	    double x3 = y[0]-getPointPos();
	    double r3 = distance(x3, y[1], y[2]);
	    if (r3 < chargeSize)
		boundCheck = true;

	    if (getPot) {
		result[0] = 300*(sq/r1 + q/r3);
		return;
	    }
	    double a1 = sq/(r1*r1*r1);
	    double a3 = q/(r3*r3*r3);
	    result[0] = x1*a1 + x3*a3;
	    result[1] = y[1]*(a1+a3);
	    result[2] = y[2]*(a1+a3);
	}
	VecFunction createNext() { return new CylinderAndLineCharge(); }
    };

    class CylinderAndLineCharge extends VecFunction {
	String getName() { return "cyl + line charge"; }
	double getCylRadius() { return (aux1Bar.getValue()+1)/110.; }
	double getSeparation() { return aux2Bar.getValue()/100.; }
	double getCylPos() { return getSeparation()/2; }
	double getPointPos() { return -getSeparation()/2-getCylRadius(); }
	void setup() {
	    setupBar(0, "Cylinder Size", 30);
	    setupBar(1, "Separation", 30);
	    setupBar(2, "Cylinder Potential", 50);
	    setXYView();
	}
	void getField(double result[], double y[]) {
	    // Smythe p70
	    double q = -.0003;
	    double a = getCylRadius();
	    double b = getSeparation() + a;
	    double spos = getCylPos();
	    double imagePos = spos - a*a/b;
	    double x1 = y[0]-spos;
	    double r1 = distance(x1, y[1]);
	    if (r1 < a)
		boundCheck = true;
	    double x2 = y[0]-imagePos;
	    double r2 = distance(x2, y[1]);
	    double x3 = y[0]-getPointPos();
	    double r3 = distance(x3, y[1]);
	    double chargeSize = .06;
	    if (r3 < chargeSize)
		boundCheck = true;
	    // pick a charge at the center of the cylinder that puts it
	    // at ground potential
	    double cq = q*(java.lang.Math.exp(b-a)-1) +
		(aux3Bar.getValue()/50.-1)*a*.0006;
	    if (getPot) {
		result[0] = -700*(cq*java.lang.Math.log(r1+1e-20)
				  -q*java.lang.Math.log(r2+1e-20)
				  +q*java.lang.Math.log(r3+1e-20));
		return;
	    }
	    double a1 = cq/(r1*r1);
	    double a2 = -q/(r2*r2);
	    double a3 = q/(r3*r3);
	    result[0] = x1*a1 + x2*a2 + x3*a3;
	    result[1] = y[1]*(a1+a2+a3);
	    result[2] = 0;
	}
	void render(Graphics g) {
	    int ic = intersectCylinder(cameraPos, getPointPos(), 0, 0,
				       getCylPos(), 0, getCylRadius(), false);
	    if (ic == 0)
		fillCylinder(g, getCylRadius(), getCylPos());
	    g.setColor(darkYellow);
	    map3d(getPointPos(), 0, -1, xpoints, ypoints, 0);
	    map3d(getPointPos(), 0, +1, xpoints, ypoints, 1);
	    g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    if (ic != 0)
		fillCylinder(g, getCylRadius(), getCylPos());
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectCylinder(cameraPos, x[0], x[1], x[2],
				  getPointPos(), 0, lineWidth, true) != 0)
		return -1;
	    if (intersectCylinder(cameraPos, x[0], x[1], x[2],
				  getCylPos(), 0, getCylRadius(), true) != 0)
		return -1;
	    return 0;
	}
	VecFunction createNext() { return new SphereInField(); }
    };

    class SphereInField extends VecFunction {
	SphereInField() { conducting = true; showD = false; }
	String getName() { return "conducting sphere in field"; }
	boolean conducting;
	boolean showD;
	int getBestSlice() { return SLICE_Y; }
	void getField(double result[], double y[]) {
	    // marion p74
	    double a = aux1Bar.getValue()/100.;
	    double a3 = a*a*a;
	    double r = distance(y);
	    double e1 = aux2Bar.getValue()/10. + 1;
	    double dimult = (conducting) ? 1 : (e1-1)/(e1+2);
	    double fmult = .0006;
	    if (r < a) {
		result[0] = result[1] = 0;
		if (conducting)
		    boundCheck = true;
		else {
		    if (getPot)
			result[0] = -(1-dimult)*y[2];
		    else
			result[2] = (showD) ? e1*fmult*(1-dimult) :
			                      fmult*(1-dimult);
		}
		return;
	    }
	    double costh = y[2]/r;
	    double sinth = java.lang.Math.sqrt(1-costh*costh);
	    double cosph = y[0]/(r*sinth);
	    double sinph = y[1]/(r*sinth);
	    double r_3 = 1/(r*r*r);
	    if (getPot) {
		result[0] = -(1-dimult*a3*r_3)*y[2];
		return;
	    }
	    double er  = (1+dimult*2*a3*r_3)*costh*fmult;
	    double eth = -(1-dimult*a3*r_3)*sinth*fmult;
	    er /= r;
	    result[0] = y[0]*er + eth*costh*cosph;
	    result[1] = y[1]*er + eth*costh*sinph;
	    result[2] = y[2]*er - eth*sinth;
	}
	void setup() {
	    setupBar(0, "Sphere Size", 60);
	    setXZViewExact();
	}
	void render(Graphics g) {
	    double a = aux1Bar.getValue()/100.;
	    fillSphere(g, a, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double a = aux1Bar.getValue()/100.;
	    return intersectSphere(cameraPos, x[0], x[1], x[2], a);
	}
	VecFunction createNext() { return new DielectricSphereInFieldE(); }
    };
    class DielectricSphereInFieldE extends SphereInField {
	DielectricSphereInFieldE() { conducting = false; showD = false; }
	String getName() { return "dielec sphere in field E"; }
	void setup() {
	    setupBar(0, "Sphere Size", 60);
	    setupBar(1, "Dielectric Strength", 60);
	    setXZViewExact();
	}
	void render(Graphics g) {
	    double a = aux1Bar.getValue()/100.;
	    renderSphere(g, a);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double a = aux1Bar.getValue()/100.;
	    return intersectSphere(cameraPos, x[0], x[1], x[2], a);
	}
	boolean noSplitFieldVectors() { return false; }
	VecFunction createNext() { return new DielectricSphereInFieldD(); }
    };
    class DielectricSphereInFieldD extends DielectricSphereInFieldE {
	DielectricSphereInFieldD() { conducting = false; showD = true; }
	String getName() { return "dielec sphere in field D"; }
	VecFunction createNext() { return new CylinderInField(); }
    };
    class CylinderInField extends VecFunction {
	CylinderInField() { conducting = true; showD = false; }
	String getName() { return "cylinder in field"; }
	boolean conducting, showD;
	double a;
	void setupFrame() {
	    a = aux1Bar.getValue()/100.;
	}
	void getField(double result[], double y[]) {
	    // smythe p67
	    double a2 = a*a;
	    double r = distance(y[0], y[1]);
	    double e1 = aux2Bar.getValue()/10. + 1;
	    double dimult = (conducting) ? 1 : (e1-1)/(e1+1);
	    double fmult = .0006;
	    if (r < a) {
		result[0] = result[1] = result[2] = 0;
		if (conducting)
		    boundCheck = true;
		else if (getPot)
		    result[0] = -(1-dimult)*y[0];
		else
		    result[0] = (showD) ? e1*fmult*(1-dimult) :
		        fmult*(1-dimult);
		return;
	    }
	    double costh = y[0]/r;
	    double sinth = y[1]/r;
	    double r_2 = 1/(r*r);
	    if (getPot) {
		result[0] = -(1-dimult*a2*r_2)*y[0];
		return;
	    }
	    double er  = (1+dimult*a2*r_2)*costh*fmult;
	    double eth = -(1-dimult*a2*r_2)*sinth*fmult;
	    er /= r;
	    result[0] = y[0]*er - eth*sinth;
	    result[1] = y[1]*er + eth*costh;
	    result[2] = 0;
	}
	void setup() {
	    setupBar(0, "Cylinder Size", 40);
	    setXYView();
	}
	void render(Graphics g) {
	    fillCylinder(g, a, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    return intersectCylinder(cameraPos, x[0], x[1], x[2],
				     a, conducting);
	}
	VecFunction createNext() { return new DielectricCylinderInFieldE(); }
    };

    class DielectricCylinderInFieldE extends CylinderInField {
	DielectricCylinderInFieldE() { conducting = false; showD = false; }
	String getName() { return "dielec cyl in field E"; }
	void setup() {
	    setupBar(0, "Cylinder Size", 40);
	    setupBar(1, "Dielectric Strength", 60);
	    setXYView();
	}
	void render(Graphics g) {
	    // draw particles behind cylinder
	    renderItems(g, 2);
	    // draw back faces of cylinder
	    g.setColor(darkYellow);
	    drawCylinder(g, a, 0, true);
	    // draw particles inside
	    renderItems(g, 1);
	    // draw front faces
	    g.setColor(darkYellow);
	    drawCylinder(g, a, 0, false);
	    // draw particles in front
	    renderItems(g, 0);
	}
	boolean noSplitFieldVectors() { return false; }
	VecFunction createNext() { return new DielectricCylinderInFieldD(); }
    };
    class DielectricCylinderInFieldD extends DielectricCylinderInFieldE {
	DielectricCylinderInFieldD() { conducting = false; showD = true; }
	String getName() { return "dielec cyl in field D"; }
	VecFunction createNext() { return new DielectricBoundaryE(); }
    };

    class DielectricBoundaryE extends InverseSquaredRadial {
	boolean showD, conducting;
	double planeZ;
	DielectricBoundaryE() { conducting = false; showD = false; }
	int getBestSlice() { return SLICE_Y; }
	String getName() { return "dielec boundary E"; }
	void setup() {
	    setupBar(0, "Charge Location", 60);
	    if (!conducting)
		setupBar(1, "Dielectric Strength", 60);
	    setViewMatrix(0, pi/40-pi/2);
	}
	void render(Graphics g) {
	    renderItems(g, 1);
	    drawPlane(g, 1, 1, planeZ);
	    double cx = aux1Bar.getValue()/50.-1.001;
	    drawCharge(g, 0, 0, cx);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double cx = aux1Bar.getValue()/50.-1.001;
	    if (intersectSphere(cameraPos, x[0], x[1], x[2]-cx,
				chargeSize) == 0
		&& intersectZPlane(cameraPos, 0, x[0], x[1], x[2]-planeZ) == 0)
		return 0;
	    return 1;
	}
	void getField(double result[], double y[]) {
	    double cx = aux1Bar.getValue()/50.-1.001;
	    double r1 = distance(y[0], y[1], y[2]-cx);
	    if (r1 < chargeSize)
		boundCheck = true;
	    double eps1 = 1;
	    double eps2 = aux2Bar.getValue()/10. + 1;
	    if (conducting)
		eps2 = 1e8;
	    if (cx < planeZ) {
		eps1 = eps2;
		eps2 = 1;
	    }
	    double q1 = .0003;
	    double q2 = -(eps2-eps1)/(eps2+eps1)*q1;
	    double ep = eps1;
	    if (cx > planeZ && y[2] < planeZ || cx < planeZ && y[2] > planeZ) {
		q1 = 2*eps2*q1/(eps2+eps1);
		q2 = 0;
		ep = eps2;
	    }
	    double r2 = distance(y[0], y[1], y[2]-planeZ*2+cx);
	    if (getPot) {
		result[0] = -1000*(q1/(r1*ep) + q2/(r2*ep));
		return;
	    }
	    if (!showD) {
		q1 /= ep;
		q2 /= ep;
	    }
	    double rq1 = q1/(r1*r1*r1);
	    double rq2 = q2/(r2*r2*r2);
	    result[0] = -y[0]*(rq1+rq2);
	    result[1] = -y[1]*(rq1+rq2);
	    result[2] = -(y[2]-cx)*rq1-(y[2]-planeZ*2+cx)*rq2;
	}
	VecFunction createNext() { return new DielectricBoundaryD(); }
    };
    class DielectricBoundaryD extends DielectricBoundaryE {
	DielectricBoundaryD() { conducting = false; showD = true; }
	String getName() { return "dielec boundary D"; }
	VecFunction createNext() { return new ConductingPlane(); }
    };
    class ConductingPlane extends DielectricBoundaryE {
	ConductingPlane() { conducting = true; showD = false; planeZ = -1; }
	String getName() { return "conducting plane + pt"; }
	VecFunction createNext() { return new FastChargeEField(); }
    };

    class FastChargeEField extends MovingChargeField {
	String getName() { return "fast charge"; }
	int getBestSlice() { return SLICE_Y; }
	void getField(double result[], double y[]) {
	    double rz = distance(y);
	    if (rz < chargeSize)
		boundCheck = true;
	    double r = distance(y[0], y[1]);
	    // sine of angle between particle vector and direction of motion
	    double sinth = r/rz;
	    double beta = (aux1Bar.getValue()+1)/102.;
	    if (getPot) {
		result[0] = -.1/
		    (rz*java.lang.Math.pow(1-beta*beta*sinth*sinth, .5));
		return;
	    }
	    
	    // field = e R (1-beta^2) / R^3 (1-beta^2 sin^2 th)^(3/2)
	    double b = -.0001 * (1-beta*beta)/
		(rz*rz*rz*java.lang.Math.pow(1-beta*beta*sinth*sinth, 1.5));
	    result[0] = b*y[0];
	    result[1] = b*y[1];
	    result[2] = b*y[2];
	}
	void setup() {
	    setupBar(0, "Speed/C", 60);
	    super.setup();
	}
	boolean nonGradient() { return false; }
	VecFunction createNext() { return new ChargedRing(); }
    };

    class ChargedRing extends CurrentLoopField {
	ChargedRing() { useColor = false; }
	String getName() { return "charged ring"; }
	int getBestSlice() { return SLICE_Y; }
	void getField(double result[], double y[]) {
	    getLoopField(result, y, 0);
	}
	void getLoopField(double result[], double y[], double zoff) {
	    double xx = y[0];
	    double yy = y[1];
	    double zz = y[2]+zoff;
	    int i;
	    result[0] = result[1] = result[2] = 0;
	    double size = aux1Bar.getValue()/100.;
	    int ct = 8;
	    double q = (getPot) ? .2/ct : -.0006/ct;
	    double ang0 = java.lang.Math.atan2(y[1], y[0]);
	    for (i = 0; i != ct; i++) {
		double ang = pi*2*i/ct;
		double jxx = size*java.lang.Math.cos(ang+ang0);
		double jyy = size*java.lang.Math.sin(ang+ang0);
		double rx = xx-jxx;
		double ry = yy-jyy;
		double rz = zz;
		double r = java.lang.Math.sqrt(rx*rx+ry*ry+rz*rz);
		if (r < .06)
		    boundCheck = true;
		double r3 = r*r*r;
		if (getPot) {
		    result[0] += -q/r;
		} else {
		    result[0] += q*rx/r3;
		    result[1] += q*ry/r3;
		    result[2] += q*rz/r3;
		}
	    }
	}
	void setup() {
	    setupBar(0, "Ring Size", 40);
	    setXZView();
	}
	boolean nonGradient() { return false; }
	VecFunction createNext() { return new ChargedRingPair(); }
    };
    class ChargedRingPair extends ChargedRing {
	String getName() { return "charged ring pair"; }
	double sep;
	int r2;
	double tres1[], tres2[];
	ChargedRingPair() {
	    tres1 = new double[3];
	    tres2 = new double[3];
	    r2 = 1;
	}
	void setupFrame() {
	    sep = aux2Bar.getValue()/100.;
	}
	void getField(double result[], double y[]) {
	    getLoopField(tres1, y, -sep);
	    getLoopField(tres2, y, sep);
	    int i;
	    for (i = 0; i != 3; i++)
		result[i] = tres1[i] + r2*tres2[i];
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    double size = aux1Bar.getValue()/100.;
	    renderLoop(g, 0, -sep, 1, size);
	    renderLoop(g, 0,  sep, 1, size);
	    renderItems(g, 1);
	}
	void setup() {
	    setupBar(0, "Ring Size", 40);
	    setupBar(1, "Ring Separation", 40);
	    setXZView();
	}
	VecFunction createNext() { return new ChargedRingDipole(); }
    };
    class ChargedRingDipole extends ChargedRingPair {
	String getName() { return "charged ring dipole"; }
	ChargedRingDipole() {
	    r2 = -1;
	}
	VecFunction createNext() { return new SlottedPlane(); }
    }
    class SlottedPlane extends VecFunction {
	String getName() { return "slotted conducting plane"; }
	Complex z, z2;
	SlottedPlane() {
	    z = new Complex();
	    z2 = new Complex();
	}
	void getField(double result[], double y[]) {
	    // W = -.5E (z +- (z^2-a^2)^1/2)
	    // dw/dz = -.5E (1 +- z/(z^2-a^2)^1/2)
	    // Smythe p92
	    double a = (aux1Bar.getValue()+1)/101.;
	    if (y[2] >= -.01 && y[2] <= .01 &&
		(y[0] < -a || y[0] > a))
		boundCheck = true;
	    z.set(y[0], y[2]);
	    z2.set(z);
	    z2.square();
	    z2.add(-a*a);
	    if (getPot) {
		z2.pow(.5);
		if (z2.b < 0)
		    z2.mult(-1);
		z2.add(z.a, z.b);
		result[0] = -z2.b*.6;
		return;
	    }
	    z2.pow(-.5);
	    // I can barely understand what Smythe is talking about but
	    // I think he wants the square root to always have a positive
	    // imaginary part.  Here we already took the reciprocal
	    // (by calling pow(-.5) instead of pow(.5)) so we want the
	    // root to have a negative imaginary part instead.
	    if (z2.b > 0)
		z2.mult(-1);
	    z2.mult(z);
	    // field = (Im dw/dz, Re dw/dz)
	    result[2] = (1+z2.a) * .003;
	    result[0] = (z2.b) * .003;
	    result[1] = 0;
	}
	void setup() {
	    setupBar(0, "Slot Size", 60);
	    setXZView();
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) != 0) {
		double a = (aux1Bar.getValue()+1)/101.;
		if (intersection[0] < -a || intersection[0] > a)
		    return 1;
	    }
	    return 0;
	}
	void render(Graphics g) {
	    g.setColor(darkYellow);
	    double a = (aux1Bar.getValue()+1)/101.;
	    map3d(-1, -1, 0, xpoints, ypoints, 0);
	    map3d(-a, -1, 0, xpoints, ypoints, 1);
	    map3d(-a, +1, 0, xpoints, ypoints, 2);
	    map3d(-1, +1, 0, xpoints, ypoints, 3);
	    g.fillPolygon(xpoints, ypoints, 4);
	    map3d( 1, -1, 0, xpoints, ypoints, 0);
	    map3d( a, -1, 0, xpoints, ypoints, 1);
	    map3d( a, +1, 0, xpoints, ypoints, 2);
	    map3d( 1, +1, 0, xpoints, ypoints, 3);
	    g.fillPolygon(xpoints, ypoints, 4);
	    renderItems(g, 0);
	}
	VecFunction createNext() { return new PlanePair(); }
    };
    class PlanePair extends ConductingPlate {
	String getName() { return "conducting planes w/ gap"; }
	PlanePair() { super(); plate = false; }
	void setup() {
	    setupBar(0, "Gap Size", 20);
	    setXZView();
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) != 0) {
		if (intersection[0] < -a || intersection[0] > a)
		    return 1;
	    }
	    return 0;
	}
	void render(Graphics g) {
	    g.setColor(darkYellow);
	    map3d(-1, -1, 0, xpoints, ypoints, 0);
	    map3d(-a, -1, 0, xpoints, ypoints, 1);
	    map3d(-a, +1, 0, xpoints, ypoints, 2);
	    map3d(-1, +1, 0, xpoints, ypoints, 3);
	    g.fillPolygon(xpoints, ypoints, 4);
	    map3d( 1, -1, 0, xpoints, ypoints, 0);
	    map3d( a, -1, 0, xpoints, ypoints, 1);
	    map3d( a, +1, 0, xpoints, ypoints, 2);
	    map3d( 1, +1, 0, xpoints, ypoints, 3);
	    g.fillPolygon(xpoints, ypoints, 4);
	    renderItems(g, 0);
	}
	VecFunction createNext() { return null; }
    };
#endif
#ifndef BUILD_E
    class InverseRotational extends InverseRadial {
	String getName() { return BUILD_CASE_EMV(null, "current line",
				  "1/r rotational"); }
	void setup() {
	    setXYViewExact();
	}
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    if (showA) {
		result[0] = result[1] = 0;
		result[2] = -.001*(java.lang.Math.log(r)-.5);
	    } else {
		if (r < lineWidth*2)
		    boundCheck = true;
		rotateParticle(result, y, .0001/(r*r));
	    }
	}
	void render(Graphics g) {
	    g.setColor(darkYellow);
	    map3d(0, 0, -1, xpoints, ypoints, 0);
	    map3d(0, 0, +1, xpoints, ypoints, 1);
	    drawCurrentLine(g, xpoints[0], ypoints[0], xpoints[1], ypoints[1],
			    12, true, 1);
	    renderItems(g, 1);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new InverseRotationalDouble(); }
    };
    class InverseRotationalDouble extends InverseRadialDouble {
	InverseRotationalDouble() { dir2 = 1; ext = false; }
	int dir2;
	boolean ext;
	String getName() { return BUILD_CASE_EMV(null, "current line double",
						 "1/r rotational double"); }
	void getField(double result[], double y[]) {
	    double sep = aux1Bar.getValue()/100.;
	    double r  = distance(y[0] - sep, y[1]);
	    double r2 = distance(y[0] + sep, y[1]);
	    if (ext) {
		double p = aux3Bar.getValue()*pi/50.;
		double s = aux2Bar.getValue()/30.;
		getDirectionField(result, y, pi/2, p);
		result[0] *= s;
		result[1] *= s;
		result[2] *= s;
	    } else
		result[0] = result[1] = result[2] = 0;
	    if (showA) {
		if (dir2 == 1)
		    result[2] += -.001*(java.lang.Math.log(r)+
				       java.lang.Math.log(r2)-1);
		else
		    result[2] += -.001*(java.lang.Math.log(r)-
					java.lang.Math.log(r2));
	    } else {
		if (r < lineWidth*2)
		    boundCheck = true;
		rotateParticleAdd(result, y, .0001/(r*r),  sep, 0);
		if (r2 < lineWidth*2)
		    boundCheck = true;
		rotateParticleAdd(result, y, dir2*.0001/(r2*r2), -sep, 0);
	    }
	}
	void setup() {
	    setupBar(0, "Line Separation", 30);
	    if (ext) {
		setupBar(1, "Ext. Strength", 28);
		setupBar(2, "Ext. Direction", 0);
	    }
	    setXYViewExact();
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    g.setColor(darkYellow);
	    int i;
	    for (i = -1; i <= 1; i += 2) {
		map3d(sep*i, 0, -1, xpoints, ypoints, 0);
		map3d(sep*i, 0, +1, xpoints, ypoints, 1);
		int dir = (i == -1) ? dir2 : 1;
		drawCurrentLine(g, xpoints[0], ypoints[0],
				xpoints[1], ypoints[1], 12, true, dir);
	    }
	    renderItems(g, 1);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new InverseRotationalDoubleExt(); }
    };
    class InverseRotationalDoubleExt extends InverseRotationalDouble {
	InverseRotationalDoubleExt() { ext = true; }
	String getName() { return BUILD_CASE_EMV(null, "cur line double + ext",
						 "1/r rot double + ext"); }
	VecFunction createNext() { return new InverseRotationalDipole(); }
    };
    class InverseRotationalDipole extends InverseRotationalDouble {
	InverseRotationalDipole() { dir2 = -1; }
	String getName() { return BUILD_CASE_EMV(null, "current line dipole",
						 "1/r rotational dipole"); }
	VecFunction createNext() { return new InverseRotationalDipoleExt(); }
    };
    class InverseRotationalDipoleExt extends InverseRotationalDouble {
	InverseRotationalDipoleExt() { dir2 = -1; ext = true; }
	void setup() {
	    super.setup();
	    aux2Bar.setValue(17);
	    aux3Bar.setValue(25);
	}
	String getName() { return BUILD_CASE_EMV(null, "cur line dipole + ext",
						 "1/r rot dipole + ext"); }
	VecFunction createNext() { return new OneDirectionFunction(); }
    };
    class OneDirectionFunction extends VecFunction {
	String getName() { return BUILD_CASE_EMV(null, "uniform field",
						 "one direction"); }
	void getField(double result[], double y[]) {
	    double th = aux1Bar.getValue() * pi /50.;
	    double ph = aux2Bar.getValue() * pi /50.;
	    getDirectionField(result, y, th, ph);
	}

	void setup() {
	    setupBar(0, "Theta", 25);
	    setupBar(1, "Phi", 0);
	    setXYView();
	}
	VecFunction createNext() { return
	    BUILD_CASE_EMV(null, new MovingChargeField(),
			   new InverseSquaredRadialSphere()); }
    };

    void getDirectionField(double result[], double y[],
			   double th, double ph) {
	double sinth = java.lang.Math.sin(th);
	double costh = java.lang.Math.cos(th);
	double sinph = java.lang.Math.sin(ph);
	double cosph = java.lang.Math.cos(ph);
	if (!showA) {
	    if (getPot) {
		result[0] = -.4*(y[0]*sinth*cosph + y[1]*sinth*sinph +
				 y[2]*costh);
		return;
	    }
	    result[0] = .0003*sinth*cosph;
	    result[1] = .0003*sinth*sinph;
	    result[2] = .0003*costh;
	} else {
	    // The A for this field is a linear rotation around
	    // an axis pointing in the direction of the field.
	    // First calculate the axis.
	    double axis[] = new double[3];
	    axis[0] = sinth*cosph;
	    axis[1] = sinth*sinph;
	    axis[2] = costh;
	    // now project the point we are looking at onto the axis
	    double d = dot(axis, y);
	    // subtract out this projection so we can get the vector
	    // from the point to the axis
	    double r[] = new double[3];
	    int i;
	    for (i = 0; i != 3; i++)
		r[i] = .0006*(y[i] - axis[i]*d);
	    // cross this vector with the axis vector to get the result
	    cross(result, axis, r);
	}
    }

#endif
#ifndef BUILD_V
    class MovingChargeField extends InverseSquaredRadial {
	String getName() { return "moving charge"; }
	void getField(double result[], double y[]) {
	    double rz = distance(y);
	    if (showA) {
		result[0] = result[1] = 0;
		result[2] = .0003/rz;
	    } else {
		double r = distance(y[0], y[1]);
		if (rz < chargeSize)
		    boundCheck = true;
		rotateParticle(result, y, .0001/(rz*rz*rz));
	    }
	}
	void render(Graphics g) {
	    drawCharge(g, 0, 0, 0, 1);
	    renderItems(g, 1);
	}
	void setup() {
	    setXZView();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return
	       BUILD_CASE_EMV(null, new FastChargeField(), null); }
    };
#endif
#ifdef BUILD_M
    class FastChargeField extends MovingChargeField {
	String getName() { return "fast charge"; }
	double getFieldStrength(double y[]) {
	    double rz = distance(y);
	    if (rz < chargeSize)
		boundCheck = true;
	    double r = distance(y[0], y[1]);
	    // sine of angle between particle vector and direction of motion
	    double sinth = r/rz;
	    double beta = (aux1Bar.getValue()+1)/102.;
	    // field = e(beta X R)(1-beta^2)/(R^3)(1-beta^2 sin^2 th)^(3/2)
            //       = C/(R^2)(1-beta^2 sin^2 th)^(3/2)
	    // We are ignoring C which affects the strength of the field and
	    // does not change its shape.  The field is in the direction
	    // of beta X R which means that it rotates the particle
	    // around the z axis.  XXX changed this, fix
	    double b = .001*(1-beta*beta)*beta/
		(rz*rz*java.lang.Math.pow(1-beta*beta*sinth*sinth, 1.5));
	    return b;
	}
	void setup() {
	    super.setup();
	    setupBar(0, "Speed/C", 60);
	}
	void render(Graphics g) {
	    // the charge is always going in the same direction,
	    // regardless of reverse
	    drawCharge(g, 0, 0, 0, reverse);
	    renderItems(g, 1);
	}
	void getField(double result[], double y[]) {
	    if (showA) {
		double rz = distance(y);
		double r = distance(y[0], y[1]);
		// sine of angle between particle vector and direction of motion
		double sinth = r/rz;
		double beta = (aux1Bar.getValue()+1)/102.;
		result[0] = result[1] = 0;
		result[2] = .003*beta/
		    (rz*java.lang.Math.pow(1-beta*beta*sinth*sinth, .5));
	    } else
		rotateParticle(result, y, getFieldStrength(y));
	}
	VecFunction createNext() { return new MovingChargeFieldDouble(); }
    };

    class MovingChargeFieldDouble extends InverseSquaredRadialDouble {
	String getName() { return "moving charge double"; }
	MovingChargeFieldDouble() { dir2 = 1; }
	int dir2;
	void getField(double result[], double y[]) {
	    result[0] = result[1] = result[2] = 0;
	    double sep = aux1Bar.getValue()/100.;
	    double rz1 = distance(y[0] - sep, y[1], y[2]);
	    double rz2 = distance(y[0] + sep, y[1], y[2]);
	    if (showA) {
		result[0] = result[1] = 0;
		result[2] = .0003*(1/rz1 + dir2/rz2);
	    } else {
		double r = distance(y[0] - sep, y[1]);
		if (rz1 < chargeSize)
		    boundCheck = true;
		rotateParticleAdd(result, y, .0001/(rz1*rz1*rz1),  sep, 0);
		if (rz2 < chargeSize)
		    boundCheck = true;
		r = distance(y[0] + sep, y[1]);
		rotateParticleAdd(result, y, dir2*.0001/(rz2*rz2*rz2),
				  -sep, 0);
	    }
	}
	void setup() {
	    setupBar(0, "Charge Separation", 30);
	    super.setup();
	}
	void render(Graphics g) {
	    double sep = aux1Bar.getValue()/100.;
	    drawCharge(g, +sep, 0, 0, 1);
	    drawCharge(g, -sep, 0, 0, dir2);
	    renderItems(g, 1);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new MovingChargeDipole(); }
    };
    class MovingChargeDipole extends MovingChargeFieldDouble {
	MovingChargeDipole() { dir2 = -1; }
	String getName() { return "moving charge dipole"; }
	VecFunction createNext() { return new CurrentLoopField(); }
    };
#endif
#ifndef BUILD_V
    class CurrentLoopField extends VecFunction {
	CurrentLoopField() { useColor = true; }
	Color colors[];
	boolean useColor;
	double size;
	String getName() { return "current loop"; }
	boolean useAdaptiveRungeKutta() { return false; }
	void setup() {
	    setXZView();
	    setupBar(0, "Loop Size", 40);
	}
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	}
	void getField(double result[], double y[]) {
	    getLoopField(result, y, 0, 0, 1, size);
	}
	void getLoopField(double result[], double y[], double xoff,
			  double zoff, int dir, double size) {
	    double xx = y[0]-xoff;
	    double yy = y[1];
	    double zz = y[2]-zoff;
	    int i;
	    result[0] = result[1] = result[2] = 0;
	    int ct = 8;
	    double q = .0006*dir/(size*ct);
	    double ang0 = java.lang.Math.atan2(y[1], y[0]);
	    for (i = 0; i != ct; i ++) {
		double ang = pi*2*i/ct;
		double jxx = size*java.lang.Math.cos(ang+ang0);
		double jyy = size*java.lang.Math.sin(ang+ang0);
		double lxx = -jyy*q;
		double lyy =  jxx*q;
		double rx = xx-jxx;
		double ry = yy-jyy;
		double rz = zz;
		double r = java.lang.Math.sqrt(rx*rx+ry*ry+rz*rz);
		if (!showA) {
		    double r3 = r*r*r;
		    if (r < .04 && useMagnetMove())
			boundCheck = true;

		    // dl x R = (lxx, lyy, 0) X R
		    //        = (lyy*rz, -lxx*rz, lxx*ry-lyy*rx)
		    // we are integrating by dl x Rhat/r^2, so we rewrite
		    // that as dl x R / r^3.
		    double cx = lyy*rz/r3;
		    double cy = -lxx*rz/r3;
		    double cz = (lxx*ry - lyy*rx)/r3;
		    result[0] += cx;
		    result[1] += cy;
		    result[2] += cz;
		} else {
		    // dl /r = lxx 
		    result[0] += 6*lxx/r;
		    result[1] += 6*lyy/r;
		}
	    }
	}
	boolean checkBounds(double y[], double oldY[]) {
	    if (!useMagnetMove())
		return false;
	    if ((y[2] > 0 && oldY[2] < 0) ||
		(y[2] < 0 && oldY[2] > 0)) {
		double r = java.lang.Math.sqrt(y[0]*y[0]+y[1]*y[1]);
		if (r < size)
		    return true;
	    }
	    return false;
	}

	void render(Graphics g) {
	    renderItems(g, 1);
	    renderLoop(g, 0, 0, 1, size);
	    renderItems(g, 0);
	}

	void renderLoop(Graphics g, double xoff, double zoff, int dir,
			double size) {
	    final int loopSegments = 72;
	    int i;
	    if (!useColor)
		g.setColor(darkYellow);
	    for (i = 0; i != loopSegments; i++) {
		double ang1 = pi*2*i/loopSegments;
		double ang2 = pi*2*(i+dir)/loopSegments;
		double jxx1 = size*java.lang.Math.cos(ang1) + xoff;
		double jyy1 = size*java.lang.Math.sin(ang1);
		double jxx2 = size*java.lang.Math.cos(ang2) + xoff;
		double jyy2 = size*java.lang.Math.sin(ang2);
		map3d(jxx1, jyy1, zoff, xpoints, ypoints, 0);
		map3d(jxx2, jyy2, zoff, xpoints, ypoints, 1);
		if (useColor)
		    g.setColor(getCurrentColor(i*dir));
		if (i == 0 && useColor)
		    drawCurrentArrow(g, xpoints[0], ypoints[0],
				     xpoints[1], ypoints[1]);
		else
		    g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	}

	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) != 0)
		return 1;
	    return 0;
	}
	boolean noSplitFieldVectors() { return false; }
	boolean nonGradient() { return true; }
	VecFunction createNext() { return
	     BUILD_CASE_EMV(null, new CurrentLoopsSideField(), null); }
    };
#endif
#ifdef BUILD_M
    class CurrentLoopsSideField extends CurrentLoopField {
	String getName() { return "loop pair"; }
	int dir2;
	double offx, offz, size;
	double tres1[], tres2[];
	CurrentLoopsSideField() {
	    tres1 = new double[3];
	    tres2 = new double[3];
	}
	void setup() {
	    setXZView();
	    setupBar(0, "Loop Size", 40);
	    setupBar(1, "Loop Separation", 10);
	    setupBar(2, "Offset", 0);
	}
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	    double sep = aux2Bar.getValue()/100.;
	    double sep2 = aux3Bar.getValue()/100.;
	    offx = sep*(1-size)+size; offz = sep2;
	    dir2 = 1;
	}
	void getField(double result[], double y[]) {
	    getLoopField(tres1, y, +offx, +offz, 1,    size);
	    getLoopField(tres2, y, -offx, -offz, dir2, size);
	    int i;
	    for (i = 0; i != 3; i++)
		result[i] = tres1[i] + tres2[i];
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    renderLoop(g, +offx, +offz,  1,   size);
	    renderLoop(g, -offx, -offz, dir2, size);
	    renderItems(g, 1);
	}
	boolean checkBounds(double y[], double oldY[]) {
	    if (!useMagnetMove())
		return false;
	    if ((y[2] > offz && oldY[2] < offz) ||
		(y[2] < offz && oldY[2] > offz)) {
		double x = y[0]-offx;
		double r = java.lang.Math.sqrt(x*x+y[1]*y[1]);
		if (r < size)
		    return true;
	    }
	    if ((y[2] > -offz && oldY[2] < -offz) ||
		(y[2] < -offz && oldY[2] > -offz)) {
		double x = y[0]+offx;
		double r = java.lang.Math.sqrt(x*x+y[1]*y[1]);
		if (r < size)
		    return true;
	    }
	    return false;
	}
	VecFunction createNext() { return new CurrentLoopsSideOppField(); }
    };
    class CurrentLoopsSideOppField extends CurrentLoopsSideField {
	String getName() { return "loop pair opposing"; }
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	    double sep = aux2Bar.getValue()/100.;
	    double sep2 = aux3Bar.getValue()/100.;
	    offx = sep*(1-size)+size; offz = sep2;
	    dir2 = -1;
	}
	VecFunction createNext() { return new CurrentLoopsStackedField(); }
    };
    class CurrentLoopsStackedField extends CurrentLoopsSideField {
	String getName() { return "loop pair stacked"; }
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	    double sep = (aux2Bar.getValue()+1)/100.;
	    double sep2 = aux3Bar.getValue()/100.;
	    offx = sep2; offz = sep;
	    dir2 = 1;
	}
	VecFunction createNext() { return new CurrentLoopsStackedOppField(); }
    };
    class CurrentLoopsStackedOppField extends CurrentLoopsSideField {
	String getName() { return "loop pair stacked, opp."; }
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	    double sep = (aux2Bar.getValue()+1)/100.;
	    double sep2 = aux3Bar.getValue()/100.;
	    offx = sep2; offz = sep;
	    dir2 = -1;
	}
	VecFunction createNext() { return new CurrentLoopsOpposingConcentric(); }
    };
    class CurrentLoopsOpposingConcentric extends CurrentLoopField {
	String getName() { return "concentric loops"; }
	int dir2;
	double tres1[], tres2[];
	double size2;
	CurrentLoopsOpposingConcentric() {
	    tres1 = new double[3];
	    tres2 = new double[3];
	}
	void setup() {
	    setXZView();
	    setupBar(0, "Outer Loop Size", 75);
	    setupBar(1, "Inner Loop Size", 50);
	}
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/101.;
	    size2 = size*(aux2Bar.getValue()+1)/101.;
	}
	void getField(double result[], double y[]) {
	    getLoopField(tres1, y, 0, 0,  1, size);
	    getLoopField(tres2, y, 0, 0, -1, size2);

	    // getLoopField's result is not proportional to size^2 (as
	    // it should be) but rather to size, because that makes
	    // the field of small loops too hard to see, and because
	    // we care more about the shape of the field than its
	    // absolute magnitude.  To correct for that, we have to
	    // multiply the smaller loop's field by size2/size to
	    // ensure that the field looks correct for two concentric
	    // loops with the same current.
	    double mult = size2/size;
	    int i;
	    for (i = 0; i != 3; i++)
		result[i] = tres1[i] + mult*tres2[i];
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    renderLoop(g, 0, 0,  1, size);
	    renderLoop(g, 0, 0, -1, size2);
	    renderItems(g, 1);
	}
	VecFunction createNext() { return new SolenoidField(); }
    };
    class SolenoidField extends VecFunction {
	String getName() { return "solenoid"; }
	boolean useRungeKutta() { return false; }
	double height, size;
	int turns;
	void setupFrame() {
	    size = (aux1Bar.getValue()+1)/100.;
	    turns = (aux3Bar.getValue()/4)+1;
	    height = (aux2Bar.getValue()+1)/25.;
	}
	void getField(double result[], double y[]) {
	    int i, j, n;
	    result[0] = result[1] = result[2] = 0;
	    int angct = 8; // was 10 aux3Bar.getValue()+1;
	    if (turns < 9)
		angct = 80/turns;
	    double ang0 = java.lang.Math.atan2(y[1], y[0]);
	    double zcoilstep = height/turns;
	    double zangstep = zcoilstep/angct;
	    double zbase = -height/2;
	    double q = .003/(turns*angct);
	    double lzz = q*zangstep;
	    if (ang0 < 0)
		ang0 += 2*pi;
	    if (ang0 < 0)
		System.out.print("-ang0?? " + ang0 + "\n");
	    ang0 %= zangstep;
	    zbase += zcoilstep*ang0/(2*pi);
	    for (i = 0; i != angct; i++) {
		double ang = pi*2*i/angct;
		double jxx = size*java.lang.Math.cos(ang+ang0);
		double jyy = size*java.lang.Math.sin(ang+ang0);
		double jzz = zbase+zangstep*i;
		double lxx = -jyy*q;
		double lyy =  jxx*q;
		double rx = y[0]-jxx;
		double ry = y[1]-jyy;
		double rx2ry2 = rx*rx+ry*ry;
		for (j = 0; j != turns; j++) {
		    double rz = y[2]-jzz;
		    double r = java.lang.Math.sqrt(rx2ry2+rz*rz);
		    if (!showA) {
			if (r < .04 && useMagnetMove())
			    boundCheck = true;
			
			// dl x R = (lxx, lyy, lzz) X R
			//        = (lyy*rz-lzz*ry, lzz*rx-lxx*rz,
			//           lxx*ry-lyy*rx)
			// we are integrating by dl x Rhat/r^2, so we rewrite
			// that as dl x R / r^3.
			double r3 = r*r*r;
			double cx = (lyy*rz-lzz*ry)/r3;
			double cy = (lzz*rx-lxx*rz)/r3;
			double cz = (lxx*ry-lyy*rx)/r3;
			result[0] += cx;
			result[1] += cy;
			result[2] += cz;
		    } else {
			result[0] += 6*lxx/r;
			result[1] += 6*lyy/r;
			result[2] += 6*lzz/r;
		    }
		    jzz += zcoilstep;
		}
	    }
	}
	void setup() {
	    setupBar(0, "Diameter", 40);
	    setupBar(1, "Height", 30);
	    setupBar(2, "# of Turns", 36);
	    setXZView();
	}
	int getViewPri(double cameraPos[], double x[]) {
	    return intersectCylinder(cameraPos, x[0], x[1], x[2], 2, false);
	}
	boolean checkBounds(double y[], double oldY[]) {
	    if (!useMagnetMove())
		return false;
	    double height2 = height*2;
	    double r  = java.lang.Math.sqrt(y[0]*y[0]+y[1]*y[1]);
	    double or = java.lang.Math.sqrt(oldY[0]*oldY[0]+oldY[1]*oldY[1]);
	    // going through walls?
	    if (y[2] < height2 && y[2] > -height2) {
		if ((r < size && or > size) ||
		    (or < size && r > size))
		    return true;
	    }
	    // passing through z=0 plane inside solenoid?
	    if ((y[2] > 0 && oldY[2] < 0) ||
		(y[2] < 0 && oldY[2] > 0)) {
		if (r < size)
		    return true;
	    }
	    return false;
	}
	void render(Graphics g) {
	    renderItems(g, 2);
	    // draw particles inside cylinder; XXX
	    renderItems(g, 1);
	    g.setColor(darkYellow);
	    int i, j;
	    int angct = 48;
	    if (turns < 10)
		angct = 480/turns;
	    double zcoilstep = height/turns;
	    double zangstep = zcoilstep/angct;
	    double zbase = -height/2;
	    for (i = 0; i != angct; i++) {
		double ang1 = pi*2*i/angct;
		double ang2 = pi*2*(i+1)/angct;
		double jxx1 = size*java.lang.Math.cos(ang1);
		double jyy1 = size*java.lang.Math.sin(ang1);
		double jxx2 = size*java.lang.Math.cos(ang2);
		double jyy2 = size*java.lang.Math.sin(ang2);
		double jzz1 = zbase + zangstep*i;
		for (j = 0; j != turns; j++) {
		    double jzz2 = jzz1 + zangstep;
		    map3d(jxx1, jyy1, jzz1, xpoints, ypoints, 0);
		    map3d(jxx2, jyy2, jzz2, xpoints, ypoints, 1);
		    g.setColor(getCurrentColor(j*angct+i));
		    if (i == 0 && j == turns/2)
			drawCurrentArrow(g, xpoints[0], ypoints[0],
					 xpoints[1], ypoints[1]);
		    else
			g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
		    jzz1 += zcoilstep;
		}
	    }
	    renderItems(g, 0);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new ToroidalSolenoidField(); }
    };
    class ToroidalSolenoidField extends VecFunction {
	ToroidalSolenoidField() { turnmult = 1; }
	String getName() { return "toroidal solenoid"; }
	boolean useRungeKutta() { return false; }
	double size1, size2, q;
	int turns, angct = 8;
	int turnmult;
	double costab1[], sintab1[];
	double costab2[][], sintab2[][];

	void setupFrame() {
	    size1 = aux1Bar.getValue()/100.;
	    size2 = aux2Bar.getValue()*size1/100.;
	    turns = aux3Bar.getValue()/3+6;
	    q = .0003/(angct*turns);
	    costab1 = new double[angct];
	    sintab1 = new double[angct];
	    costab2 = new double[angct][turns];
	    sintab2 = new double[angct][turns];
	    int i, j;
	    for (i = 0; i != angct; i++) {
		double ang = pi*2*i/angct;
		costab1[i] = java.lang.Math.cos(ang);
		sintab1[i] = java.lang.Math.sin(ang);
		for (j = 0; j != turns; j++) {
		    double ang2 = (pi*2*j+ang)/(turnmult*turns);
		    costab2[i][j] = java.lang.Math.cos(ang2);
		    sintab2[i][j] = java.lang.Math.sin(ang2);
		}
	    }
	}
	void finishFrame() {
	    costab1 = sintab1 = null;
	    costab2 = sintab2 = null;
	}

	void setup() {
	    setupBar(0, "Center Radius", 60);
	    setupBar(1, "Outer Radius", 80);
	    setupBar(2, "# of turns", 18);
	}

	void getField(double result[], double y[]) {
	    int i, j, n;
	    result[0] = result[1] = result[2] = 0;
	    for (i = 0; i != angct; i++) {
		double cosp = costab1[i];
		double sinp = sintab1[i];
		double jzz = size2*sinp;
		double lzz = q*turns*size2*cosp;
		double rz = y[2]-jzz;
		for (j = 0; j != turns; j++) {
		    double cosa = costab2[i][j];
		    double sina = sintab2[i][j];
		    double jxx = cosa*(size1+size2*cosp);
		    double jyy = sina*(size1+size2*cosp);
		    double lxx = q*
			(-(size1+size2*cosp)*sina - turns*size2*cosa*sinp);
		    double lyy = q*
			((size1+size2*cosp)*cosa - turns*size2*sina*sinp);
		    double rx = y[0]-jxx;
		    double ry = y[1]-jyy;
		    double r = distance(rx, ry, rz);
		    if (!showA) {
			double r3 = r*r*r;
			if (r < .04 && useMagnetMove())
			    boundCheck = true;

			// dl x R = (lxx, lyy, lzz) X R
			//        = (lyy*rz-lzz*ry, lzz*rx-lxx*rz,
			//           lxx*ry-lyy*rx)
			// we are integrating by dl x Rhat/r^2, so we rewrite
			// that as dl x R / r^3.
			double cx = (lyy*rz-lzz*ry)/r3;
			double cy = (lzz*rx-lxx*rz)/r3;
			double cz = (lxx*ry-lyy*rx)/r3;
			result[0] += cx;
			result[1] += cy;
			result[2] += cz;
		    } else {
			result[0] += 6*lxx/r;
			result[1] += 6*lyy/r;
			result[2] += 6*lzz/r;
		    }
		}
	    }
	}
	int getViewPri(double cameraPos[], double x[]) {
	    return intersectCylinder(cameraPos, x[0], x[1], x[2], 2, false);
	}
	void render(Graphics g) {
	    renderItems(g, 2);
	    // draw particles inside cylinder; XXX
	    renderItems(g, 1);
	    g.setColor(darkYellow);
	    int jzz, i;
	    int steps = turns*48;
	    for (i = 0; i != steps; i++) {
		getToroidPoint(xpoints, ypoints, size1, size2, turns, i, 0);
		getToroidPoint(xpoints, ypoints, size1, size2, turns, i+1, 1);
		g.setColor(getCurrentColor(i));
		if (i == 50)
		    drawArrow(g, null, xpoints[0], ypoints[0],
			      xpoints[1], ypoints[1], 7);
		else
		    g.drawLine(xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
	    }
	    renderItems(g, 0);
	}
	void getToroidPoint(int xpoints[], int ypoints[], double size1,
			    double size2, int turns, int i, int n) {
	    int angct = 48;
	    double ang = pi*2*(i % angct)/angct;
	    double cosp = java.lang.Math.cos(ang);
	    double sinp = java.lang.Math.sin(ang);
	    double ang2 = (pi*2*(i/angct)+ang)/(turns*turnmult);
	    double cosa = java.lang.Math.cos(ang2);
	    double sina = java.lang.Math.sin(ang2);
	    map3d(cosa*(size1+size2*cosp),
		  sina*(size1+size2*cosp),
		  size2*sinp,
		  xpoints, ypoints, n);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new HorseshoeElectromagnetField(); }
    };
    class HorseshoeElectromagnetField extends ToroidalSolenoidField {
	HorseshoeElectromagnetField() { turnmult = 2; }
	String getName() { return "horseshoe electromagnet"; }
	void setup() {
	    setupBar(0, "Center Radius", 40);
	    setupBar(1, "Outer Radius", 50);
	    setupBar(2, "# of turns", 18);
	    setXYView();
	}
	VecFunction createNext() { return new SquareLoopField(); }
    };
    class SquareLoopField extends VecFunction {
	String getName() { return "square loop"; }
	double lstart, lstop, size;

	void setup() {
	    setupBar(0, "Loop Size", 60);
	    setXZView();
	}

	void setupFrame() {
	    size = aux1Bar.getValue()/100.;
	    lstart = -size;
	    lstop  =  size;
	}

	void getField(double result[], double y[]) {
	    getLoopField(result, y, 0, 1);
	}

	void getLineField(double result[], double y[], double offo,
			  double offt, int lcoord, int ocoord, int tcoord,
			  int dir) {
	    double a1 = lstart-y[lcoord];
	    double a2 = lstop-y[lcoord];
	    double r = distance(y[ocoord]-offo, y[tcoord]-offt);
	    if (r < lineWidth && a1 <= 0 && a2 >= 0)
		boundCheck = true;
	    double y2 = r*r;
	    double a12 = a1*a1;
	    double a22 = a2*a2;
	    double a12y2 = java.lang.Math.sqrt(a12+y2);
	    double a22y2 = java.lang.Math.sqrt(a22+y2);
	    if (showA) {
		if (lcoord < ocoord)
		    dir = -dir;
		result[lcoord] +=
		    dir*.0003*java.lang.Math.log((a2+a22y2)/(a1+a12y2))/size;
		return;
	    }
	    double q = dir*.0001/size;
	    double fth =
		q* (-1/(a12+y2+a1*a12y2)+1/(a22+y2+a2*a22y2));
	    result[tcoord] += fth*(y[ocoord]-offo);
	    result[ocoord] -= fth*(y[tcoord]-offt);
	}

	void getLoopField(double result[], double y[], double zoff, int dir) {
	    result[0] = result[1] = result[2] = 0;
	    getLineField(result, y,  size, zoff, 0, 1, 2,  dir);
	    getLineField(result, y, -size, zoff, 0, 1, 2, -dir);
	    getLineField(result, y,  size, zoff, 1, 0, 2,  dir);
	    getLineField(result, y, -size, zoff, 1, 0, 2, -dir);
	}
	boolean checkBounds(double y[], double oldY[]) {
	    if (!useMagnetMove())
		return false;
	    if ((y[2] > 0 && oldY[2] < 0) ||
		(y[2] < 0 && oldY[2] > 0)) {
		if (y[0] < size  && y[1] < size &&
		    y[0] > -size && y[1] > -size)
		    return true;
	    }
	    return false;
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    g.setColor(darkYellow);
	    map3d(-size, -size, 0, xpoints, ypoints, 0);
	    map3d(+size, -size, 0, xpoints, ypoints, 1);
	    map3d(+size, +size, 0, xpoints, ypoints, 2);
	    map3d(-size, +size, 0, xpoints, ypoints, 3);
	    int i;
	    for (i = 0; i != 4; i++) {
		int j = (i + 1) & 3;
		drawCurrentLine(g, xpoints[i], ypoints[i],
				xpoints[j], ypoints[j], 8, i == 0, 1);
	    }
	    renderItems(g, 1);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) == 0)
		return 1;
	    return 0;
	}
	boolean noSplitFieldVectors() { return false; }
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new RectLoopField(); }
    };
    class RectLoopField extends SquareLoopField {
	String getName() { return "rectangular loop"; }
	double sizeX, sizeY;

	void setup() {
	    setupBar(0, "Loop Width", 60);
	    setupBar(1, "Loop Depth", 40);
	    setXZView();
	}

	void setupFrame() {
	    sizeX = aux1Bar.getValue()/100.+.01;
	    sizeY = aux2Bar.getValue()/100.+.01;
	}

	void getField(double result[], double y[]) {
	    result[0] = result[1] = result[2] = 0;
	    lstart = -sizeX;
	    lstop  =  sizeX;
	    size = sizeY;
	    getLineField(result, y,  sizeY, 0, 0, 1, 2,  1);
	    getLineField(result, y, -sizeY, 0, 0, 1, 2, -1);
	    lstart = -sizeY;
	    lstop  =  sizeY;
	    size = sizeX;
	    getLineField(result, y,  sizeX, 0, 1, 0, 2,  1);
	    getLineField(result, y, -sizeX, 0, 1, 0, 2, -1);
	}

	boolean checkBounds(double y[], double oldY[]) {
	    if (!useMagnetMove())
		return false;
	    if ((y[2] > 0 && oldY[2] < 0) ||
		(y[2] < 0 && oldY[2] > 0)) {
		if (y[0] < sizeX  && y[1] < sizeY &&
		    y[0] > -sizeX && y[1] > -sizeY)
		    return true;
	    }
	    return false;
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    g.setColor(darkYellow);
	    map3d(-sizeX, -sizeY, 0, xpoints, ypoints, 0);
	    map3d(+sizeX, -sizeY, 0, xpoints, ypoints, 1);
	    map3d(+sizeX, +sizeY, 0, xpoints, ypoints, 2);
	    map3d(-sizeX, +sizeY, 0, xpoints, ypoints, 3);
	    int i;
	    for (i = 0; i != 4; i++) {
		int j = (i + 1) & 3;
		drawCurrentLine(g, xpoints[i], ypoints[i],
				xpoints[j], ypoints[j], 8, i == 0, 1);
	    }
	    renderItems(g, 1);
	}
	VecFunction createNext() { return new CornerField(); }
    };
    class CornerField extends SquareLoopField {
	String getName() { return "corner"; }
	void setup() {
	    setXZView();
	    setupBar(0, "Offset", 50);
	}
	double offset;
	void setupFrame() {
	    size = 2;
	    offset = aux1Bar.getValue()/50.-1;
	    lstart = offset;
	    lstop = 10+offset;
	}

	void getField(double result[], double y[]) {
	    result[0] = result[1] = result[2] = 0;
	    getLineField(result, y, offset, 0, 0, 1, 2, -1);
	    getLineField(result, y, offset, 0, 1, 0, 2, -1);
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    g.setColor(darkYellow);
	    map3d(offset, offset, 0, xpoints, ypoints, 0);
	    map3d(1,      offset, 0, xpoints, ypoints, 1);
	    map3d(offset, 1,      0, xpoints, ypoints, 2);
	    drawCurrentLine(g,
			    xpoints[0], ypoints[0],
			    xpoints[1], ypoints[1], 8, true, 1);
	    drawCurrentLine(g,
			    xpoints[2], ypoints[2],
			    xpoints[0], ypoints[0], 8, false, 1);
	    renderItems(g, 1);
	}
	VecFunction createNext() { return new MagneticSphereB(); }
    };
    class MagneticSphereB extends VecFunction {
	String getName() { return "magnetic sphere"; }
	void getField(double result[], double y[]) {
	    double a = aux1Bar.getValue()/100.;
	    double r = distance(y);
	    if (r < a) {
		boundCheck = true;
		result[0] = result[1] = result[2] = 0;
		return;
	    }
	    double rz = distance(y[0], y[1]);
	    double costh = y[2]/r;
	    double sinth = rz/r;
	    double sinph = y[1]/rz;
	    double cosph = y[0]/rz;
	    if (!showA) {
		// rhat  = (sinth*cosph, sinth*sinph, costh)
		// thhat = (costh*cosph, costh*sinph, -sinth)
		double r3 = .003*a*a*a/(r*r*r);
		double eth = 2*sinth*r3;
		double er  =   costh*r3;
		result[0] = sinth*cosph*er + costh*cosph*eth;
		result[1] = sinth*sinph*er + costh*sinph*eth;
		result[2] = costh      *er - sinth*      eth;
	    } else {
		// phhat = (-sinph, cosph, 0)
		double aph = .003*a*a*a*sinth/(r*r);
		result[0] = -sinph*aph;
		result[1] =  cosph*aph;
		result[2] = 0;
	    }
	}
	void setup() {
	    setupBar(0, "Sphere Size", 50);
	    setXZView();
	}
	void render(Graphics g) {
	    double a = aux1Bar.getValue()/100.;
	    fillSphere(g, a, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    double a = aux1Bar.getValue()/100.;
	    return intersectSphere(cameraPos, x[0], x[1], x[2], a);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new MonopoleAttempt(); }
    };
    class MonopoleAttempt extends SquareLoopField {
	String getName() { return "monopole attempt"; }
	double tres[][], yflip[], rad, size;
	int count;
	MonopoleAttempt() {
	    tres = new double[8][3];
	    yflip = new double[3];
	}
	void setup() {
	    setXZView();
	    setupBar(0, "Loop Size", 40);
	    setupBar(1, "Separation", 10);
	    setupBar(2, "Loop Count", 100);
	    dispChooser.select(DISP_VECTORS);
	}
	void setupFrame() {
	    super.setupFrame();
	    size = (aux1Bar.getValue())/100.;
	    rad = aux2Bar.getValue()/100. + size;
	    count = (aux3Bar.getValue()*6)/101 + 1;
	}
	void drawLoop(Graphics g) {
	    int i;
	    for (i = 0; i != 4; i++) {
		int j = (i + 1) & 3;
		drawCurrentLine(g, xpoints[i], ypoints[i],
				xpoints[j], ypoints[j], 8, i == 0, 1);
	    }
	}
	void render(Graphics g) {
	    renderItems(g, 0);
	    g.setColor(darkYellow);
	    double size = aux1Bar.getValue()/100.;

	    int i;
	    int ct = count;
	    for (i = -1; i <= 1; i += 2) {
		if (--ct < 0)
		    break;
		map3d(-size,   -size,   rad*i, xpoints, ypoints, 0);
		map3d(+size*i, -size*i, rad*i, xpoints, ypoints, 1);
		map3d(+size,   +size,   rad*i, xpoints, ypoints, 2);
		map3d(-size*i, +size*i, rad*i, xpoints, ypoints, 3);
		drawLoop(g);
	    }

	    for (i = -1; i <= 1; i += 2) {
		if (--ct < 0)
		    break;
		map3d(-size,   rad*i, -size,   xpoints, ypoints, 0);
		map3d(-size*i, rad*i, +size*i, xpoints, ypoints, 1);
		map3d(+size,   rad*i, +size,   xpoints, ypoints, 2);
		map3d(+size*i, rad*i, -size*i, xpoints, ypoints, 3);
		drawLoop(g);
	    }

	    for (i = -1; i <= 1; i += 2) {
		if (--ct < 0)
		    break;
		map3d(rad*i, -size,   -size,   xpoints, ypoints, 0);
		map3d(rad*i, +size*i, -size*i, xpoints, ypoints, 1);
		map3d(rad*i, +size,   +size,   xpoints, ypoints, 2);
		map3d(rad*i, -size*i, +size*i, xpoints, ypoints, 3);
		drawLoop(g);
	    }

	    renderItems(g, 1);
	}
	void getField(double result[], double y[]) {
	    int i;
	    for (i = 0; i != 6; i++)
		tres[i][0] = tres[i][1] = tres[i][2] = 0;
	    getLoopField(tres[0], y, -rad, -1);
	    if (count > 1)
		getLoopField(tres[1], y,  rad,  1);
	    yflip[1] = y[0];
	    yflip[2] = y[1];
	    yflip[0] = y[2];
	    if (count > 2)
		getLoopField(tres[2], yflip, -rad, -1);
	    if (count > 3)
		getLoopField(tres[3], yflip,  rad,  1);
	    yflip[2] = y[0];
	    yflip[0] = y[1];
	    yflip[1] = y[2];
	    if (count > 4)
		getLoopField(tres[4], yflip, -rad, -1);
	    if (count > 5)
		getLoopField(tres[5], yflip,  rad,  1);
	    for (i = 0; i != 3; i++)
		result[i] = tres[0][i] + tres[1][i] +
		    tres[2][(i+1)%3] + tres[3][(i+1)%3] +
		    tres[4][(i+2)%3] + tres[5][(i+2)%3];
	}
	VecFunction createNext() { return null; }
    };
#endif
#ifdef BUILD_V
    class InverseSquaredRadialSphere extends VecFunction {
	String getName() { return "1/r^2 sphere"; }
	double getSize() { return (aux1Bar.getValue()+1)/110.; }
	void getField(double result[], double y[]) {
	    double r = distance(y);
	    if (r < .01)
		boundCheck = true;
	    double a = getSize();
	    if (getPot) {
		result[0] = .1*((r > a) ? -1/r : -3/(2*a)+r*r/(2*a*a*a));
		return;
	    }
	    if (r < a)
		r = a;
	    double alpha = .0003/(r*r*r);
	    result[0] = -y[0]*alpha;
	    result[1] = -y[1]*alpha;
	    result[2] = -y[2]*alpha;
	}
	void setup() {
	    setupBar(0, "Sphere Size", 70);
	}

	void render(Graphics g) {
	    renderSphere(g, getSize());
	}
	int getViewPri(double cameraPos[], double x[]) {
	    return intersectSphere(cameraPos, x[0], x[1], x[2], getSize());
	}
	boolean noSplitFieldVectors() { return false; }
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new ConstRadial(); }
    };
    class ConstRadial extends InverseSquaredRadial {
	String getName() { return "const radial"; }
	void getField(double result[], double y[]) {
	    double r = distance(y);
	    if (r < chargeSize)
		boundCheck = true;
	    double q = .0003/r;
	    if (getPot) {
		result[0] = r-1;
		return;
	    }
	    result[0] = -q*y[0];
	    result[1] = -q*y[1];
	    result[2] = -q*y[2];
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new LinearRadial(); }
    };

    class LinearRadial extends InverseSquaredRadial {
	String getName() { return "linear radial"; }
	void getField(double result[], double y[]) {
	    double r = distance(y);
	    if (r < chargeSize)
		boundCheck = true;
	    if (getPot) {
		result[0] = r*r-1;
		return;
	    }
	    double k = .0003;
	    result[0] = -y[0]*k;
	    result[1] = -y[1]*k;
	    result[2] = -y[2]*k;
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new ConstantToZAxis(); }
    };

    class ConstantToZAxis extends InverseRadial {
	String getName() { return "constant to z axis"; }
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    if (r < lineWidth)
		boundCheck = true;
	    if (getPot) {
		result[0] = r-1;
		return;
	    }
	    double q = .0003/r;
	    result[0] = -y[0]*q;
	    result[1] = -y[1]*q;
	    result[2] = 0;
	}
	void setup() {
	    setXYView();
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new ConstantToXYPlane(); }
    };
    class ConstantToXYPlane extends VecFunction {
	String getName() { return "constant to xy plane"; }
	void getField(double result[], double y[]) {
	    double alpha = .0003;
	    if (y[2] > -.01 && y[2] < .01)
		boundCheck = true;
	    if (getPot) {
		result[0] = java.lang.Math.abs(y[2])-1;
		return;
	    }
	    result[0] = 0;
	    result[1] = 0;
	    result[2] = (y[2] < 0) ? alpha : -alpha;
	}
	void setup() {
	    setXZView();
	}
	void render(Graphics g) {
	    renderItems(g, 1);
	    drawPlane(g, 1, 1, 0);
	    renderItems(g, 0);
	}
	int getViewPri(double cameraPos[], double x[]) {
	    if (intersectZPlane(cameraPos, 0, x[0], x[1], x[2]) == 0)
		return 0;
	    return 1;
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new LinearToZAxis(); }
    };
    class LinearToZAxis extends InverseRadial {
	String getName() { return "linear to z axis"; }
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    if (r < lineWidth)
		boundCheck = true;
	    if (getPot) {
		result[0] = r*r-1;
		return;
	    }
	    double q = .0003;
	    result[0] = -y[0]*q;
	    result[1] = -y[1]*q;
	    result[2] = 0;
	}
	void setup() {
	    setXYView();
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new LinearToXYPlane(); }
    };
    class LinearToXYPlane extends ConstantToXYPlane {
	String getName() { return "linear to xy plane"; }
	void getField(double result[], double y[]) {
	    if (getPot) {
		result[0] = y[2]*y[2]-1;
		return;
	    }
	    if (y[2] > -.01 && y[2] < .01)
		boundCheck = true;
	    double alpha = .0003;
	    result[0] = 0;
	    result[1] = 0;
	    result[2] = -alpha*y[2];
	}
	void setup() {
	    setXYView();
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new LinearToYZXZPlane(); }
    };
    class LinearToYZXZPlane extends VecFunction {
	String getName() { return "linear to yz, xz planes"; }
	void getField(double result[], double y[]) {
	    if (y[0] > -.01 && y[0] < .01)
		boundCheck = true;
	    if (y[1] > -.01 && y[1] < .01)
		boundCheck = true;
	    double alpha = .0003;
	    double r = java.lang.Math.sqrt((aux1Bar.getValue()+1)/51.);
	    if (getPot) {
		result[0] = (y[0]*y[0]*r+y[1]*y[1]/r)-1;
		return;
	    }
	    result[0] = -alpha*r*y[0];
	    result[1] = -alpha/r*y[1];
	    result[2] = 0;
	}
	void setup() {
	    setXYView();
	    setupBar(0, "X/Y Ratio", 50);
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new LinearToYZXZXYPlane(); }
    };
    class LinearToYZXZXYPlane extends VecFunction {
	String getName() { return "linear to yz, xz, xy planes"; }
	void getField(double result[], double y[]) {
	    if (y[2] > -.01 && y[2] < .01)
		boundCheck = true;
	    double alpha = .0003;
	    double r1 = (aux1Bar.getValue()+1)/51.;
	    double r2 = (aux2Bar.getValue()+1)/51.;
	    if (getPot) {
		result[0] = (y[0]*y[0]*r1+y[1]*y[1]+y[2]*y[2]/r2)-1;
		return;
	    }
	    result[0] = -alpha*r1*y[0];
	    result[1] = -alpha   *y[1];
	    result[2] = -alpha/r2*y[2];
	}
	void setup() {
	    setXYView();
	    setupBar(0, "X/Y Ratio", 50);
	    setupBar(1, "Y/Z Ratio", 50);
	}
	boolean checkBoundsWithForce() { return false; }
	VecFunction createNext() { return new InverseToXYPlane(); }
    };
    class InverseToXYPlane extends ConstantToXYPlane {
	String getName() { return "inverse to xy plane"; }
	void getField(double result[], double y[]) {
	    if (y[2] > -.01 && y[2] < .01)
		boundCheck = true;
	    double alpha = .0003;
	    double zz = y[2];
	    if (getPot) {
		result[0] = -.01/(zz*zz);
		return;
	    }
	    if (zz == 0)
		zz = .00001;
	    result[0] = 0;
	    result[1] = 0;
	    result[2] = -alpha/zz;
	}
	void setup() {
	    setXZView();
	}
	VecFunction createNext() { return new InverseSquareRotational(); }
    };
    class InverseSquareRotational extends InverseRadial {
	String getName() { return "1/r^2 rotational"; }
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    if (r < lineWidth*2)
		boundCheck = true;
	    rotateParticle(result, y, .0001/(r*r*r));
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new LinearRotational(); }
    };
    class LinearRotational extends InverseRadial {
	String getName() { return "linear rotational"; }
	void setup() {
	    setXYViewExact();
	}
	void getField(double result[], double y[]) {
	    double q = .0003;
	    result[0] = -q*y[1];
	    result[1] =  q*y[0];
	    result[2] = 0;
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new LinearRotationalA(); }
    };
    class LinearRotationalA extends InverseRadial {
	String getName() { return "fz=-(x^2+y^2)"; }
	void setup() {
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = .0003;
	    result[0] = result[1] = 0;
	    result[2] = -q*(y[0]*y[0]+y[1]*y[1]);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new ConstantRotational(); }
    };

    class ConstantRotational extends InverseRadial {
	String getName() { return "constant rotational"; }
	void setup() {
	    setXYViewExact();
	}
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    rotateParticle(result, y, .0003/r);
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new ConstantRotationalA(); }
    };

    class ConstantRotationalA extends InverseRadial {
	String getName() { return "(0,0,-r)"; }
	void setup() {
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double r = distance(y[0], y[1]);
	    result[0] = result[1] = 0;
	    result[2] = -.0006*r;
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new Helical(); }
    };

    class Helical extends InverseRadial {
	String getName() { return "helical"; }
	void setup() {
	    setXZView();
	    setupBar(0, "Z Speed", 30);
	}
	void getField(double result[], double y[]) {
	    double q= .0003;
	    result[0] = -q*y[1];
	    result[1] =  q*y[0];
	    result[2] =  .00001*aux1Bar.getValue();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new FxEqualsYField(); }
    };

    class FxEqualsYField extends VecFunction {
	String getName() { return "fx=y"; }
	void getField(double result[], double y[]) {
	    result[2] = result[1] = 0;
	    result[0] = y[1] * .0006;
	}
	void setup() {
	    setXYView();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new FxEqualsY2(); }
    };

    class FxEqualsY2 extends VecFunction {
	String getName() { return "fx=y2"; }
	void getField(double result[], double y[]) {
	    result[2] = result[1] = 0;
	    result[0] = y[1]*y[1] * .001;
	}
	void setup() {
	    setXYView();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new LinearZRotational(); }
    };

    class LinearZRotational extends VecFunction {
	String getName() { return "(-yz,xz,0)"; }
	void setup() {
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = .001*y[2];
	    result[0] = -q*y[1];
	    result[1] =  q*y[0];
	    result[2] = 0;
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new YzXz0Field(); }
    };

    class YzXz0Field extends VecFunction {
	String getName() { return "(yz,xz,0)"; }
	void setup() {
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = .0006*y[2];
	    result[0] = q*y[1];
	    result[1] = q*y[0];
	    result[2] = 0;
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new XY_2ZField(); }
    };

    class XY_2ZField extends VecFunction {
	String getName() { return "(-x,-y,2z)"; }
	void setup() {
	    setXZView();
	}
	void getField(double result[], double y[]) {
	    double q = .0006;
	    result[0] = q*y[0];
	    result[1] = q*y[1];
	    result[2] = -2*q*y[2];
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new XY0Field(); }
    };

    class XY0Field extends VecFunction {
	String getName() { return "(-x,y,0)"; }
	void setup() {
	    setXYView();
	}
	void getField(double result[], double y[]) {
	    double q = .0006;
	    result[0] = -q*y[0];
	    result[1] =  q*y[1];
	    result[2] = 0;
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new RotationalExpansion(); }
    };

    class RotationalExpansion extends VecFunction {
	String getName() { return "(x-y,x+y,0)"; }
	void getField(double result[], double y[]) {
	    double q = .0003;
	    result[0] = q*(y[0]-y[1]);
	    result[1] = q*(y[0]+y[1]);
	    result[2] = 0;
	}
	void setup() {
	    setXYView();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new RotationalExpansion3D(); }
    };
    class RotationalExpansion3D extends VecFunction {
	String getName() { return "(x-y,x+y,2z)"; }
	void getField(double result[], double y[]) {
	    double q = .0003;
	    result[0] = q*(y[0]-y[1]);
	    result[1] = q*(y[0]+y[1]);
	    result[2] = q*(y[2]*2);
	}
	void setup() {
	    setXYView();
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return new RosslerAttractor(); }
    };
    class RosslerAttractor extends VecFunction {
	String getName() { return "Rossler attractor"; }
	void getField(double result[], double y[]) {
	    int scale = aux2Bar.getValue()*2+20;
	    double xx = y[0] * 24;
	    double yy = y[1] * 24;
	    double zz = (y[2]+.75) * scale;
	    double k = .00002;
	    double c = aux1Bar.getValue()*.1;
	    result[0] = -(yy+zz)*k;
	    result[1] = k*(xx+.2*yy);
	    result[2] = k*(.2+xx*zz-c*zz);
	}
	void setup() {
	    setXZView();
	    setupBar(0, "c", 80);
	    setupBar(1, "Z Scale", 36);
	    strengthBar.setValue(75);
	}
	boolean nonGradient() { return true; }
	boolean redistribute() { return false; }
	VecFunction createNext() { return new LorenzAttractor(); }
    };
    class LorenzAttractor extends VecFunction {
	String getName() { return "Lorenz attractor"; }
	void setup() {
	    setXZView();
	    setupBar(0, "Scale", 24);
	}
	void getField(double result[], double y[]) {
	    int scale = aux1Bar.getValue()/2 + 23;
	    double xx = y[0] * scale;
	    double yy = y[1] * scale;
	    double zz = y[2] * scale + scale;
	    double k = .00002;
	    result[0] = (-10*xx+10*yy)*k;
	    result[1] = k*(28*xx-yy-xx*zz);
	    result[2] = k*(-(8./3.)*zz+xx*yy);
	}
	boolean nonGradient() { return true; }
	boolean redistribute() { return false; }
	VecFunction createNext() { return new UserDefinedPotential(); }
    };
    class UserDefinedPotential extends VecFunction {
	Expr expr;
	double y0[];
	String getName() { return "user-defined potential"; }
	void setup() {
	    setXZView();
	    textFields[0].setText("x*x-z*z");
	    textFields[0].show();
	    textFieldLabel.setText("Potential Function");
	    textFieldLabel.show();
	    actionPerformed();
	    y0 = new double[3];
	}
	void actionPerformed() {
	    parseError = false;
	    ExprParser ep = new ExprParser(textFields[0].getText());
	    expr = ep.parseExpression();
	    if (ep.gotError())
		parseError = true;
	}
	void getField(double result[], double y[]) {
	    double k = .00001;
	    int i;
	    for (i = 0; i != 3; i++)
		y0[i] = y[i];
	    double pot0 = expr.eval(y0);
	    if (getPot) {
		result[0] = pot0*.01;
		return;
	    }

	    y0[0] += k;
	    result[0] = pot0-expr.eval(y0);
	    y0[0] = y[0];

	    y0[1] += k;
	    result[1] = pot0-expr.eval(y0);
	    y0[1] = y[1];
	    
	    y0[2] += k;
	    result[2] = pot0-expr.eval(y0);
	    for (i = 0; i != 3; i++)
		if (!(result[i] > -10 && result[i] < 10))
		    boundCheck = true;
	}
	VecFunction createNext() { return new UserDefinedFunction(); }
    };
    class UserDefinedFunction extends VecFunction {
	Expr exprs[];
	String getName() { return "user-defined field"; }
	void setup() {
	    setXZView();
	    exprs = new Expr[3];
	    textFields[0].setText("x");
	    textFields[1].setText("y");
	    textFields[2].setText("z");
	    textFieldLabel.setText("Field Functions");
	    textFieldLabel.show();
	    int i;
	    for (i = 0; i != 3; i++)
		textFields[i].show();
	    actionPerformed();
	}
	void actionPerformed() {
	    int i;
	    parseError = false;
	    for (i = 0; i != 3; i++) {
		ExprParser ep = new ExprParser(textFields[i].getText());
		exprs[i] = ep.parseExpression();
		if (ep.gotError())
		    parseError = true;
	    }
	}
	void getField(double result[], double y[]) {
	    double k = .0002;
	    int i;
	    for (i = 0; i != 3; i++) {
		result[i] = k*exprs[i].eval(y);
		if (!(result[i] > -10 && result[i] < 10))
		    boundCheck = true;
	    }
	}
	boolean nonGradient() { return true; }
	VecFunction createNext() { return null; }
    };
#endif

    class DrawData {
	public Graphics g;
	public double mult;
	public double field[], vv[];
    };

    class Particle {
	public double pos[];
	public double vel[];
	public int viewPri;
	public double lifetime;
	public double phi, theta, phiv, thetav;
	public double stepsize;
	Particle() {
	    pos = new double[3]; vel = new double[3];
	    stepsize = 1;
	}
    };

    class FieldVector {
	public int sx1, sy1, sx2, sy2;
	public double p1[], p2[];
	public int col;
	public int viewPri;
    };

#ifdef BUILD_E    
    // this is a complex number class that avoids creating new objects
    // whenever possible.  Takes a little more effort to use, but it is
    // much faster because we don't have to do constant garbage
    // collection.
    class Complex {
	public double a, b;
	Complex() { a = b = 0; }
	void set(double aa, double bb) { a = aa; b = bb; }
	void set(Complex c) { set(c.a, c.b); }
	void add(double r) { a += r; }
	void add(double r, double i) { a += r; b += i; }
	void square() { set(a*a-b*b, 2*a*b); }
	void mult(double c, double d) { set(a*c-b*d, a*d+b*c); }
	void mult(double c) { a *= c; b *= c; }
	void mult(Complex c) { mult(c.a, c.b); }
	void recip() {
	    double n = a*a+b*b;
	    set(a/n, -b/n);
	}
	void pow(double p) {
	    double arg = java.lang.Math.atan2(b, a);
	    arg *= p;
	    double abs = java.lang.Math.pow(a*a+b*b, p*.5);
	    set(abs*java.lang.Math.cos(arg),
		abs*java.lang.Math.sin(arg));
	}
	void sin() {
	    set(cosh(b)*java.lang.Math.sin(a),
		java.lang.Math.cos(a)*sinh(b));
	}
	void cos() {
	    set(cosh(b)*java.lang.Math.cos(a),
		java.lang.Math.sin(a)*sinh(b));
	}
	void log() {
	    set(java.lang.Math.log(a*a+b*b),
		java.lang.Math.atan2(b, a));
	}
	void arcsin() {
	    Complex z2 = new Complex();
	    z2.set(a, b);
	    z2.square();
	    z2.mult(-1);
	    z2.add(1);
	    z2.pow(.5);
	    mult(0, 1);
	    add(z2.a, z2.b);
	    log();
	    mult(0, -1);
	}
    };

    double cosh(double a) {
	return .5*(java.lang.Math.exp(a)+java.lang.Math.exp(-a));
    }
    double sinh(double a) {
	return .5*(java.lang.Math.exp(a)-java.lang.Math.exp(-a));
    }
#endif
};

#ifdef BUILD_V
class ExprState {
    public double x, y, z;
}

class Expr {
    Expr(Expr e1, Expr e2, int v) {
	left = e1;
	right = e2;
	type = v;
    }
    Expr(int v, double vv) {
	type = v;
	value = vv;
    }
    Expr(int v) {
	type = v;
    }
    double eval(double es[]) {
	switch (type) {
	case E_ADD: return left.eval(es)+right.eval(es);
	case E_SUB: return left.eval(es)-right.eval(es);
	case E_MUL: return left.eval(es)*right.eval(es);
	case E_DIV: return left.eval(es)/right.eval(es);
	case E_POW: return java.lang.Math.pow(left.eval(es), right.eval(es));
	case E_UMINUS: return -left.eval(es);
	case E_VAL: return value;
	case E_X:   return es[0]*10;
	case E_Y:   return es[1]*10;
	case E_Z:   return es[2]*10;
	case E_R:   return java.lang.Math.sqrt(
			      es[0]*es[0]+es[1]*es[1]+es[2]*es[2])*10;
	case E_SIN: return java.lang.Math.sin(left.eval(es));
	case E_COS: return java.lang.Math.cos(left.eval(es));
	case E_ABS: return java.lang.Math.abs(left.eval(es));
	case E_EXP: return java.lang.Math.exp(left.eval(es));
	case E_LOG: return java.lang.Math.log(left.eval(es));
	case E_SQRT: return java.lang.Math.sqrt(left.eval(es));
	case E_TAN: return java.lang.Math.tan(left.eval(es));
	default: System.out.print("unknown\n");
	}
	return 0;
    }
    Expr left, right;
    double value;
    int type;
    static final int E_ADD = 1;
    static final int E_SUB = 2;
    static final int E_X = 3;
    static final int E_Y = 4;
    static final int E_Z = 5;
    static final int E_VAL = 6;
    static final int E_MUL = 7;
    static final int E_DIV = 8;
    static final int E_POW = 9;
    static final int E_UMINUS = 10;
    static final int E_SIN = 11;
    static final int E_COS = 12;
    static final int E_ABS = 13;
    static final int E_EXP = 14;
    static final int E_LOG = 15;
    static final int E_SQRT = 16;
    static final int E_TAN = 17;
    static final int E_R = 18;
};

class ExprParser {
    String text;
    String token;
    int pos;
    int tlen;
    boolean err;

    void getToken() {
	while (pos < tlen && text.charAt(pos) == ' ')
	    pos++;
	if (pos == tlen) {
	    token = "";
	    return;
	}
	int i = pos;
	int c = text.charAt(i);
	if ((c >= '0' && c <= '9') || c == '.') {
	    for (i = pos; i != tlen; i++) {
		if (!((text.charAt(i) >= '0' && text.charAt(i) <= '9') ||
		      text.charAt(i) == '.'))
		    break;
	    }
	} else if (c >= 'a' && c <= 'z') {
	    for (i = pos; i != tlen; i++) {
		if (!(text.charAt(i) >= 'a' && text.charAt(i) <= 'z'))
		    break;
	    }
	} else {
	    i++;
	}
	token = text.substring(pos, i);
	pos = i;
    }

    boolean skip(String s) {
	if (token.compareTo(s) != 0)
	    return false;
	getToken();
	return true;
    }

    void skipOrError(String s) {
	if (!skip(s))
	    err = true;
    }

    Expr parseExpression() {
	if (token.length() == 0)
	    return new Expr(Expr.E_VAL, 0.);
	Expr e = parse();
	if (token.length() > 0)
	    err = true;
	return e;
    }

    Expr parse() {
	Expr e = parseMult();
	while (true) {
	    if (skip("+"))
		e = new Expr(e, parseMult(), Expr.E_ADD);
	    else if (skip("-"))
		e = new Expr(e, parseMult(), Expr.E_SUB);
	    else
		break;
	}
	return e;
    }

    Expr parseMult() {
	Expr e = parseUminus();
	while (true) {
	    if (skip("*"))
		e = new Expr(e, parseUminus(), Expr.E_MUL);
	    else if (skip("/"))
		e = new Expr(e, parseUminus(), Expr.E_DIV);
	    else
		break;
	}
	return e;
    }

    Expr parseUminus() {
	skip("+");
	if (skip("-"))
	    return new Expr(parsePow(), null, Expr.E_UMINUS);
	return parsePow();
    }

    Expr parsePow() {
	Expr e = parseTerm();
	while (true) {
	    if (skip("^"))
		e = new Expr(e, parseTerm(), Expr.E_POW);
	    else
		break;
	}
	return e;
    }

    Expr parseFunc(int t) {
	skipOrError("(");
	Expr e = parse();
	skipOrError(")");
	return new Expr(e, null, t);
    }

    Expr parseTerm() {
	if (skip("(")) {
	    Expr e = parse();
	    skipOrError(")");
	    return e;
	}
	if (skip("x"))
	    return new Expr(Expr.E_X);
	if (skip("y"))
	    return new Expr(Expr.E_Y);
	if (skip("z"))
	    return new Expr(Expr.E_Z);
	if (skip("r"))
	    return new Expr(Expr.E_R);
	if (skip("pi"))
	    return new Expr(Expr.E_VAL, 3.14159265358979323846);
	if (skip("e"))
	    return new Expr(Expr.E_VAL, 2.7182818284590452354);
	if (skip("sin"))
	    return parseFunc(Expr.E_SIN);
	if (skip("cos"))
	    return parseFunc(Expr.E_COS);
	if (skip("abs"))
	    return parseFunc(Expr.E_ABS);
	if (skip("exp"))
	    return parseFunc(Expr.E_EXP);
	if (skip("log"))
	    return parseFunc(Expr.E_LOG);
	if (skip("sqrt"))
	    return parseFunc(Expr.E_SQRT);
	if (skip("tan"))
	    return parseFunc(Expr.E_TAN);
	try {
	    Expr e = new Expr(Expr.E_VAL, Double.valueOf(token).doubleValue());
	    getToken();
	    return e;
	} catch (Exception e) {
	    err = true;
	    System.out.print("unrecognized token: " + token + "\n");
	    return new Expr(Expr.E_VAL, 0);
	}
    }

    ExprParser(String s) {
	text = s;
	tlen = text.length();
	pos = 0;
	err = false;
	getToken();
    }
    boolean gotError() { return err; }
};
#endif
