// EMWave2.java (c) 2002 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.awt.image.*;
import java.applet.Applet;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.util.Random;
import java.util.Arrays;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.Math;
import java.awt.event.*;

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

class EMWave2Layout implements LayoutManager {
    public EMWave2Layout() {}
    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) {
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw* 2/3;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	int barwidth = targetw - cw;
	cw += insets.left;
	int i;
	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)
		    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 EMWave2 extends Applet implements ComponentListener {
    static EMWave2Frame 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 EMWave2Frame(null);
        ogf.init();
    }

    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new EMWave2Frame(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 EMWave2Frame extends Frame
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseMotionListener, MouseListener, ItemListener {
    
    Thread engine = null;

    Dimension winSize;
    Image dbimage;
    
    Random random;
    int gridSizeX;
    int gridSizeY;
    int gridSizeXY;
    int windowWidth = 50;
    int windowHeight = 50;
    int windowOffsetX = 0;
    int windowOffsetY = 0;
    public static final int sourceRadius = 7;
    public static final double freqMult = .0233333/2;
    
    public String getAppletInfo() {
	return "EMWave2 by Paul Falstad";
    }

    Button clearButton;
    Button ClearAllButton;
    Checkbox stoppedCheck;
    Choice modeChooser;
    Choice viewChooser;
    Choice sourceChooser;
    Choice setupChooser;
    Vector setupList;
    Setup setup;
    Scrollbar speedBar;
    Scrollbar forceBar;
    Scrollbar resBar;
    Scrollbar brightnessBar;
    Scrollbar lineDensityBar;
    Scrollbar auxBar;
    Scrollbar adjustBar;
    Label auxLabel;
    Label adjustLabel;
    double forceTimeZero;
    double sourceMult;
    static final double pi = 3.14159265358979323846;
    OscElement grid[];
    int gw;
    OscSource sources[];
    static final int MODE_PERF_CONDUCTOR = 0;
    static final int MODE_GOOD_CONDUCTOR = 1;
    static final int MODE_FAIR_CONDUCTOR = 2;
    static final int MODE_J_POS = 3;
    static final int MODE_J_NEG = 4;
    static final int MODE_FERROMAG = 5;
    static final int MODE_DIAMAG = 6;
    static final int MODE_MEDIUM = 7;
    static final int MODE_M_DOWN = 8;
    static final int MODE_M_UP = 9;
    static final int MODE_M_LEFT = 10;
    static final int MODE_M_RIGHT = 11;
    static final int MODE_RESONANT = 12;
    static final int MODE_CLEAR = 13;
    static final int MODE_ADJUST = 14; // same as next one
    static final int MODE_ADJ_CONDUCT = 14;
    static final int MODE_ADJ_PERM = 15;
    static final int MODE_ADJ_J = 16;
    static final int MODE_ADJ_MEDIUM = 17;
    static final int MODE_ADJ_MAG_DIR = 18;
    static final int MODE_ADJ_MAG_STR = 19;
    static final int VIEW_E = 0;
    static final int VIEW_B = 1;
    static final int VIEW_B_LINES = 2;
    static final int VIEW_B_STRENGTH = 3;
    static final int VIEW_J = 4;
    static final int VIEW_E_B = 5;
    static final int VIEW_E_B_LINES = 6;
    static final int VIEW_E_B_J = 7;
    static final int VIEW_E_B_LINES_J = 8;
    static final int VIEW_H = 9;
    static final int VIEW_M = 10;
    static final int VIEW_TYPE = 11;
    static final int VIEW_A = 12;
    static final int VIEW_POYNTING = 13;
    static final int VIEW_ENERGY = 14;
    static final int VIEW_POYNTING_ENERGY = 15;
    static final int VIEW_FORCE = 16;
    static final int VIEW_EFF_CUR = 17;
    static final int VIEW_MAG_CHARGE = 18;
    static final int VIEW_CURL_E = 19;
    static final int VIEW_BX = 20;
    static final int VIEW_BY = 21;
    static final int VIEW_HX = 22;
    static final int VIEW_HY = 23;
    static final int VIEW_NONE = -1;
    static final int TYPE_CONDUCTOR = 1;
    static final int TYPE_DIAMAGNET = 2;
    static final int TYPE_FERROMAGNET = 3;
    static final int TYPE_MAGNET = 4;
    static final int TYPE_CURRENT = 5;
    static final int TYPE_MEDIUM = 6;
    static final int TYPE_NONE = 0;
    // this fudge factor was found by trial and error
    static final double mhmult = 12;
    int dragX, dragY;
    int selectedSource;
    int forceBarValue;
    boolean dragging;
    boolean dragClear;
    boolean dragSet;
    double t;
    int pause;
    MemoryImageSource imageSource;
    int pixels[];
    int sourceCount = -1;
    boolean sourcePlane = false;
    int sourceFreqCount = -1;
    int sourceWaveform = SWF_SIN;
    int auxFunction;
    int adjustSelectX1, adjustSelectY1, adjustSelectX2, adjustSelectY2;
    static final int mediumMax = 191;
    static final double mediumMaxIndex = .5;
    static final int SWF_SIN = 0;
    static final int SWF_PACKET = 1;
    static final int AUX_NONE = 0;
    static final int AUX_PHASE = 1;
    static final int AUX_FREQ = 2;
    static final int AUX_SPEED = 3;
    static final int SRC_NONE = 0;
    static final int SRC_1S1F = 1;
    static final int SRC_2S1F = 3;
    static final int SRC_2S2F = 4;
    static final int SRC_4S1F = 6;
    static final int SRC_1S1F_PACKET = 7;
    static final int SRC_1S1F_PLANE = 8;
    static final int SRC_2S1F_PLANE = 10;
    static final int SRC_1S1F_PLANE_PACKET = 12;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    EMWave2Canvas cv;
    EMWave2 applet;

    EMWave2Frame(EMWave2 a) {
	super("TM Electrodynamics Applet v1.4b");
	applet = a;
    }

    boolean useBufferedImage = false;
    
    public void init() {
        String jv = System.getProperty("java.class.version");
        double jvf = new Double(jv).doubleValue();
        if (jvf >= 48)
	    useBufferedImage = true;
	
	setupList = new Vector();
	Setup s = new SingleSourceSetup();
	int i = 0;
	while (s != null) {
	    setupList.addElement(s);
	    s = s.createNext();
	    if (i++ > 300) {
		System.out.print("setup loop?\n");
		return;
	    }
	}
	String os = System.getProperty("os.name");

	sources = new OscSource[4];
	setLayout(new EMWave2Layout());
	cv = new EMWave2Canvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	setupChooser = new Choice();
	for (i = 0; i != setupList.size(); i++)
	    setupChooser.add("Setup: " +
			     ((Setup) setupList.elementAt(i)).getName());
	setup = (Setup) setupList.elementAt(0);
	setupChooser.addItemListener(this);
	add(setupChooser);

	sourceChooser = new Choice();
	sourceChooser.add("No Sources");
	sourceChooser.add("1 Src, 1 Freq");
	sourceChooser.add("1 Src, 2 Freq");
	sourceChooser.add("2 Src, 1 Freq");
	sourceChooser.add("2 Src, 2 Freq");
	sourceChooser.add("3 Src, 1 Freq");
	sourceChooser.add("4 Src, 1 Freq");
	sourceChooser.add("1 Src, 1 Freq (Packet)");
	sourceChooser.add("1 Plane Src, 1 Freq");
	sourceChooser.add("1 Plane Src, 2 Freq");
	sourceChooser.add("2 Plane Src, 1 Freq");
	sourceChooser.add("2 Plane Src, 2 Freq");
	sourceChooser.add("1 Plane 1 Freq (Packet)");
	sourceChooser.select(SRC_1S1F);
	sourceChooser.addItemListener(this);
	add(sourceChooser);

	modeChooser = new Choice();
	modeChooser.add("Mouse = Add Perf. Conductor");
	modeChooser.add("Mouse = Add Good Conductor");
	modeChooser.add("Mouse = Add Fair Conductor");
	modeChooser.add("Mouse = Add Current (+)");
	modeChooser.add("Mouse = Add Current (-)");
	modeChooser.add("Mouse = Add Ferromagnet");
	modeChooser.add("Mouse = Add Diamagnet");
	modeChooser.add("Mouse = Add Dielectric");
	modeChooser.add("Mouse = Add Magnet (Down)");
	modeChooser.add("Mouse = Add Magnet (Up)");
	modeChooser.add("Mouse = Add Magnet (Left)");
	modeChooser.add("Mouse = Add Magnet (Right)");
	modeChooser.add("Mouse = Add Resonant Medium");
	modeChooser.add("Mouse = Clear");
	modeChooser.add("Mouse = Adjust Conductivity");
	modeChooser.add("Mouse = Adjust Permeability");
	modeChooser.add("Mouse = Adjust Current");
	modeChooser.add("Mouse = Adjust Dielectric");
	modeChooser.add("Mouse = Adjust Mag Dir");
	modeChooser.add("Mouse = Adjust Mag Strength");
	modeChooser.addItemListener(this);
	add(modeChooser);

	viewChooser = new Choice();
	viewChooser.add("Show Electric Field (E)");
	viewChooser.add("Show Magnetic Field (B)");
	viewChooser.add("Show B Lines");
	viewChooser.add("Show B Strength");
	viewChooser.add("Show Current (j)");
	viewChooser.add("Show E/B");
	viewChooser.add("Show E/B lines");
	viewChooser.add("Show E/B/j");
	viewChooser.add("Show E/B lines/j");
	viewChooser.add("Show Mag. Intensity (H)");
	viewChooser.add("Show Magnetization (M)");
	viewChooser.add("Show Material Type");
	viewChooser.add("Show Vec. Potential");
	viewChooser.add("Show Poynting Vector");
	viewChooser.add("Show Energy Density");
	viewChooser.add("Show Poynting/Energy");
	viewChooser.add("Show Force");
	viewChooser.add("Show Effective Current");
	viewChooser.add("Show Magnetic Charge");
	viewChooser.add("Show Curl E");
	viewChooser.add("Show Bx");
	viewChooser.add("Show By");
	viewChooser.add("Show Hx");
	viewChooser.add("Show Hy");
	viewChooser.addItemListener(this);
	add(viewChooser);
	viewChooser.select(VIEW_E_B_J);

	add(clearButton = new Button("Clear Fields"));
	clearButton.addActionListener(this);
	add(ClearAllButton = new Button("Clear All"));
	ClearAllButton.addActionListener(this);
	stoppedCheck = new Checkbox("Stopped");
	stoppedCheck.addItemListener(this);
	add(stoppedCheck);
	
	add(new Label("Simulation Speed", Label.CENTER));
	add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 180, 1, 1, 2000));
	speedBar.addAdjustmentListener(this);

	add(new Label("Resolution", Label.CENTER));
	add(resBar = new Scrollbar(Scrollbar.HORIZONTAL, 40, 5, 16, 140));
	resBar.addAdjustmentListener(this);
	setResolution();

	add(new Label("Source Frequency", Label.CENTER));
	add(forceBar = new Scrollbar(Scrollbar.HORIZONTAL,
				     forceBarValue = 10, 1, 1, 40));
	forceBar.addAdjustmentListener(this);

	add(new Label("Brightness", Label.CENTER));
	add(brightnessBar = new Scrollbar(Scrollbar.HORIZONTAL,
				    10, 1, 1, 2000));
	brightnessBar.addAdjustmentListener(this);

	add(new Label("Line Density", Label.CENTER));
	add(lineDensityBar = new Scrollbar(Scrollbar.HORIZONTAL,
				    50, 1, 10, 100));
	lineDensityBar.addAdjustmentListener(this);

	add(auxLabel = new Label("", Label.CENTER));
	add(auxBar = new Scrollbar(Scrollbar.HORIZONTAL, 1, 1, 1, 40));
	auxBar.addAdjustmentListener(this);

	add(adjustLabel = new Label("", Label.CENTER));
	add(adjustBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 0, 102));
	adjustBar.addAdjustmentListener(this);

	add(new Label("http://www.falstad.com"));

	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	random = new Random();
	reinit();
	setup = (Setup) setupList.elementAt(0);
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);
	setModeChooser();
	
	resize(660, 500);
	handleResize();
	Dimension x = getSize();
	Dimension screen = getToolkit().getScreenSize();
	setLocation((screen.width  - x.width)/2,
		    (screen.height - x.height)/2);
	show();
    }

    void reinit() {
	sourceCount = -1;
	adjustSelectX1 = -1;
	grid = new OscElement[gridSizeXY];
	int i;
	for (i = 0; i != gridSizeXY; i++)
	    grid[i] = new OscElement();
	doSetup();
    }
    
    void setDamping() {
	int i, j;
	for (i = 0; i != gridSizeXY; i++) {
	    grid[i].damp = 1;
	    // need this to avoid reflections in Dielectric setup
	    if (grid[i].medium > 0)
		grid[i].damp = .99;
	}
	for (i = 0; i != windowOffsetX; i++)
	    for (j = 0; j != gridSizeX; j++) {
		double da = Math.exp(-(windowOffsetX-i)*.002);
		grid[i+j*gw].damp = grid[gridSizeX-1-i+gw*j].damp =
		    grid[j+i*gw].damp = grid[j+gw*(gridSizeY-1-i)].damp = da;
	    }
    }

    void handleResize() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	pixels = null;
	if (useBufferedImage) {
	    try {
		/* simulate the following code using reflection:
		   dbimage = new BufferedImage(d.width, d.height,
		   BufferedImage.TYPE_INT_RGB);
		   DataBuffer db = (DataBuffer)(((BufferedImage)dbimage).
		   getRaster().getDataBuffer());
		   DataBufferInt dbi = (DataBufferInt) db;
		   pixels = dbi.getData();
		*/
		Class biclass = Class.forName("java.awt.image.BufferedImage");
		Class dbiclass = Class.forName("java.awt.image.DataBufferInt");
		Class rasclass = Class.forName("java.awt.image.Raster");
		Constructor cstr = biclass.getConstructor(
		    new Class[] { int.class, int.class, int.class });
		dbimage = (Image) cstr.newInstance(new Object[] {
		    new Integer(d.width), new Integer(d.height),
		    new Integer(BufferedImage.TYPE_INT_RGB)});
		Method m = biclass.getMethod("getRaster", null);
		Object ras = m.invoke(dbimage, null);
		Object db = rasclass.getMethod("getDataBuffer", null).
		    invoke(ras, null);
		pixels = (int[])
		    dbiclass.getMethod("getData", null).invoke(db, null);
	    } catch (Exception ee) {
		// ee.printStackTrace();
		System.out.println("BufferedImage failed");
	    }
	}
	if (pixels == null) {
	    pixels = new int[d.width*d.height];
	    int i;
	    for (i = 0; i != d.width*d.height; i++)
		pixels[i] = 0xFF000000;
	    imageSource = new MemoryImageSource(d.width, d.height, pixels, 0,
						d.width);
	    imageSource.setAnimated(true);
	    imageSource.setFullBufferUpdates(true);
	    dbimage = cv.createImage(imageSource);
	}
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }
    
    void doClear() {
	int x, y;
	// I set all the elements in the grid to 1e-10 instead of 0 because
	// if I set them to zero, then the simulation slows down for a
	// short time until the grid fills up again.  Don't ask me why!
	// I don't know.  This showed up when I started using floats
	// instead of doubles.
	for (x = 0; x < gridSizeXY; x++) {
	    grid[x].az = grid[x].dazdt = 1e-10;
	    grid[x].epos = 0;
	    if (grid[x].resonant)
		grid[x].jz = 0;
	}
	t = 0;
	doFilter();
    }

    void doClearAll() {
	int x, y;
	for (x = 0; x < gridSizeXY; x++) {
	    OscElement oe = grid[x];
	    oe.jz = 0;
	    oe.az = oe.dazdt = 1e-10f;
	    oe.boundary = false;
	    oe.gray = false;
	    oe.resonant = false;
	    oe.conductivity = 0;
	    oe.perm = 1;
	    oe.medium = 0;
	    oe.mx = oe.my = 0;
	    oe.epos = 0;
	}
	setDamping();
	sourceChooser.select(SRC_NONE);
	setSources();
    }

    void calcBoundaries() {
	int x, y;
	int bound = 0;
	// if walls are in place on border, need to extend that through
	// hidden area to avoid "leaks"
	for (x = 0; x < gridSizeX; x++)
	    for (y = 0; y < windowOffsetY; y++) {
		grid[x+gw*y].conductivity = grid[x+gw*windowOffsetY].conductivity;
		grid[x+gw*(gridSizeY-y-1)].conductivity =
		    grid[x+gw*(gridSizeY-windowOffsetY-1)].conductivity;
	    }
	for (y = 0; y < gridSizeY; y++)
	    for (x = 0; x < windowOffsetX; x++) {
		grid[x+gw*y].conductivity = grid[windowOffsetX+gw*y].conductivity;
		grid[gridSizeX-x-1+gw*y].conductivity =
		    grid[gridSizeX-windowOffsetX-1+gw*y].conductivity;
	    }
	for (x = 1; x < gridSizeX-1; x++)
	    for (y = 1; y < gridSizeY-1; y++) {
		int gi = x+gw*y;
		OscElement oe = grid[gi];
		double perm = oe.perm;
		int medium = oe.medium;
		double mx = oe.mx;
		double my = oe.my;
		OscElement e1 = grid[gi-1];
		OscElement e2 = grid[gi+1];
		OscElement e3 = grid[gi-gw];
		OscElement e4 = grid[gi+gw];

		oe.gray = 
		    (oe.conductivity > 0 || oe.medium != 0 ||
		     oe.perm != 1 || oe.mx != 0 || oe.my != 0 ||
		     oe.resonant);
		    
		// mark all grid squares where the permeability, medium,
		// or magnetization is different from one of the neighbors
		if (e1.perm != perm || e2.perm != perm ||
		    e3.perm != perm || e4.perm != perm ||
		    e1.medium != medium || e2.medium != medium ||
		    e3.medium != medium || e4.medium != medium ||
		    e1.mx != mx || e2.mx != mx || e3.mx != mx || e4.mx != mx ||
		    e1.my != my || e2.my != my || e3.my != my || e4.my != my ||
		    oe.resonant) {
		    oe.boundary = true;
		    bound++;
		} else
		    oe.boundary = false;
	    }
    }

    int getPanelHeight() { return winSize.height / 3; }

    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();
    }

    long lastTime = 0;
    
    public void updateEMWave2(Graphics realg) {
	if (winSize == null || winSize.width == 0) {
	    // this works around some weird bug in IE which causes the
	    // applet to not show up properly the first time after
	    // a reboot.
	    handleResize();
	    return;
	}
	double tadd = 0;
	if (!stoppedCheck.getState()) {
	    int val = 5;
	    tadd = val*.05;
	}
	int i, j;

	boolean stopFunc = dragging;
	if (stoppedCheck.getState())
	    stopFunc = true;
	double speedValue = speedBar.getValue()/2.;
	if (stopFunc)
	    lastTime = 0;
	else {
	    if (lastTime == 0)
		lastTime = System.currentTimeMillis();
	    if (speedValue*(System.currentTimeMillis()-lastTime) < 1000)
		stopFunc = true;
	}
	if (!stopFunc) {
	    int iter;
	    int mxx = gridSizeX-1;
	    int mxy = gridSizeY-1;
	    for (iter = 1; ; iter++) {
		doSources(tadd, false);
		setup.doStep();
		double sinhalfth = 0;
		double sinth = 0;
		double scaleo = 0;
		double tadd2 = tadd;
		double forcecoef = 1;
		int curMedium = 0;
		OscElement oew, oee, oen, oes, oe;
		double previ, nexti, prevj, nextj, basis, a, b;
System.out.println("forcecoef " + forcecoef + " tadd2 " + (tadd*tadd));
		for (j = 1; j != mxy; j++) {
		    int gi = j*gw+1;
		    int giEnd = gi+mxx-1;
		    oe = grid[gi-1];
		    oee = grid[gi];
		    for (; gi != giEnd; gi++) {
			oew = oe;
			oe = oee;
			oee = grid[gi+1];
			oen = grid[gi-gw];
			oes = grid[gi+gw];

			if (oe.conductivity > 0)
			    oe.jz = 0;

			if (oe.boundary) {
			    // here we may be on the boundary between two
			    // media, so we have to do some extra work
			    if (oe.resonant) {
				oe.jz = oe.jz*.999 +
				    -oe.dazdt * .001 - oe.epos * .02;
				oe.epos += oe.jz * .2;
			    }
			    if (curMedium != oe.medium) {
				curMedium = oe.medium;
				forcecoef = (1-
				   (mediumMaxIndex/mediumMax)*curMedium);
				forcecoef *= forcecoef;
			    }
			    double az = oe.az;
			    previ = (oew.az-az)/oew.perm;
			    nexti = (oee.az-az)/oee.perm;
			    prevj = (oen.az-az)/oen.perm;
			    nextj = (oes.az-az)/oes.perm;
			    basis = (nexti+previ+nextj+prevj)*.25;
			    // calculate effective current
			    double jz = oew.my - oee.my +
				oes.mx - oen.mx + oe.jz;
			    a = oe.perm*basis + jz;
			} else {
			    // easy way
			    previ = oew.az;
			    nexti = oee.az;
			    prevj = oen.az;
			    nextj = oes.az;
			    basis = (nexti+previ+nextj+prevj)*.25;
			    a = oe.jz - (oe.az - basis);
			}
			oe.dazdt = (oe.dazdt * oe.damp) + a * forcecoef;
		    }
		}
		tadd2 = tadd*tadd;
		for (j = 1; j != mxy; j++) {
		    int gi = j*gw+1;
		    int giEnd = gi-1+mxx;
		    for (; gi != giEnd; gi++) {
			oe = grid[gi];
			if (oe.conductivity > 0) {
			    a = -oe.dazdt*oe.conductivity;
			    oe.jz = a;
			    oe.dazdt += a;
			}
			oe.az += oe.dazdt * tadd2;
		    }
		}
		t += tadd;
		filterGrid();
		long tm = System.currentTimeMillis();
		if (tm-lastTime > 200 ||
		    iter*1000 >= speedValue*(tm-lastTime)) {
		    lastTime = tm;
		    break;
		}
	    }
	}

	renderGrid();
	
	int intf = (gridSizeY/2-windowOffsetY)*winSize.height/windowHeight;
	for (i = 0; i < sourceCount; i++) {
	    OscSource src = sources[i];
	    int xx = src.getScreenX();
	    int yy = src.getScreenY();
	    plotSource(i, xx, yy);
	}
	if (adjustSelectX1 != -1) {
	    int c = getrand(255);
	    int col = 0x010100 * c + 0xFF000000;
	    int lx1 = (int) (adjustSelectX1*winSize.width /windowWidth);
	    int ly1 = (int) (adjustSelectY1*winSize.height/windowHeight);
	    int lx2 = (int) ((adjustSelectX2+1)*winSize.width /windowWidth);
	    int ly2 = (int) ((adjustSelectY2+1)*winSize.height/windowHeight);
	    plotRect(lx1, ly1, lx2-1, ly2-1, col);
	}
	
	if (imageSource != null)
	    imageSource.newPixels();
	
	realg.drawImage(dbimage, 0, 0, this);
	if (!stoppedCheck.getState())
	    cv.repaint(pause);
    }

    void plotRect(int x1, int y1, int x2, int y2, int col) {
	int i;
	for (i = x1; i <= x2; i++) {
	    plotPixel(i, y1, col);
	    plotPixel(i, y2, col);
	}
	for (i = y1; i <= y2; i++) {
	    plotPixel(x1, i, col);
	    plotPixel(x2, i, col);
	}
    }
    
    void plotPixel(int x, int y, int pix) {
	if (x < 0 || x >= winSize.width)
	    return;
	try { pixels[x+y*winSize.width] = pix; } catch (Exception e) {}
    }

    // draw a circle the slow and dirty way
    void plotSource(int n, int xx, int yy) {
	int rad = sourceRadius;
	int j;
	int col = (n == selectedSource) ? 0xFF00FFFF : 0xFFFFFFFF;
	for (j = 0; j <= rad; j++) {
	    int k = (int) (Math.sqrt(rad*rad-j*j)+.5);
	    plotPixel(xx+j, yy+k, col);
	    plotPixel(xx+k, yy+j, col);
	    plotPixel(xx+j, yy-k, col);
	    plotPixel(xx-k, yy+j, col);
	    plotPixel(xx-j, yy+k, col);
	    plotPixel(xx+k, yy-j, col);
	    plotPixel(xx-j, yy-k, col);
	    plotPixel(xx-k, yy-j, col);
	    plotPixel(xx, yy+j, col);
	    plotPixel(xx, yy-j, col);
	    plotPixel(xx+j, yy, col);
	    plotPixel(xx-j, yy, col);
	}
    }
    
    void renderGrid() {
	double mult = brightnessBar.getValue() / 50.0;
	double emult = 1;
	int ix = 0;
	int i, j, k, l;

	int viewScalar, viewVector, viewScalarCond, viewVectorCond;
	int v = viewChooser.getSelectedIndex();
	if (v == VIEW_FORCE)
	    calcForce();
	boolean showLines = false;
	viewScalar = viewScalarCond =
	    viewVector = viewVectorCond = VIEW_NONE;
	switch (v) {
	case VIEW_E:
	case VIEW_A:
	case VIEW_J:
	case VIEW_BX:
	case VIEW_BY:
	case VIEW_HX:
	case VIEW_HY:
	case VIEW_EFF_CUR:
	case VIEW_MAG_CHARGE:
	case VIEW_TYPE:
	case VIEW_B_STRENGTH:
	case VIEW_ENERGY:
	    viewScalar = viewScalarCond = v;
	    break;
	case VIEW_B:
	case VIEW_POYNTING:
	case VIEW_H:
	case VIEW_M:
	case VIEW_CURL_E:
	    viewVector = viewVectorCond = v;
	    break;
	case VIEW_B_LINES:
	    showLines = true;
	    break;
	case VIEW_FORCE:
	    viewScalar = viewScalarCond = VIEW_E;
	    viewVector = viewVectorCond = VIEW_FORCE;
	    break;
	case VIEW_E_B:
	    viewScalar = viewScalarCond = VIEW_E;
	    viewVector = viewVectorCond = VIEW_B;
	    emult = .3;
	    break;
	case VIEW_E_B_J:
	    viewScalar = VIEW_E;
	    viewScalarCond = VIEW_J;
	    viewVector = viewVectorCond = VIEW_B;
	    emult = .3;
	    break;
	case VIEW_E_B_LINES:
	    viewScalar = viewScalarCond = VIEW_E;
	    showLines = true;
	    emult = .3;
	    break;
	case VIEW_E_B_LINES_J:
	    viewScalar = VIEW_E;
	    viewScalarCond = VIEW_J;
	    showLines = true;
	    emult = .3;
	    break;
	case VIEW_POYNTING_ENERGY:
	    viewScalar = viewScalarCond = VIEW_ENERGY;
	    viewVector = viewVectorCond = VIEW_POYNTING;
	    break;
	}
	for (j = 0; j != windowHeight; j++) {
	    ix = winSize.width*(j*winSize.height/windowHeight);
	    int gi = (j+windowOffsetY)*gw+windowOffsetX;
	    for (i = 0; i != windowWidth; i++, gi++) {
		int x = i*winSize.width/windowWidth;
		int y = j*winSize.height/windowHeight;
		int x2 = (i+1)*winSize.width/windowWidth;
		int y2 = (j+1)*winSize.height/windowHeight;
		int i2 = i+windowOffsetX;
		int j2 = j+windowOffsetY;
		int vs = viewScalar;
		int vv = viewVector;
		int col_r = 0, col_g = 0, col_b = 0;
		OscElement oe = grid[gi];
		if (oe.gray || oe.jz != 0) {
		    col_r = col_g = col_b = 64;
		    if (oe.conductivity > 0 || (oe.jz != 0 && !oe.resonant)) {
			vv = viewVectorCond;
			vs = viewScalarCond;
		    }
		}
		if (vs != VIEW_NONE) {
		    double dy = 0;
		    switch (vs) {
		    case VIEW_E:   dy = -oe.dazdt * emult; break;
		    case VIEW_A:   dy = oe.az * .2; break;
		    case VIEW_J:   dy = oe.jz; break;
		    case VIEW_BX:
			dy = grid[gi-gw].az - grid[gi+gw].az;
			break;
		    case VIEW_BY:
			dy = -(grid[gi+1].az - grid[gi-1].az);
			break;
		    case VIEW_HX:
			dy = ((grid[gi-gw].az - grid[gi+gw].az)/oe.perm
			      - oe.mx*mhmult);
			break;
		    case VIEW_HY:
			dy = -((grid[gi+1].az - grid[gi-1].az)/oe.perm
			      - oe.my*mhmult);
			break;
		    case VIEW_EFF_CUR:
			dy = getMagY(gi-1) - getMagY(gi+1) +
			    getMagX(gi+gw) - getMagX(gi-gw);
			break;
		    case VIEW_MAG_CHARGE:
			dy = grid[gi-1].mx - grid[gi+1].mx +
			     grid[gi-gw].my - grid[gi+gw].my;
			break;
		    case VIEW_B_STRENGTH:
			{
			    double dx = grid[gi-gw].az - grid[gi+gw].az;
			    dy = grid[gi+1].az - grid[gi-1].az;
			    dy = Math.sqrt(dx*dx+dy*dy);
			    break;
			}
		    case VIEW_ENERGY:
			{
			    double n = 1/
				(1-oe.medium*mediumMaxIndex/mediumMax);
			    double dielec = n*n;
			    double dx = grid[gi-gw].az - grid[gi+gw].az;
			    dy = grid[gi+1].az - grid[gi-1].az;
			    dy = .4*((dx*dx+dy*dy)/oe.perm +
				     oe.dazdt * oe.dazdt * dielec);
			    break;
			}
		    }
		    dy *= mult;
		    if (dy < -1)
			dy = -1;
		    if (dy > 1)
			dy = 1;
		    if (vs == VIEW_TYPE) {
			double dr = 0, dg = 0, db = 0;
			if (oe.resonant) {
			    dr = 1;
			    dg = .75;
			    db = .5;
			} else if (oe.perm < 1) {
			    dr = 1-oe.perm;
			} else if (oe.perm > 1) {
			    dg = (oe.perm-1)/30;
			} else if (oe.mx != 0 || oe.my != 0) {
			    dr = .53; dg = .27; db = .63;
			} else if (oe.medium > 0) {
			    dr = oe.medium/(double) mediumMax;
			    dg = dr*.5;
			} else if (oe.conductivity > 0) {
			    dg = db = oe.conductivity;
			    if (oe.conductivity == 1)
				dr = 1;
			} else if (oe.jz > 0) {
			    dr = dg = oe.jz*mult;
			} else if (oe.jz < 0) {
			    db = -oe.jz*mult;
			}
			dr = clamp(dr);
			dg = clamp(dg);
			db = clamp(db);
			col_r = col_r+(int) (dr*(255-col_r));
			col_g = col_g+(int) (dg*(255-col_g));
			col_b = col_b+(int) (db*(255-col_b));
		    } else if (vs == VIEW_J || vs == VIEW_EFF_CUR ||
			       vs == VIEW_ENERGY) {
			if (dy < 0)
			    col_b = col_b+(int) (-dy*(255-col_b));
			else {
			    col_r = col_r+(int) (dy*(255-col_r));
			    col_g = col_g+(int) (dy*(255-col_g));
			}
		    } else {
			if (dy < 0)
			    col_r = col_r+(int) (-dy*(255-col_r));
			else
			    col_g = col_g+(int) (dy*(255-col_g));
		    }
		}
		int col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		for (k = 0; k != x2-x; k++, ix++)
		    for (l = 0; l != y2-y; l++)
			pixels[ix+l*winSize.width] = col;
		oe.col = col;
		if (vv != VIEW_NONE) {
		    double dx = 0, dy = 0;
		    switch (vv) {
		    case VIEW_B:
			dx = grid[gi-gw].az - grid[gi+gw].az;
			dy = grid[gi+1].az - grid[gi-1].az;
			break;
		    case VIEW_H:
			dx = (grid[gi-gw].az - grid[gi+gw].az)/oe.perm -
			    mhmult*oe.mx;
			dy = (grid[gi+1].az - grid[gi-1].az)/oe.perm -
			    mhmult*oe.my;
			break;
		    case VIEW_M:
			{
			    double mm = 1-1/oe.perm;
			    dx = (grid[gi-gw].az - grid[gi+gw].az)*mm +
				oe.mx;
			    dy = (grid[gi+1].az - grid[gi-1].az)*mm +
				oe.my;
			}
			break;
		    case VIEW_POYNTING:
			dy = 5*oe.dazdt *
			    (grid[gi-gw].az - grid[gi+gw].az)/oe.perm;
			dx = -5*oe.dazdt *
			    (grid[gi+1].az - grid[gi-1].az)/oe.perm;
			break;
		    case VIEW_FORCE:
			dx = forceVecs[forceMap[i2][j2]][0];
			dy = forceVecs[forceMap[i2][j2]][1];
			break;
		    case VIEW_CURL_E:
			dx = -5*(grid[gi-gw].dazdt - grid[gi+gw].dazdt);
			dy = -5*(grid[gi+1].dazdt - grid[gi-1].dazdt);
			break;
		    }
		    double dn = Math.sqrt(dx*dx+dy*dy);
		    if (dn > 0) {
			dx /= dn;
			dy /= dn;
		    }
		    dn *= mult;
		    if (dn > 1) {
			if (dn > 2)
			    dn = 2;
			dn -= 1;
			col_g = 255;
			col_r = col_r+(int) (dn*(255-col_r));
			col_b = col_b+(int) (dn*(255-col_b));
		    } else
			col_g = col_g+(int) (dn*(255-col_g));
		    col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		    int sw2 = (x2-x)/2;
		    int sh2 = (y2-y)/2;
		    int x1 = x+sw2-(int) (sw2*dx);
		    int y1 = y+sh2-(int) (sh2*dy);
		    x2 = x+sw2+(int) (sw2*dx);
		    y2 = y+sh2+(int) (sh2*dy);
		    drawLine(x1, y1, x2, y2, col);
		    int as = 3;
		    drawLine(x2, y2,
			     (int) ( dy*as-dx*as+x2),
			     (int) (-dx*as-dy*as+y2), col);
		    drawLine(x2, y2,
			     (int) (-dy*as-dx*as+x2),
			     (int) ( dx*as-dy*as+y2), col);
		}
	    }
	}
	if (showLines) {
	    renderLines();
	    lineDensityBar.enable();
	} else {
	    lineDensityBar.disable();
	}
    }

    void drawLine(int x1, int y1, int x2, int y2, int col) {
	if (x1 < 0) x1 = 0;
	if (y1 < 0) y1 = 0;
	if (x2 < 0) x2 = 0;
	if (y2 < 0) y2 = 0;
	if (x1 >= winSize.width-1)  x1 = winSize.width-1;
	if (y1 >= winSize.height-1) y1 = winSize.height-1;
	if (x2 >= winSize.width-1)  x2 = winSize.width-1;
	if (y2 >= winSize.height-1) y2 = winSize.height-1;
	int dx = abs(x2-x1);
	int dy = abs(y2-y1);
	if (dx > dy) {
	    if (x1 > x2) {
		int q;
		q = x1; x1 = x2; x2 = q;
		q = y1; y1 = y2; y2 = q;
	    }
	    int x;
	    for (x = x1; x <= x2; x++)
		pixels[x+(y1+(y2-y1)*(x-x1)/dx)*winSize.width] = col;
	} else if (dy > 0) {
	    if (y1 > y2) {
		int q;
		q = x1; x1 = x2; x2 = q;
		q = y1; y1 = y2; y2 = q;
	    }
	    int y;
	    for (y = y1; y <= y2; y++)
		pixels[x1+(x2-x1)*(y-y1)/dy+y*winSize.width] = col;
	}
    }
    
    double getMagX(int gi) {
	OscElement oe = grid[gi];
	double mm = 1-1/oe.perm;
	return (grid[gi-gw].az - grid[gi+gw].az)*mm + oe.mx;
    }

    double getMagY(int gi) {
	OscElement oe = grid[gi];
	double mm = 1-1/oe.perm;
	return (grid[gi+1].az - grid[gi-1].az)*mm + oe.my;
    }

    double clamp(double x) {
	return (x < 0) ? 0 : (x > 1) ? 1 : x;
    }

    void doSources(double tadd, boolean clear) {
	int i, j;
	if (sourceCount == 0)
	    return;
	double w = forceBar.getValue()*(t-forceTimeZero)*freqMult;
	double w2 = w;
	switch (auxFunction) {
	case AUX_FREQ:
	    w2 = auxBar.getValue()*t*freqMult;
	    break;
	case AUX_PHASE:
	    {
		// scrollbars don't always go all the way to the end,
		// so we do some extra work to make sure we can set
		// the phase all the way from 0 to pi
		int au = auxBar.getValue()-1;
		if (au > 38)
		    au = 38;
		w2 = w+au*(pi/38);
		break;
	    }
	}
	double v = 0;
	double v2 = 0;
	switch (sourceWaveform) {
	case SWF_SIN:
	    v = Math.sin(w);
	    if (sourceCount >= 2)
		v2 = Math.sin(w2);
	    else if (sourceFreqCount == 2)
		v = (v+Math.sin(w2))*.5;
	    break;
	case SWF_PACKET:
	    {
		w %= pi*2;
		double adjw = w/(freqMult*forceBar.getValue());
		adjw -= 10;
		v = Math.exp(-.01*adjw*adjw)*
		    Math.sin(adjw*.2);
		if (adjw < 0)
		    doFilter();
	    }
	    break;
	}
	if (clear)
	    v = v2 = 0;
	sources[0].v = sources[2].v = (float) (2*v*sourceMult);
	sources[1].v = sources[3].v = (float) (2*v2*sourceMult);
	if (sourcePlane) {
	    for (j = 0; j != sourceCount/2; j++) {
		OscSource src1 = sources[j*2];
		OscSource src2 = sources[j*2+1];
		OscSource src3 = sources[j];
		drawPlaneSource(src1.x, src1.y,
				src2.x, src2.y, src3.v*.1);
	    }
	} else {
	    for (i = 0; i != sourceCount; i++) {
		OscSource src = sources[i];
		OscElement oe = grid[src.x+gw*src.y];
		oe.jz = src.v;
	    }
	}
    }

    int abs(int x) {
	return x < 0 ? -x : x;
    }

    void drawPlaneSource(int x1, int y1, int x2, int y2, double v) {
	if (y1 == y2) {
	    if (x1 == windowOffsetX)
		x1 = 0;
	    if (x2 == windowOffsetX)
		x2 = 0;
	    if (x1 == windowOffsetX+windowWidth-1)
		x1 = gridSizeX-1;
	    if (x2 == windowOffsetX+windowWidth-1)
		x2 = gridSizeX-1;
	}
	if (x1 == x2) {
	    if (y1 == windowOffsetY)
		y1 = 0;
	    if (y2 == windowOffsetY)
		y2 = 0;
	    if (y1 == windowOffsetY+windowHeight-1)
		y1 = gridSizeY-1;
	    if (y2 == windowOffsetY+windowHeight-1)
		y2 = gridSizeY-1;
	}
	// need to draw a line from x1,y1 to x2,y2
	if (x1 == x2 && y1 == y2) {
	    grid[x1+gw*y1].jz = v;
	} else if (abs(y2-y1) > abs(x2-x1)) {
	    // y difference is greater, so we step along y's
	    // from min to max y and calculate x for each step
	    int sgn = sign(y2-y1);
	    int x, y;
	    for (y = y1; y != y2+sgn; y += sgn) {
		x = x1+(x2-x1)*(y-y1)/(y2-y1);
		grid[x+gw*y].jz = v;
	    }
	} else {
	    // x difference is greater, so we step along x's
	    // from min to max x and calculate y for each step
	    int sgn = sign(x2-x1);
	    int x, y;
	    for (x = x1; x != x2+sgn; x += sgn) {
		y = y1+(y2-y1)*(x-x1)/(x2-x1);
		grid[x+gw*y].jz = v;
	    }
	}
    }

    int sign(int x) {
	return (x < 0) ? -1 : (x == 0) ? 0 : 1;
    }

    byte linegrid[];

    // render electric field lines
    void renderLines() {
	double x = 0, y = 0;
	int lineGridSize = lineDensityBar.getValue();
	int lineGridSize2 = lineGridSize*lineGridSize;
	if (linegrid == null)
	    linegrid = new byte[lineGridSize2];
	double lspacing = lineGridSize/(double) windowWidth;
	double startx = -1, starty = 0;
	int linemax = 0;
	double mult = brightnessBar.getValue() / 50.0;
	boolean doArrow = false;
	int dir = 1;
	double olddn = -1;
	int oldcol = -1;
	int gridsearchx = 0, gridsearchy = 0;
	int i, j;
	for (i = 0; i != lineGridSize2; i++)
	    linegrid[i] = 0;
	int oldcgx = -1, oldcgy = -1;
	while (true) {
	    if (linemax-- == 0 || x == 0) {
		if (dir == 1) {
		    int gi = gridsearchx+lineGridSize*gridsearchy;
		    while (true) {
			if (linegrid[gi] == 0)
			    break;
			if (++gridsearchx == lineGridSize) {
			    if (++gridsearchy == lineGridSize)
				break;
			    gridsearchx = 0;
			}
			gi++;
		    }
		    if (gridsearchx == lineGridSize && gridsearchy == lineGridSize)
			break;
		    startx = gridsearchx/lspacing;
		    starty = gridsearchy/lspacing;
		}
		x = startx+.48/lspacing;
		y = starty+.48/lspacing;
		linemax = 40; // was 100
		dir = -dir;
		oldcgx = oldcgy = -1;
	    }
	    if (x < 0 || y < 0 || x >= windowWidth || y >= windowHeight) {
		x = 0;
		continue;
	    }
	    int cgx = (int) (x*lspacing);
	    int cgy = (int) (y*lspacing);
	    doArrow = true;
	    if (cgx != oldcgx || cgy != oldcgy) {
		int lg = ++linegrid[cgx+lineGridSize*cgy];
		if (lg > 2) {
		    x = 0;
		    continue;
		}
		oldcgx = cgx;
		oldcgy = cgy;
	    } else
		doArrow = false;
	    int xi = windowOffsetX+(int) x;
	    int yi = windowOffsetY+(int) y;
	    int gi = xi+gw*yi;
	    OscElement oe = grid[gi];
	    double dx = grid[gi-gw].az - grid[gi+gw].az;
	    double dy = grid[gi+1].az  - grid[gi-1].az;
	    double dn = Math.sqrt(dx*dx+dy*dy);
	    if (dn == 0) {
		x = 0;
		continue;
	    }
	    dx /= dn;
	    dy /= dn;
	    double oldx = x;
	    double oldy = y;
	    x += .5*dx*dir;
	    y += .5*dy*dir;
	    dn *= mult;
	    int col = grid[gi].col;
	    if (dn != olddn || col != oldcol) {
		int col_r = (col>>16) & 255;
		int col_g = (col>> 8) & 255;
		int col_b = col & 255;
		if (dn > 1) {
		    if (dn > 2)
			dn = 2;
		    dn -= 1;
		    col_g = 255;
		    col_r = col_r+(int) (dn*(255-col_r));
		    col_b = col_b+(int) (dn*(255-col_b));
		} else
		    col_g = col_g+(int) (dn*(255-col_g));
		col = (255<<24) | (col_r<<16) | (col_g<<8) | col_b;
		olddn = dn;
		oldcol = col;
	    }
	    int lx1 = (int) (oldx*winSize.width /windowWidth);
	    int ly1 = (int) (oldy*winSize.height/windowHeight);
	    int lx2 = (int) (x*winSize.width /windowWidth);
	    int ly2 = (int) (y*winSize.height/windowHeight);
	    drawLine(lx1, ly1, lx2, ly2, col);
	    if (doArrow && linegrid[cgx+lineGridSize*cgy] == 1) {
		if ((cgx & 3) == 0 && (cgy & 3) == 0) {
		    int as = 5;
		    drawLine(lx2, ly2,
			     (int) ( dy*as-dx*as+lx2),
			     (int) (-dx*as-dy*as+ly2), col);
		    drawLine(lx2, ly2,
			     (int) (-dy*as-dx*as+lx2),
			     (int) ( dx*as-dy*as+ly2), col);
		}
	    }
	}
    }

    byte forceMap[][];
    double forceVecs[][];

    void calcForce() {
	int x, y;
	forceMap = new byte[gridSizeX][gridSizeY];
	forceVecs = new double[256][2];
	byte magno = 1;
	for (x = windowOffsetX; x != windowWidth+windowOffsetX; x++)
	    for (y = windowOffsetY; y != windowHeight+windowOffsetY; y++) {
		if (forceMap[x][y] != 0 || !grid[x+gw*y].feelsForce())
		    continue;
		forceVecs[magno][0] = forceVecs[magno][1] = 0;
		forceSearch(x, y, magno++);
	    }
    }

    void forceSearch(int x, int y, byte magno) {
	if (forceMap[x][y] != 0)
	    return;
	if (x < windowOffsetX ||
	    y < windowOffsetY ||
	    x >= windowOffsetX+windowWidth ||
	    y >= windowOffsetY+windowHeight)
	    return;
	int gi = x+y*gw;
	double mc = getMagX(gi-1)  - getMagX(gi+1) +
	            getMagY(gi-gw) - getMagY(gi+gw);
	double bx = grid[gi-gw].az - grid[gi+gw].az;
	double by = grid[gi+1].az  - grid[gi-1].az;
	forceVecs[magno][0] += mc*bx + grid[gi].jz*by;
	forceVecs[magno][1] += mc*by - grid[gi].jz*bx;
	if (grid[gi].feelsForce()) {
	    forceMap[x][y] = magno;
	    forceSearch(x-1, y, magno);
	    forceSearch(x+1, y, magno);
	    forceSearch(x, y-1, magno);
	    forceSearch(x, y+1, magno);
	}
    }

    int filterCount = 0;

    // filter out high-frequency noise
    void filterGrid() {
	if ((filterCount++ & 3) != 0)
	    return;
	if (filterCount > 200)
	    return;
	// filter less aggressively if there is a source on the screen,
	// to avoid damping the waves
	double mult1 = (forceBar.getValue() > 7 &&
			sourceCount > 0 &&
			sourceWaveform == SWF_SIN) ? 40 : 8;
	double mult2 = 4+mult1;
	int x, y;
	for (y = 1; y < gridSizeY-1; y++)
	    for (x = 1; x < gridSizeX-1; x++) {
		int gi = x+y*gw;
		OscElement oe = grid[gi];
		if (oe.jz != 0 || oe.conductivity > 0)
		    continue;
		if (oe.perm != grid[gi-1].perm ||
		    oe.perm != grid[gi+1].perm ||
		    oe.perm != grid[gi-gw].perm ||
		    oe.perm != grid[gi+gw].perm)
		    continue;
		double jz = grid[gi-1].my - grid[gi+1].my +
		    grid[gi+gw].mx - grid[gi-gw].mx;
		if (jz != 0)
		    continue;
		oe.az = (oe.az*mult1 + grid[gi-1].az + grid[gi+1].az +
			 grid[gi-gw].az + grid[gi+gw].az)/mult2;
	    }
    }

    void noFilter() {
	filterCount = 200;
    }
    void doFilter() {
	filterCount %= 4;
    }

    void edit(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();
	if (selectedSource != -1) {
	    doSources(1, true);
	    x = x*windowWidth/winSize.width;
	    y = y*windowHeight/winSize.height;
	    OscSource s = sources[selectedSource];
	    if (x >= 0 && y >= 0 && x < windowWidth && y < windowHeight) {
		int ox = s.x;
		int oy = s.y;
		s.x = x+windowOffsetX;
		s.y = y+windowOffsetY;
		cv.repaint(pause);
	    }
	    return;
	}
	if (modeChooser.getSelectedIndex() >= MODE_ADJUST) {
	    int xp = x*windowWidth/winSize.width;
	    int yp = y*windowHeight/winSize.height;
	    if (adjustSelectX1 == -1) { 
		adjustSelectX1 = adjustSelectX2 = xp;
		adjustSelectY1 = adjustSelectY2 = yp;
		adjustBar.enable();
		return;
	    }
	    adjustSelectX1 = max(0, min(xp, adjustSelectX1));
	    adjustSelectX2 = min(windowWidth-1,  max(xp, adjustSelectX2));
	    adjustSelectY1 = max(0, min(yp, adjustSelectY1));
	    adjustSelectY2 = min(windowHeight-1, max(yp, adjustSelectY2));
	    adjustBar.enable();
	    return;
	}
	if (dragX == x && dragY == y)
	    editFuncPoint(x, y);
	else {
	    // need to draw a line from old x,y to new x,y and
	    // call editFuncPoint for each point on that line.  yuck.
	    if (abs(y-dragY) > abs(x-dragX)) {
		// y difference is greater, so we step along y's
		// from min to max y and calculate x for each step
		int x1 = (y < dragY) ? x : dragX;
		int y1 = (y < dragY) ? y : dragY;
		int x2 = (y > dragY) ? x : dragX;
		int y2 = (y > dragY) ? y : dragY;
		dragX = x;
		dragY = y;
		for (y = y1; y <= y2; y++) {
		    x = x1+(x2-x1)*(y-y1)/(y2-y1);
		    editFuncPoint(x, y);
		}
	    } else {
		// x difference is greater, so we step along x's
		// from min to max x and calculate y for each step
		int x1 = (x < dragX) ? x : dragX;
		int y1 = (x < dragX) ? y : dragY;
		int x2 = (x > dragX) ? x : dragX;
		int y2 = (x > dragX) ? y : dragY;
		dragX = x;
		dragY = y;
		for (x = x1; x <= x2; x++) {
		    y = y1+(y2-y1)*(x-x1)/(x2-x1);
		    editFuncPoint(x, y);
		}
	    }
	}
	calcBoundaries();
	cv.repaint(pause);
    }

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

    void editFuncPoint(int x, int y) {
	int xp = x*windowWidth/winSize.width;
	int yp = y*windowHeight/winSize.height;

	if (xp < 0 || xp >= windowWidth ||
	    yp < 0 || yp >= windowHeight)
	    return;

	xp += windowOffsetX;
	yp += windowOffsetY;
	OscElement oe = grid[xp+gw*yp];
	doFilter();
	if (!dragSet && !dragClear) {
	    dragClear = oe.conductivity != 0 || oe.medium != 0 ||
		oe.mx != 0 || oe.my != 0 || oe.perm != 1 ||
		oe.jz != 0 || oe.resonant;
	    dragSet = !dragClear;
	}

	oe.conductivity = 0;
	oe.medium = 0;
	oe.mx = oe.my = 0;
	oe.perm = 1;
	oe.jz = 0;
	oe.resonant = false;

	if (dragClear)
	    return;

	switch (modeChooser.getSelectedIndex()) {
	case MODE_J_POS: oe.jz =  1; break;
	case MODE_J_NEG: oe.jz = -1; break;
	case MODE_FERROMAG: addPerm(xp, yp, 5);  break;
	case MODE_DIAMAG:  addPerm(xp, yp, .5); break;
	case MODE_MEDIUM:  oe.medium = mediumMax; break;
	case MODE_M_DOWN:   oe.my = 1;  break;
	case MODE_M_UP:     oe.my = -1; break;
	case MODE_M_LEFT:   oe.mx = -1; break;
	case MODE_M_RIGHT:  oe.mx = 1;  break;
	case MODE_PERF_CONDUCTOR: addConductor(xp, yp, 1); break;
	case MODE_GOOD_CONDUCTOR: addConductor(xp, yp, .9); break;
	case MODE_FAIR_CONDUCTOR: addConductor(xp, yp, .5); break;
	case MODE_RESONANT: oe.resonant = true; break;
	}
    }

    void selectSource(MouseEvent me) {
	int x = me.getX();
	int y = me.getY();
	int i;
	for (i = 0; i != sourceCount; i++) {
	    OscSource src = sources[i];
	    int sx = src.getScreenX();
	    int sy = src.getScreenY();
	    int r2 = (sx-x)*(sx-x)+(sy-y)*(sy-y);
	    if (sourceRadius*sourceRadius > r2) {
		selectedSource = i;
		return;
	    }
	}
	selectedSource = -1;
    }

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

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(100);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == clearButton) {
	    doClear();
	    cv.repaint();
	}
	if (e.getSource() == ClearAllButton) {
	    doClearAll();
	    cv.repaint();
	}
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
	if (e.getSource() == resBar) {
	    setResolution();
	    reinit();
	    cv.repaint(pause);
	}
	if (e.getSource() == brightnessBar)
	    cv.repaint(pause);
	if (e.getSource() == lineDensityBar) {
	    cv.repaint(pause);
	    linegrid = null;
	}
	if (e.getSource() == forceBar)
	    setForce();
	if (e.getSource() == adjustBar)
	    doAdjust();
    }

    void setForceBar(int x) {
	forceBar.setValue(x);
	forceBarValue = x;
	forceTimeZero = 0;
    }

    void setForce() {
	// adjust time zero to maintain continuity in the force func
	// even though the frequency has changed.
	double oldfreq = forceBarValue * freqMult;
	forceBarValue = forceBar.getValue();
	double newfreq = forceBarValue * freqMult;
	double adj = newfreq-oldfreq;
	forceTimeZero = t-oldfreq*(t-forceTimeZero)/newfreq;
    }

    void setResolution() {
	windowWidth = windowHeight = resBar.getValue();
	windowOffsetX = windowOffsetY = 20;
	gridSizeX = windowWidth + windowOffsetX*2;
	gridSizeY = windowHeight + windowOffsetY*2;
	gridSizeXY = gridSizeX*gridSizeY;
	gw = gridSizeX;
	System.out.println("gridsize " + gridSizeX + " window " + windowWidth);
	linegrid = null;
    }

    void setResolution(int x) {
	resBar.setValue(x);
	setResolution();
	reinit();
    }

    void doAdjust() {
	if (adjustSelectX1 == -1)
	    return;
	int vali = adjustBar.getValue();
	if (vali < 1)
	    vali = 1;
	if (vali > 99)
	    vali = 100;
	if (modeChooser.getSelectedIndex() == MODE_ADJ_PERM && vali < 3)
	    vali = 3;
	float val = vali/100.f;
	int x, y;
	for (y = adjustSelectY1; y <= adjustSelectY2; y++)
	    for (x = adjustSelectX1; x <= adjustSelectX2; x++) {
		OscElement oe = grid[x+windowOffsetX+gw*(y+windowOffsetY)];
		switch (modeChooser.getSelectedIndex()) {
		case MODE_ADJ_CONDUCT:
		    if (oe.getType() == TYPE_CONDUCTOR)
			oe.conductivity = val;
		    break;
		case MODE_ADJ_PERM:
		    if (oe.getType() == TYPE_FERROMAGNET)
			oe.perm = vali/2.f;
		    break;
		case MODE_ADJ_J:
		    if (oe.getType() == TYPE_CURRENT)
			oe.jz = (oe.jz < 0) ? -val : val;
		    break;
		case MODE_ADJ_MEDIUM:
		    if (oe.getType() == TYPE_MEDIUM)
			oe.medium = (int) (val*mediumMax);
		    break;
		case MODE_ADJ_MAG_DIR:
		    if (oe.getType() == TYPE_MAGNET) {
			double m =
			    Math.sqrt(oe.mx*oe.mx+oe.my*oe.my);
			oe.mx = (float) (Math.cos(val*2*pi)*m);
			oe.my = (float) -(Math.sin(val*2*pi)*m);
		    }
		    break;
		case MODE_ADJ_MAG_STR:
		    if (oe.getType() == TYPE_MAGNET) {
			float mult = (float) (val/Math.
					      sqrt(oe.mx*oe.mx+oe.my*oe.my));
			oe.mx *= mult;
			oe.my *= mult;
		    }
		    break;
		}
	    }
	calcBoundaries();
    }

    public void mouseDragged(MouseEvent e) {
	if (!dragging)
	    selectSource(e);
	dragging = true;
	edit(e);
    }
    public void mouseMoved(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
	    return;
	int x = e.getX();
	int y = e.getY();
	dragX = x; dragY = y;
	selectSource(e);
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
	selectedSource = -1;
	cv.repaint();
    }
    public void mousePressed(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	adjustSelectX1 = -1;
	adjustBar.disable();
	int x = e.getX();
	int y = e.getY();
	dragX = x; dragY = y;
	if (!dragging)
	    selectSource(e);
	dragging = true;
	edit(e);
    }
    public void mouseReleased(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = false;
	dragSet = dragClear = false;
	cv.repaint();
    }
    public void itemStateChanged(ItemEvent e) {
	cv.repaint(pause);
	if (e.getItemSelectable() == stoppedCheck)
	    return;
	if (e.getItemSelectable() == sourceChooser) {
	    setSources();
	    doFilter();
	}
	if (e.getItemSelectable() == setupChooser)
	    doSetup();
	if (e.getItemSelectable() == modeChooser)
	    setModeChooser();
    }

    void setModeChooser() {
	if (modeChooser.getSelectedIndex() < MODE_ADJUST) {
	    adjustLabel.hide();
	    adjustBar.hide();
	    validate();
	    adjustSelectX1 = -1;
	    return;
	}
	switch (modeChooser.getSelectedIndex()) {
	case MODE_ADJ_CONDUCT: adjustLabel.setText("Conductivity"); break;
	case MODE_ADJ_PERM:    adjustLabel.setText("Permeability"); break;
	case MODE_ADJ_J:       adjustLabel.setText("Current"); break;
	case MODE_ADJ_MEDIUM:  adjustLabel.setText("Dielectric Constant"); break;
	case MODE_ADJ_MAG_DIR: adjustLabel.setText("Direction"); break;
	case MODE_ADJ_MAG_STR: adjustLabel.setText("Strength"); break;
	}
	adjustLabel.show();
	adjustBar.show();
	if (adjustSelectX1 == -1)
	    adjustBar.disable();
	else
	    adjustBar.enable();
	validate();
    }

    void doSetup() {
	t = 0;
	doClearAll();
	// don't use previous source positions, use defaults
	sourceCount = -1;
	filterCount = 0;
	sourceChooser.select(SRC_1S1F);
	setForceBar(10);
	brightnessBar.setValue(100);
	auxBar.setValue(1);
	setup = (Setup)
	    setupList.elementAt(setupChooser.getSelectedIndex());
	setup.select();
	setup.doSetupSources();
	calcBoundaries();
	setDamping();
    }

    void addMedium() {
	int i, j;
	for (i = 0; i != gridSizeX; i++)
	    for (j = gridSizeY/2; j != gridSizeY; j++)
		grid[i+gw*j].medium = mediumMax;
    }

    void addCondMedium(double cv) {
	conductFillRect(0, gridSizeY/2, gridSizeX-1, gridSizeY-1, cv);
    }

    void addResMedium() {
	int i, j;
	for (i = 0; i != gridSizeX; i++)
	    for (j = gridSizeY/2; j != gridSizeY; j++)
		grid[i+gw*j].resonant = true;
    }

    void addUniformField() {
	float v = 2f/windowHeight;
	int y;
	for (y = 0; y != gridSizeY; y++) {
	    grid[windowOffsetX+gw*y].jz = v;
	    grid[windowOffsetX+windowWidth-1+gw*y].jz = -v;
	}
    }

    void addSolenoid(int x1, int y1, int x2, int y2, double v) {
	int i;
	for (i = y1; i <= y2; i++) {
	    grid[x1+gw*i].jz = v;
	    grid[x2+gw*i].jz = -v;
	}
    }

    void addMagnet(int x1, int y1, int x2, int y2, double v) {
	int i, j;
	for (i = y1; i <= y2; i++)
	    for (j = x1; j <= x2; j++)
		grid[j+gw*i].my = (float) v;
    }

    void addMagnet(int x1, int y1, int x2, int y2, double vx, double vy) {
	int i, j;
	for (i = y1; i <= y2; i++)
	    for (j = x1; j <= x2; j++) {
		grid[j+gw*i].mx = (float) vx;
		grid[j+gw*i].my = (float) vy;
	    }
    }

    void setSources() {
	if (sourceCount > 0)
	    doSources(1, true);
	sourceMult = 1;
	int oldSCount = sourceCount;
	boolean oldPlane = sourcePlane;
	sourceFreqCount = 1;
	sourcePlane = (sourceChooser.getSelectedIndex() >= SRC_1S1F_PLANE);
	sourceWaveform = SWF_SIN;
	sourceCount = 1;
	switch (sourceChooser.getSelectedIndex()) {
	case 0: sourceCount = 0; break;
	case 2: sourceFreqCount = 2; break;
	case 3: sourceCount = 2; break;
	case 4: sourceCount = 2; sourceFreqCount = 2; break;
	case 5: sourceCount = 3; break;
	case 6: sourceCount = 4; break;
	case 7: sourceWaveform = SWF_PACKET; break;
	case 9: sourceFreqCount = 2; break;
	case 10: sourceCount = 2; break;
	case 11: sourceCount = sourceFreqCount = 2; break;
	case 12: sourceWaveform = SWF_PACKET; break;
	}
	if (sourceFreqCount >= 2) {
	    auxFunction = AUX_FREQ;
	    auxBar.setValue(forceBar.getValue());
	    if (sourceCount == 2)
		auxLabel.setText("Source 2 Frequency");
	    else
		auxLabel.setText("2nd Frequency");
	} else if (sourceCount == 2 || sourceCount == 4) {
	    auxFunction = AUX_PHASE;
	    auxBar.setValue(1);
	    auxLabel.setText("Phase Difference");
	} else {
	    auxFunction = AUX_NONE;
	    auxBar.hide();
	    auxLabel.hide();
	}
	if (auxFunction != AUX_NONE) {
	    auxBar.show();
	    auxLabel.show();
	}
	validate();
	
	if (sourcePlane) {
	    sourceCount *= 2;
	    if (!(oldPlane && oldSCount == sourceCount)) {
		int x2 = windowOffsetX+windowWidth-1;
		int y2 = windowOffsetY+windowHeight-1;
		sources[0] = new OscSource(windowOffsetX, windowOffsetY);
		sources[1] = new OscSource(x2, windowOffsetY);
		sources[2] = new OscSource(windowOffsetX, y2);
		sources[3] = new OscSource(x2, y2);
	    }
	} else if (!(oldSCount == sourceCount && !oldPlane)) {
	    sources[0] = new OscSource(gridSizeX/2, windowOffsetY+1);
	    sources[1] = new OscSource(gridSizeX/2, gridSizeY-windowOffsetY-2);
	    sources[2] = new OscSource(windowOffsetX+1, gridSizeY/2);
	    sources[3] = new OscSource(gridSizeX-windowOffsetX-2, gridSizeY/2);
	}
    }

    class OscSource {
	int x;
	int y;
	double v;
	OscSource(int xx, int yy) { x = xx; y = yy; }
	int getScreenX() {
	    return ((x-windowOffsetX) * winSize.width+winSize.width/2)
		/windowWidth;
	}
	int getScreenY() {
	    return ((y-windowOffsetY) * winSize.height+winSize.height/2)
		/windowHeight;
	}
    };

    class OscElement {
	// permeability of this square (1 = vacuum)
	float perm;
	// conductivity (0, vacuum, 1 = perfect)
	float conductivity;
	// permanent magnetization
	float mx, my;
	// current
	double jz;
	// electron position (for resonance)
	float epos;
	// damping (used to keep waves from reflecting back after going
	// off the screen)
	double damp;
	// z component of vector potential and its first derivative
	double az, dazdt;
	// dielectric strength (0 = none, up to mediumMax)
	int medium;
	// temp variable used to store color when drawing field lines
	int col;
	// true if we are on a boundary between media
	boolean boundary;
	// true if this is a gray square (some medium)
	boolean gray;
	// true if this is a resonant medium
	boolean resonant;
	int getType() {
	    if (perm < 1)
		return TYPE_DIAMAGNET;
	    else if (perm > 1)
		return TYPE_FERROMAGNET;
	    else if (mx != 0 || my != 0)
		return TYPE_MAGNET;
	    else if (medium > 0)
		return TYPE_MEDIUM;
	    else if (conductivity > 0)
		return TYPE_CONDUCTOR;
	    else if (jz != 0)
		return TYPE_CURRENT;
	    return TYPE_NONE;
	}
	boolean feelsForce() {
	    int t = getType();
	    return (t != TYPE_NONE && t != TYPE_MEDIUM);
	}
    };

    abstract class Setup {
	abstract String getName();
	void select() {}
	void deselect() {}
	void valueChanged(Scrollbar s) {}
	void doStep() {}
	void doSetupSources() { setSources(); }
	abstract Setup createNext();
	Setup() { }
    };

    class SingleSourceSetup extends Setup {
	String getName() { return "Single Source"; }
	void select() {
	    setForceBar(30);
	}
	Setup createNext() { return new DoubleSourceSetup(); }
    }
    class DoubleSourceSetup extends Setup {
	String getName() { return "Two Sources"; }
	void select() {
	    setForceBar(30);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = gridSizeY/2 - 8;
	    sources[1].y = gridSizeY/2 + 8;
	    sources[0].x = sources[1].x = gridSizeX/2;
	}
	Setup createNext() { return new PlaneWaveSetup(); }
    }
    class PlaneWaveSetup extends Setup {
	String getName() { return "Plane Wave"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    brightnessBar.setValue(225);
	    setForceBar(30);
	}
	Setup createNext() { return new IntersectingPlaneWavesSetup(); }
    }
    class IntersectingPlaneWavesSetup extends Setup {
	String getName() { return "Intersecting Planes"; }
	void select() {
	    brightnessBar.setValue(70);
	    setForceBar(34);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F_PLANE);
	    setSources();
	    sources[0].y = sources[1].y = windowOffsetY;
	    sources[0].x = windowOffsetX+1;
	    sources[2].x = sources[3].x = windowOffsetX;
	    sources[2].y = windowOffsetY+1;
	    sources[3].y = windowOffsetY+windowHeight-1;
	}
	Setup createNext() { return new SingleWireSetup(); }
    }
    class SingleWireSetup extends Setup {
	String getName() { return "Single Wire"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].jz =  1;
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new DoubleWireSetup(); }
    }
    class DoubleWireSetup extends Setup {
	String getName() { return "Wire Pair"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    grid[gridSizeX/2+gw*(gridSizeY/2-3)].jz =  1;
	    grid[gridSizeX/2+gw*(gridSizeY/2+3)].jz =  1;
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new DipoleWireSetup(); }
    }
    class DipoleWireSetup extends Setup {
	String getName() { return "Dipole Wire Pair"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    grid[gridSizeX/2+gw*(gridSizeY/2-3)].jz =  1;
	    grid[gridSizeX/2+gw*(gridSizeY/2+3)].jz = -1;
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new MagnetPairSetup(); }
    }
    class MagnetPairSetup extends Setup {
	String getName() { return "Magnet Pair"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addMagnet(gridSizeX/2-windowWidth/4-3, gridSizeY/2-2,
		      gridSizeX/2-windowWidth/4+3, gridSizeY/2+2, -.2);
	    addMagnet(gridSizeX/2+windowWidth/4-3, gridSizeY/2-2,
		      gridSizeX/2+windowWidth/4+3, gridSizeY/2+2, -.2);
	}
	Setup createNext() { return new MagnetPairOppSetup(); }
    }
    class MagnetPairOppSetup extends Setup {
	String getName() { return "Magnet Pair, Opp"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addMagnet(gridSizeX/2-windowWidth/4-3, gridSizeY/2-2,
		      gridSizeX/2-windowWidth/4+3, gridSizeY/2+2, -.2);
	    addMagnet(gridSizeX/2+windowWidth/4-3, gridSizeY/2-2,
		      gridSizeX/2+windowWidth/4+3, gridSizeY/2+2, .2);
	}
	Setup createNext() { return new MagnetPairStackedSetup(); }
    }
    class MagnetPairStackedSetup extends Setup {
	String getName() { return "Magnet Pair Stacked"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addMagnet(gridSizeX/2-3, gridSizeY/2-10,
		      gridSizeX/2+3, gridSizeY/2-5, -.2);
	    addMagnet(gridSizeX/2-3, gridSizeY/2+5,
		      gridSizeX/2+3, gridSizeY/2+10, -.2);
	}
	Setup createNext() { return new MagnetPairStackedOppSetup(); }
    }
    class MagnetPairStackedOppSetup extends Setup {
	String getName() { return "Magnet Pair Stacked Opp"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addMagnet(gridSizeX/2-3, gridSizeY/2-10,
			gridSizeX/2+3, gridSizeY/2- 5, .2);
	    addMagnet(gridSizeX/2-3, gridSizeY/2+ 5,
			gridSizeX/2+3, gridSizeY/2+10, -.2);
	}
	Setup createNext() { return new UniformFieldSetup(); }
    }
    class UniformFieldSetup extends Setup {
	String getName() { return "Uniform Field"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addUniformField();
	    brightnessBar.setValue(225);
	}
	Setup createNext() { return new ApertureFieldSetup(); }
    }
    class ApertureFieldSetup extends Setup {
	String getName() { return "Field Near Aperture"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    float v = 2f/windowHeight;
	    int y;
	    for (y = 0; y != gridSizeY; y++)
		grid[windowOffsetX+gw*y].jz = v;
	    int r1 = gridSizeX/2-windowWidth/6;
	    int r2 = gridSizeX/2+windowWidth/6;
	    conductDrawRect(r1, windowOffsetY,
			    r1, windowOffsetY+windowHeight-1, 1);
	    conductDrawRect(r1, gridSizeY/2-6,
			    r1, gridSizeY/2+6, 0);
	    brightnessBar.setValue(740);
	}
	Setup createNext() { return new SolenoidSetup(); }
    }
    class SolenoidSetup extends Setup {
	String getName() { return "Solenoid"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int h = windowHeight/3;
	    double v = 2./h;
	    int cy = gridSizeY/2;
	    addSolenoid(gridSizeX/2-3, cy-h, gridSizeX/2+3, cy+h, v);
	}
	Setup createNext() { return new ToroidalSolenoidSetup(); }
    }
    class ToroidalSolenoidSetup extends Setup {
	String getName() { return "Toroidal Solenoid"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addWireCircle(gridSizeX/2, gridSizeY/2, windowHeight/3,
			  -30/360., 0, 360);
	    addWireCircle(gridSizeX/2, gridSizeY/2, windowHeight/6,
			   30/360., 0, 360);
	    brightnessBar.setValue(400);
	}
	Setup createNext() { return new CylinderSetup(); }
    }
    class CylinderSetup extends Setup {
	String getName() { return "Sphere"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int res = 4;
	    int cx = gridSizeX/2 * res;
	    int cy = gridSizeY/2 * res;
	    int r = windowHeight/5 * res;
	    double my = -1./(r*r);
	    int x, y;
	    for (x = -r; x <= r; x++) {
		int yd = (int) Math.sqrt(r*r-x*x);
		for (y = -yd; y <= yd; y++)
		    grid[( x+cx)/res+gw*((y+cy)/res)].my += my;
	    }
	    brightnessBar.setValue(450);
	}
	Setup createNext() { return new ThickWireSetup(); }
    }
    class ThickWireSetup extends Setup {
	String getName() { return "Thick Wire"; }
	void select() {
	    int r = windowWidth/4;
	    addThickWire(gridSizeX/2, gridSizeY/2, r, 1./(r*r));
	    sourceChooser.select(SRC_NONE);
	}
	Setup createNext() { return new HoleInWire1Setup(); }
    }
    class HoleInWire1Setup extends Setup {
	String getName() { return "Hole In Wire 1"; }
	void select() {
	    int r = windowWidth/3;
	    double j = 1./(r*r);
	    // avoid rounding error
	    j = ((int) (j*1024))/1024.;
	    if (j == 0)
		j = 1/1024.;
	    addThickWire(gridSizeX/2, gridSizeY/2, r, j);
	    addThickWire(gridSizeX/2, gridSizeY/2, r*2/3, -j);
	    sourceChooser.select(SRC_NONE);
	    brightnessBar.setValue(450);
	}
	Setup createNext() { return new HoleInWire2Setup(); }
    }
    class HoleInWire2Setup extends Setup {
	String getName() { return "Hole In Wire 2"; }
	void select() {
	    int r = windowWidth/3;
	    double j = 1./(r*r);
	    // avoid rounding error
	    j = ((int) (j*1024))/1024.;
	    if (j == 0)
		j = 1/1024.;
	    addThickWire(gridSizeX/2, gridSizeY/2, r, j);
	    addThickWire(gridSizeX/2+r/4, gridSizeY/2, r/2, -j);
	    sourceChooser.select(SRC_NONE);
	    brightnessBar.setValue(450);
	}
	Setup createNext() { return new FerromagnetSetup(); }
    }
    class FerromagnetSetup extends Setup {
	String getName() { return "Ferromagnet"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addSolenoid(gridSizeX/2-2, gridSizeY/2-2,
			gridSizeX/2+2, gridSizeY/2+2, .4);
	    int x1 = windowOffsetX+3;
	    int x2 = windowOffsetX+windowWidth-4;
	    permFillRect(x1, gridSizeY/2+4,
			 x2, gridSizeY/2+8, 5);
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new DiamagnetSetup(); }
    }
    class DiamagnetSetup extends Setup {
	String getName() { return "Diamagnet"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addSolenoid(gridSizeX/2-2, gridSizeY/2-2,
			gridSizeX/2+2, gridSizeY/2+2, .4);
	    int x1 = windowOffsetX+3;
	    int x2 = windowOffsetX+windowWidth-4;
	    permFillRect(x1, gridSizeY/2+4,
			 x2, gridSizeY/2+8, .5);
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new MeissnerEffectSetup(); }
    }
    class MeissnerEffectSetup extends Setup {
	String getName() { return "Meissner Effect"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    addSolenoid(gridSizeX/2-2, gridSizeY/2-2,
			gridSizeX/2+2, gridSizeY/2+2, .4);
	    int x1 = windowOffsetX+3;
	    int x2 = windowOffsetX+windowWidth-4;
	    conductFillRect(x1, gridSizeY/2+4,
			    x2, gridSizeY/2+8, 1);
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new HorseshoeSetup(); }
    }
    class HorseshoeSetup extends Setup {
	String getName() { return "Horseshoe Magnet"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int r1 = windowHeight/3;
	    int r2 = windowHeight/6;
	    addWireCircle(gridSizeX/2, gridSizeY/2, r1, -30/360., 0, 180);
	    addWireCircle(gridSizeX/2, gridSizeY/2, r2,  30/360., 0, 180);
	    int y;
	    for (y = 0; y != r2; y++) {
		int x;
		for (x = -r1; x <= r1; x++)
		    grid[gridSizeX/2+x+gw*(gridSizeY/2+y)].jz =
			grid[gridSizeX/2+x+gw*(gridSizeY/2+y-1)].jz;
	    }
	    brightnessBar.setValue(400);
	}
	Setup createNext() { return new Horseshoe2Setup(); }
    }
    class Horseshoe2Setup extends HorseshoeSetup {
	String getName() { return "Horseshoe + Load"; }
	void select() {
	    super.select();
	    int r1 = windowHeight/3;
	    int r2 = windowHeight/6;
	    permFillRect(gridSizeX/2-r1-3, gridSizeY/2+r2,
			 gridSizeX/2+r1+3, gridSizeY/2+r2*2, 5);
	    brightnessBar.setValue(225);
	}
	Setup createNext() { return new MagneticShielding1Setup(); }
    }
    class MagneticShielding1Setup extends Setup {
	String getName() { return "Magnetic Shielding 1"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    addSolenoid(gridSizeX/2-2, gridSizeY/2-2,
			gridSizeX/2+2, gridSizeY/2+2, .4);
	    int x1 = windowOffsetX+3;
	    int x2 = windowOffsetX+windowWidth-4;
	    permDrawRect(x1, gridSizeY/2+4,
			 x2, gridSizeY/2+5, 10);
	}
	Setup createNext() { return new MagneticShielding2Setup(); }
    }
    class MagneticShielding2Setup extends Setup {
	String getName() { return "Magnetic Shielding 2"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    addSolenoid(gridSizeX/2-2, gridSizeY/2-2,
			gridSizeX/2+2, gridSizeY/2+2, .4);
	    for (i = 6; i <= 8; i++)
		permDrawRect(gridSizeX/2-i, gridSizeY/2-i,
			     gridSizeX/2+i, gridSizeY/2+i, 10);
	}
	Setup createNext() { return new MagneticShielding3Setup(); }
    }
    class MagneticShielding3Setup extends Setup {
	String getName() { return "Magnetic Shielding 3"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    addSolenoid(cx-1, cy-1, cx+1, cy+1, 4);
	    int th;
	    brightnessBar.setValue(340);
	    for (th = 0; th != 360; th += 3) {
		double r1 = 4.9;
		int x = cx+(int) (Math.cos(th*pi/180)*r1);
		int y = cy-(int) (Math.sin(th*pi/180)*r1);
		addPerm(x, y, 5);
		double r2 = 5.9;
		x = cx+(int) (Math.cos(th*pi/180)*r2);
		y = cy-(int) (Math.sin(th*pi/180)*r2);
		addPerm(x, y, 5);
	    }
	}
	Setup createNext() { return new MagneticShielding4Setup(); }
    }
    class MagneticShielding4Setup extends Setup {
	String getName() { return "Magnetic Shielding 4"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i;
	    for (i = 6; i <= 8; i++)
		permDrawRect(gridSizeX/2-i, gridSizeY/2-i,
			     gridSizeX/2+i, gridSizeY/2+i, 10);
	    addUniformField();
	    brightnessBar.setValue(250);
	}
	Setup createNext() { return new MagneticCircuit1Setup(); }
    }
    class MagneticCircuit1Setup extends Setup {
	String getName() { return "Magnetic Circuit 1"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i;
	    for (i = 6; i <= 9; i++) {
		permDrawRect(gridSizeX/2-i, gridSizeY/2-i,
			     gridSizeX/2+i, gridSizeY/2+i, 10);
	    }
	    addSolenoid(gridSizeX/2+5, gridSizeY/2-1,
			gridSizeX/2+10, gridSizeY/2+1, .2);
	}
	Setup createNext() { return new MagneticCircuit2Setup(); }
    }
    class MagneticCircuit2Setup extends MagneticCircuit1Setup {
	String getName() { return "Magnetic Circuit 2"; }
	void select() {
	    super.select();
	    permFillRect(gridSizeX/2-9, gridSizeY/2-1,
			 gridSizeX/2-6, gridSizeY/2+1, 1);
	}
	Setup createNext() { return new MonopoleAttemptSetup(); }
    }
    class MonopoleAttemptSetup extends Setup {
	String getName() { return "Monopole Attempt"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int w = windowWidth/5;
	    int i;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    int j;
	    for (j = 0; j != 3; j++) {
		for (i = -w+1; i < w; i++) {
		    grid[cx-w+gw*(cy+i)].mx = -1;
		    grid[cx+w+gw*(cy+i)].mx =  1;
		    grid[cx+i+gw*(cy-w)].my = -1;
		    grid[cx+i+gw*(cy+w)].my =  1;
		}
		w++;
	    }
	}
	Setup createNext() { return new QuadrupoleLensSetup(); }
    }
    class QuadrupoleLensSetup extends Setup {
	String getName() { return "Quadrupole Lens"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int x;
	    int w = gridSizeX/2-1;
	    int h = windowWidth/4;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    float str = 1/(float) h;
	    for (x = -w; x <= w; x++) {
		int yd = (int) Math.sqrt(x*x+h*h);
		int y;
		for (y = yd; y <= w; y++) {
		    grid[cx+x+gw*(cy+y)].my = -str;
		    grid[cx+x+gw*(cy-y)].my = str;
		    grid[cx+y+gw*(cy+x)].mx = str;
		    grid[cx-y+gw*(cy+x)].mx = -str;
		}
	    }
	}
	Setup createNext() { return new HalbachArraySetup(); }
    }
    class HalbachArraySetup extends Setup {
	String getName() { return "Halbach Array"; }
	void select() {
	    brightnessBar.setValue(80);
	    sourceChooser.select(SRC_NONE);
	    int sz = 5;
	    int y1 = gridSizeY/2-sz/2;
	    int y2 = gridSizeY/2+sz/2;
	    int fx = gridSizeX/2 -sz/2 -2*sz;
	    int s1 = sz-1;
	    addMagnet(fx, y1, fx+s1, y2, -.2,   0); fx += sz;
	    addMagnet(fx, y1, fx+s1, y2,   0, -.2); fx += sz;
	    addMagnet(fx, y1, fx+s1, y2,  .2,   0); fx += sz;
	    addMagnet(fx, y1, fx+s1, y2,   0,  .2); fx += sz;
	    addMagnet(fx, y1, fx+s1, y2, -.2,   0); fx += sz;
	}
	Setup createNext() { return new HalbachArray2Setup(); }
    }
    class HalbachArray2Setup extends Setup {
	String getName() { return "Halbach Array (long)"; }
	void select() {
	    brightnessBar.setValue(80);
	    sourceChooser.select(SRC_NONE);
	    int sz = 3;
	    int y1 = gridSizeY/2-1;
	    int y2 = gridSizeY/2+1;
	    int fx = windowOffsetX + (windowWidth-windowWidth/sz*sz)/2;
	    int s1 = sz-1;
	    int i;
	    double dx = -.2; double dy = 0;
	    for (i = 0; i != windowWidth/sz; i++) {
		addMagnet(fx, y1, fx+s1, y2, dx, dy);
		fx += sz;
		double dq = dx; dx = -dy; dy = dq;
	    }
	}
	Setup createNext() { return new HalbachArray3Setup(); }
    }
    class HalbachArray3Setup extends Setup {
	String getName() { return "Halbach Array (dipole)"; }
	void select() {
	    brightnessBar.setValue(47);
	    sourceChooser.select(SRC_NONE);
	    int i;
	    int r2 = windowWidth/3;
	    int r1 = r2/2;
	    int x, y;
	    for (x = -r2; x <= r2; x++)
		for (y = -r2; y <= r2; y++) {
		    double r = Math.sqrt(x*x+y*y);
		    if (r > r2+.9 || r < r1)
			continue;
		    double a = Math.atan2(y, x)*180/pi + 22.5 + 45;
		    if (a < 0)
			a += 360;
		    int ai = (int) (a/45);
		    float dq = ((ai & 2) == 0) ? .2f : -.2f;
		    float dx = 0, dy = 0;
		    if ((ai & 1) == 0)
			dx = dq;
		    else
			dy = dq;
		    grid[gridSizeX/2+x+gw*(gridSizeY/2+y)].mx = dx;
		    grid[gridSizeX/2+x+gw*(gridSizeY/2+y)].my = dy;
		}
	}
	Setup createNext() { return new HalbachArray4Setup(); }
    }
    class HalbachArray4Setup extends Setup {
	String getName() { return "Halbach Array (quadrupole)"; }
	void select() {
	    brightnessBar.setValue(255);
	    sourceChooser.select(SRC_NONE);
	    int i;
	    int r2 = windowWidth/3;
	    int r1 = r2*2/3;
	    int x, y;
	    for (x = -r2; x <= r2; x++)
		for (y = -r2; y <= r2; y++) {
		    double r = Math.sqrt(x*x+y*y);
		    if (r > r2+.9 || r < r1)
			continue;
		    double a = Math.atan2(y, x)*180/pi + 11.25;
		    if (a < 0)
			a += 360;
		    int ai = (int) (a/22.5);
		    double da = -pi/2 + (pi*3/2)*ai/4.;
		    float dx = (float) Math.cos(da);
		    float dy = (float) Math.sin(da);
		    float d0 = -.06f;
		    grid[gridSizeX/2+x+gw*(gridSizeY/2+y)].mx = dx*d0;
		    grid[gridSizeX/2+x+gw*(gridSizeY/2+y)].my = dy*d0;
		}
	}
	Setup createNext() { return new DielectricSetup(); }
    }
    class DielectricSetup extends Setup {
	String getName() { return "Dielectric"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PACKET);
	    addMedium();
	    setForceBar(4);
	    brightnessBar.setValue(1000);
	    noFilter();
	}
	Setup createNext() { return new ConductReflectSetup(); }
    }
    class ConductReflectSetup extends Setup {
	String getName() { return "Fair Conductor Reflection"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PACKET);
	    addCondMedium(.5);
	    setForceBar(4);
	    brightnessBar.setValue(800);
	}
	Setup createNext() { return new Conduct2ReflectSetup(); }
    }
    class Conduct2ReflectSetup extends Setup {
	String getName() { return "Poor Conductor Reflection"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PACKET);
	    addCondMedium(.1);
	    setForceBar(4);
	    brightnessBar.setValue(800);
	}
	Setup createNext() { return new SkinEffect1Setup(); }
    }
    class SkinEffect1Setup extends Setup {
	String getName() { return "Skin Effect 1"; }
	void select() {
	    sourceChooser.select(SRC_1S1F);
	    addCondMedium(.33);
	    setForceBar(6);
	    brightnessBar.setValue(800);
	}
	Setup createNext() { return new SkinEffect2Setup(); }
    }
    class SkinEffect2Setup extends Setup {
	String getName() { return "Skin Effect 2"; }
	void select() {
	    sourceChooser.select(SRC_1S1F);
	    addCondMedium(.33);
	    setForceBar(40);
	    brightnessBar.setValue(800);
	}
	Setup createNext() { return new ResonantAbsSetup(); }
    }
    class ResonantAbsSetup extends Setup {
	String getName() { return "Resonant Absorption"; }
	void select() {
	    addResMedium();
	    setForceBar(23);
	    brightnessBar.setValue(200);
	    noFilter();
	}
	Setup createNext() { return new Dispersion1Setup(); }
    }
    class Dispersion1Setup extends ResonantAbsSetup {
	String getName() { return "Dispersion 1"; }
	void select() {
	    super.select();
	    setForceBar(14);
	}
	Setup createNext() { return new Dispersion2Setup(); }
    }
    class Dispersion2Setup extends ResonantAbsSetup {
	String getName() { return "Dispersion 2"; }
	void select() {
	    super.select();
	    setForceBar(21);
	}
	Setup createNext() { return new Dispersion3Setup(); }
    }
    class Dispersion3Setup extends ResonantAbsSetup {
	String getName() { return "Dispersion 3"; }
	void select() {
	    super.select();
	    setForceBar(25);
	}
	Setup createNext() { return new Dispersion4Setup(); }
    }
    class Dispersion4Setup extends ResonantAbsSetup {
	String getName() { return "Dispersion 4"; }
	void select() {
	    super.select();
	    setForceBar(39);
	}
	Setup createNext() { return new DiffusionSetup(); }
    }
    class DiffusionSetup extends Setup {
	String getName() { return "Magnetic Diffusion"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    conductFillRect(1, 1, gridSizeX-2, gridSizeY-2, .2);
	    addConductor(gridSizeX/2, gridSizeY/2, 0);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].jz = 1;
	    brightnessBar.setValue(800);
	}
	Setup createNext() { return new OscRingSetup(); }
    }
    class OscRingSetup extends Setup {
	String getName() { return "Oscillating Ring"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = sources[1].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 4;
	    sources[1].x = gridSizeX/2 + 4;
	    auxBar.setValue(40);
	}
	void select() {
	    setForceBar(26);
	    brightnessBar.setValue(86);
	}
	Setup createNext() { return new OscRingPairSetup(); }
    }
    class OscRingPairSetup extends Setup {
	String getName() { return "Oscillating Ring Pair"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_4S1F);
	    setSources();
	    sources[0].y = sources[1].y = gridSizeY/2-2;
	    sources[2].y = sources[3].y = gridSizeY/2+2;
	    sources[0].x = sources[3].x = gridSizeX/2 - 2;
	    sources[1].x = sources[2].x = gridSizeX/2 + 2;
	    auxBar.setValue(40);
	}
	void select() {
	    setForceBar(26);
	    brightnessBar.setValue(86);
	}
	Setup createNext() { return new OscRingInductionSetup(); }
    }
    class OscRingInductionSetup extends Setup {
	String getName() { return "Ring Induction"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = sources[1].y = gridSizeY/2-2;
	    sources[0].x = gridSizeX/2 - 4;
	    sources[1].x = gridSizeX/2 + 4;
	    auxBar.setValue(40);
	}
	void select() {
	    setForceBar(12);
	    brightnessBar.setValue(140);
	    addConductor(gridSizeX/2-4, gridSizeY/2+2, .5);
	    addConductor(gridSizeX/2+4, gridSizeY/2+2, .5);
	}
	Setup createNext() { return new WireInductionSetup(); }
    }
    class WireInductionSetup extends Setup {
	String getName() { return "Wire Induction"; }
	void doSetupSources() {
	    setForceBar(12);
	    sourceChooser.select(SRC_1S1F);
	    setSources();
	    sources[0].y = gridSizeY/2-2;
	    sources[0].x = gridSizeX/2;
	}
	void select() {
	    brightnessBar.setValue(140);
	    addConductor(gridSizeX/2, gridSizeY/2+2, .5);
	}
	Setup createNext() { return new OscRingEddy1Setup(); }
    }
    class OscRingEddy1Setup extends OscRingSetup {
	String getName() { return "Ring + Fair Conductor"; }
	void select() {
	    brightnessBar.setValue(280);
	    setForceBar(3);
	    conductFillRect(0, gridSizeY/2+3,
			    gridSizeX-1, gridSizeY/2+5, .5);
	}
	Setup createNext() { return new OscRingEddy2Setup(); }
    }
    class OscRingEddy2Setup extends OscRingSetup {
	String getName() { return "Ring + Poor Conductor"; }
	void select() {
	    brightnessBar.setValue(280);
	    setForceBar(3);
	    conductFillRect(0, gridSizeY/2+3,
			    gridSizeX-1, gridSizeY/2+5, .1);
	}
	Setup createNext() { return new WireEddy1Setup(); }
    }
    class WireEddy1Setup extends Setup {
	String getName() { return "Wire + Fair Conductor"; }
	void doSetupSources() {
	    setForceBar(3);
	    sourceChooser.select(SRC_1S1F);
	    setSources();
	    sources[0].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2;
	}
	void select() {
	    brightnessBar.setValue(280);
	    conductFillRect(0, gridSizeY/2+3,
			    gridSizeX-1, gridSizeY/2+5, .5);
	}
	Setup createNext() { return new WireEddy2Setup(); }
    }
    class WireEddy2Setup extends WireEddy1Setup {
	String getName() { return "Wire + Poor Conductor"; }
	void select() {
	    brightnessBar.setValue(280);
	    conductFillRect(0, gridSizeY/2+3,
			    gridSizeX-1, gridSizeY/2+5, .1);
	}
	Setup createNext() { return new OscRingPermSetup(); }
    }
    class OscRingPermSetup extends Setup {
	String getName() { return "Rings + Ferromagnet"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = sources[1].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 4;
	    sources[1].x = gridSizeX/2 + 4;
	    auxBar.setValue(40);
	}
	void select() {
	    setForceBar(6);
	    brightnessBar.setValue(94);
	    addConductor(gridSizeX/2-4, gridSizeY/2+10, .5);
	    addConductor(gridSizeX/2+4, gridSizeY/2+10, .5);
	    addConductor(gridSizeX/2-4, gridSizeY/2-10, .5);
	    addConductor(gridSizeX/2+4, gridSizeY/2-10, .5);
	    permFillRect(gridSizeX/2-2, gridSizeY/2-1,
			 gridSizeX/2+2, windowOffsetY+windowHeight-1, 50);
	    conductFillRect(gridSizeX/2-2, gridSizeY/2-1,
			    gridSizeX/2+2, windowOffsetY+windowHeight-1, .05);
	}
	Setup createNext() { return new SolenoidOscSetup(); }
    }
    class SolenoidOscSetup extends Setup {
	String getName() { return "Osc. Solenoid"; }
	void select() {
	    sourceChooser.select(SRC_2S1F_PLANE);
	    setSources();
	    int h = windowHeight/3;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = gridSizeX/2-3;
	    sources[2].x = sources[3].x = gridSizeX/2+3;
	    sources[0].y = sources[2].y = cy-h;
	    sources[1].y = sources[3].y = cy+h;
	    auxBar.setValue(40);
	    setForceBar(9);
	}
	void doSetupSources() {}
	Setup createNext() { return new TransformerSetup(); }
    }
    class TransformerSetup extends SolenoidOscSetup {
	String getName() { return "Transformer"; }
	void select() {
	    super.select();
	    int h = windowHeight/3;
	    int cy = gridSizeY/2;
	    conductDrawRect(gridSizeX/2-5, cy-h, gridSizeX/2-5, cy+h, .9);
	    conductDrawRect(gridSizeX/2+5, cy-h, gridSizeX/2+5, cy+h, .9);
	    brightnessBar.setValue(340);
	}
	Setup createNext() { return new ToroidalSolenoidOscSetup(); }
    }
    class ToroidalSolenoidOscSetup extends Setup {
	String getName() { return "Osc Toroidal Solenoid"; }
	void select() {
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY;
	    sourceChooser.select(SRC_NONE);
	    brightnessBar.setValue(300);
	    setForceBar(8);
	}
	void doSetupSources() {}
	void doStep() {
	    double val = grid[windowOffsetX+gw*windowOffsetY].jz * 30;
	    int i, j;
	    for (i = 0; i != windowWidth; i++)
		for (j = 0; j != windowHeight; j++)
		    grid[i+windowOffsetX+gw*(j+windowOffsetY)].jz = 0;
	    addWireCircle(gridSizeX/2, gridSizeY/2, windowHeight/3,
			  -val/360., 0, 360);
	    addWireCircle(gridSizeX/2, gridSizeY/2, windowHeight/6,
			   val/360., 0, 360);
	}
	Setup createNext() { return new CoaxCableSetup(); }
    }
    class CoaxCableSetup extends Setup {
	String getName() { return "Coaxial Cable"; }
	void select() {
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY;
	    brightnessBar.setValue(300);
	    setForceBar(8);
	}
	void doSetupSources() {}
	void doStep() {
	    double val = grid[windowOffsetX+gw*windowOffsetY].jz * 30;
	    int i, j;
	    for (i = 0; i != windowWidth; i++)
		for (j = 0; j != windowHeight; j++)
		    grid[i+windowOffsetX+gw*(j+windowOffsetY)].jz = 0;
	    int sz = 3;
	    addWireCircle(gridSizeX/2, gridSizeY/2, sz, -val/360., 0, 360);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].jz = val/16;
	}
	Setup createNext() { return new CondInOscFieldSetup(); }
    }
    class CondInOscFieldSetup extends Setup {
	String getName() { return "Cond. in Osc. Field"; }
	void doSetupSources() {}
	void select() {
	    sourceChooser.select(SRC_2S1F_PLANE);
	    setSources();
	    int h = windowHeight/3;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = windowOffsetX;
	    sources[2].x = sources[3].x = windowOffsetX+windowWidth-1;
	    sources[0].y = sources[2].y = 0;
	    sources[1].y = sources[3].y = gridSizeY-1;
	    conductFillRect(gridSizeX/2-4, gridSizeY/2-4,
			    gridSizeX/2+4, gridSizeY/2+4, .4);
	    setForceBar(2);
	    auxBar.setValue(40);
	}
	Setup createNext() { return new MovingWireSetup(); }
    }
    class MovingWireSetup extends Setup {
	String getName() { return "Moving Wire"; }
	double y;
	int dir, delay;
	void select() {
	    sourceChooser.select(SRC_NONE);
	    y = windowOffsetY;
	    dir = 1;
	    delay = 0;
	    stopDelay = 200;
	    brightnessBar.setValue(200);
	}
	int stopDelay;
	void doStep() {
	    if (delay > 0) {
		delay--;
		filt();
		return;
	    }
	    int yi = (int) y;
	    int i;
	    for (i = 0; i != 2; i++) {
		int gi = gridSizeX/2+i+gw*yi;
		grid[gi].jz = 0;
		grid[gi+gw].jz = 0;
		grid[gi+gw+gw].jz = 0;
	    }
	    y += dir*.06;
	    int yi2 = (int) y;
	    if (yi != yi2) {
		if (yi2 == gridSizeY/2)
		    delay = stopDelay;
		if (yi2 == windowOffsetY ||
		    yi2 == windowOffsetY+windowHeight-3) {
		    dir = -dir;
		    delay = stopDelay;
		}
	    }
	    yi = yi2;
	    float yfrac = (float) (y-yi);
	    for (i = 0; i != 2; i++) {
		int gi = gridSizeX/2+i+gw*yi;
		grid[gi].jz = (1-yfrac)*.25;
		grid[gi+gw].jz = .25;
		grid[gi+gw+gw].jz = yfrac*.25;
	    }
	    filt();
	    calcBoundaries();
	}
	int filtstep = 0;
	void filt() {
	    // do filtering around the moving wire
	    int xi = gridSizeX/2;
	    int yi = (int) y;
	    int x, y;
	    int r = 10;
	    for (y = yi-r; y <= yi+r; y++)
		for (x = xi-r; x <= xi+r; x++) {
		    int gi = x+y*gw;
		    OscElement oe = grid[gi];
		    if (oe.jz != 0 || oe.conductivity > 0)
			continue;
		    double rr = Math.sqrt((y-yi)*(y-yi)+
					  (x-xi)*(x-xi));
		    double mult1 = 8+rr;
		    double mult2 = 4+mult1;
		    oe.az = (oe.az*mult1 + grid[gi-1].az + grid[gi+1].az +
			     grid[gi-gw].az + grid[gi+gw].az)/mult2;
		}
	}
	Setup createNext() { return new MovingWireTubeSetup(); }
    }
    class MovingWireTubeSetup extends MovingWireSetup {
	String getName() { return "Moving Wire in Tube"; }
	void select() {
	    super.select();
	    int w = 4;
	    conductFillRect(gridSizeX/2-w, windowOffsetY,
			    gridSizeX/2-w, windowOffsetY+windowHeight, .6);
	    w++;
	    conductFillRect(gridSizeX/2+w, windowOffsetY,
			    gridSizeX/2+w, windowOffsetY+windowHeight, .6);
	    stopDelay = 500;
	    brightnessBar.setValue(500);
	}
	Setup createNext() { return new MovingMagnetSetup(); }
    }
    class MovingMagnetSetup extends Setup {
	String getName() { return "Moving Magnet in Tube"; }
	double y;
	int dir, delay;
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int w = 5;
	    conductFillRect(gridSizeX/2-w, windowOffsetY,
			    gridSizeX/2-w, windowOffsetY+windowHeight, .6);
	    conductFillRect(gridSizeX/2+w, windowOffsetY,
			    gridSizeX/2+w, windowOffsetY+windowHeight, .6);
	    y = windowOffsetY;
	    dir = 1;
	    delay = 0;
	    brightnessBar.setValue(250);
	}
	void doStep() {
	    if (delay > 0) {
		delay--;
		filt();
		return;
	    }
	    int yi = (int) y;
	    int x, i;
	    for (x = -3; x <= 3; x++) {
		for (i = 0; i <= 2; i++)
		    grid[gridSizeX/2+x+gw*(yi+i)].my = 0;
	    }
	    y += dir*.06;
	    int yi2 = (int) y;
	    if (yi != yi2) {
		if (yi2 == gridSizeY/2)
		    delay = 500;
		if (yi2 == windowOffsetY ||
		    yi2 == windowOffsetY+windowHeight-3) {
		    dir = -dir;
		    delay = 500;
		}
	    }
	    yi = yi2;
	    float yfrac = (float) (y-yi);
	    for (x = -3; x <= 3; x++) {
		int gi = gridSizeX/2 + x + gw*yi;
		grid[gi].my = -(1-yfrac);
		grid[gi+gw].my = -1;
		grid[gi+gw+gw].my = -yfrac;
	    }
	    calcBoundaries();
	    /*for (x = -4; x <= 4; x++)
		for (i = -1; i <= 3; i++)
		grid[gridSizeX/2+x][yi+i].boundary = true;*/
	    filt();
	}
	int filtstep = 0;
	void filt() {
	    // run a filter around the moving wire
	    int xi = gridSizeX/2;
	    int yi = (int) y;
	    int x, y;
	    int r = 12;
	    double mult1 = 8;
	    double mult2 = 4+mult1;
	    for (y = yi-r; y <= yi+r; y++)
		for (x = xi-r; x <= xi+r; x++) {
		    int gi = x+gw*y;
		    OscElement oe = grid[gi];
		    if (oe.jz != 0 || oe.conductivity > 0)
			continue;
		    double jz = grid[gi-1].my - grid[gi+1].my +
			grid[gi+gw].mx - grid[gi-gw].mx;
		    if (jz != 0)
			continue;
		    oe.az = (oe.az*mult1 + grid[gi-1].az + grid[gi+1].az +
			     grid[gi-gw].az + grid[gi+gw].az)/mult2;
		}
	}
	Setup createNext() { return new RotatingMagnet1Setup(); }
    }
    class RotatingMagnet1Setup extends Setup {
	String getName() { return "Rotating Magnet 1"; }
	double mt;
	void select() {
	    sourceChooser.select(SRC_NONE);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].mx = 1;
	    calcBoundaries();
	    setForceBar(10);
	    mt = 0;
	    brightnessBar.setValue(500);
	}
	void doStep() {
	    mt += forceBar.getValue() * .003;
	    grid[gridSizeX/2+gw*(gridSizeY/2)].mx =
		(float) Math.cos(mt);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].my =
		(float) -Math.sin(mt);
	    doFilter();
	}
	Setup createNext() { return new RotatingMagnet2Setup(); }
    }
    class RotatingMagnet2Setup extends RotatingMagnet1Setup {
	String getName() { return "Rotating Magnet 2"; }
	void doStep() {
	    mt += forceBar.getValue() * .003;
	    grid[gridSizeX/2+gw*(gridSizeY/2)].mx =
		(float) Math.cos(mt);
	    grid[gridSizeX/2+gw*(gridSizeY/2)].my =
		(float) -Math.abs(Math.sin(mt));
	    doFilter();
	    brightnessBar.setValue(500);
	}
	Setup createNext() { return new Scattering1Setup(); }
    }
    class Scattering1Setup extends Setup {
	String getName() { return "Scattering 1"; }
	int ctr;
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    brightnessBar.setValue(100);
	    setForceBar(23);
	    int i, j;
	    for (i = gridSizeX/2-1; i <= gridSizeX/2+1; i++)
		for (j = gridSizeY/2-1; j <= gridSizeY/2+1; j++)
		    grid[i+gw*j].resonant = true;
	}
	void doStep() {
	    ctr++;
	    if (ctr >= 600 && ctr <= 700) {
		double c = (ctr-600)*.01;
		sourceMult = 1-c;
	    } else if (ctr >= 1100) {
		double c = (ctr-1100)*.01;
		sourceMult = c;
		if (ctr == 1200)
		    ctr = 0;
	    }
	}
	Setup createNext() { return new Scattering2Setup(); }
    }
    class Scattering2Setup extends Scattering1Setup {
	String getName() { return "Scattering 2"; }
	int ctr;
	void select() {
	    super.select();
	    setForceBar(16);
	}
	Setup createNext() { return new BigModeSetup(); }
    }
    class BigModeSetup extends Setup {
	String getName() { return "Big TM11 Mode"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i;
	    int n = windowWidth*3/4;
	    int x = windowOffsetX+windowWidth/2-n/2;
	    int y = windowOffsetY+windowHeight/2-n/2;
	    for (i = 1; i != 4; i++)
		conductDrawRect(x-i, y-i, x+n+i-1, y+n+i-1, 1);
	    setupMode(x, y, n, n, 1, 1);
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new OneByOneModesSetup(); }
    }
    void setupMode(int x, int y, int sx, int sy, int nx, int ny) {
	int i, j;
	for (i = 0; i != sx; i++)
	    for (j = 0; j != sy; j++) {
		grid[i+x+gw*(j+y)].az = 2*
		    (Math.sin(pi*nx*(i+1)/(sx+1))*
		     Math.sin(pi*ny*(j+1)/(sy+1)));
		grid[i+x+gw*(j+y)].dazdt = 0;
	    }
	noFilter();
    }
    class OneByOneModesSetup extends Setup {
	String getName() { return "TM11 Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    while (y + ny < windowHeight) {
		int nx = ((y+ny)*(windowWidth-8)/windowHeight)+6;
		int y1 = y + windowOffsetY;
		int x1 = windowOffsetX + 1;
		conductDrawRect(x1-1, y1-1, x1+nx, y1+ny, 1);
		setupMode(x1, y1, nx, ny, 1, 1);
		y += ny+2;
	    }
	}
	Setup createNext() { return new OneByNModesSetup(); }
    }
    class OneByNModesSetup extends Setup {
	String getName() { return "TMn1 Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 8;
	    int nx = windowWidth-2;
	    int mode = 1;
	    while (y + ny < windowHeight) {
		int y1 = y + windowOffsetY;
		int x1 = windowOffsetX + 1;
		conductDrawRect(x1-1, y1-1, x1+nx, y1+ny, 1);
		setupMode(x1, y1, nx, ny, mode, 1);
		y += ny+2;
		mode++;
	    }
	}
	Setup createNext() { return new NByNModesSetup(); }
    }
    class NByNModesSetup extends Setup {
	String getName() { return "TMnn Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int modex, modey;
	    int maxmode = 3;
	    if (resBar.getValue() >= 70)
		maxmode++;
	    if (resBar.getValue() >= 100)
		maxmode++;
	    int ny = windowHeight/maxmode-2;
	    int nx = windowWidth/maxmode-2;
	    for (modex = 1; modex <= maxmode; modex++)
		for (modey = 1; modey <= maxmode; modey++) {
		    int x1 = windowOffsetX + 1 + (ny+2)*(modey-1);
		    int y1 = windowOffsetY + 1 + (nx+2)*(modex-1);
		    conductDrawRect(x1-1, y1-1, x1+nx, y1+ny, 1);
		    setupMode(x1, y1, nx, ny, modex, modey);
		}
	}
	Setup createNext() { return new OneByNModeCombosSetup(); }
    }
    class OneByNModeCombosSetup extends Setup {
	String getName() { return "TMn1 Mode Combos"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 8;
	    int nx = windowWidth-2;
	    while (y + ny < windowHeight) {
		int mode1 = getrand(8)+1;
		int mode2;
		do
		    mode2 = getrand(8)+1;
		while (mode1 == mode2);
		int y1 = y + windowOffsetY;
		int x1 = windowOffsetX + 1;
		conductDrawRect(x1-1, y1-1, x1+nx, y1+ny, 1);
		for (i = 0; i != nx; i++)
		    for (j = 0; j != ny; j++) {
			grid[i+x1+gw*(j+y1)].az = (float) 2*
			    (Math.sin(mode1*pi*(i+1)/(nx+1))*
			     Math.sin(pi*(j+1)/(ny+1))*.5 +
			     Math.sin(mode2*pi*(i+1)/(nx+1))*
			     Math.sin(pi*(j+1)/(ny+1))*.5);
			grid[i+x1+gw*(j+y1)].dazdt = 0;
		    }
		y += ny+2;
	    }
	    noFilter();
	}
	Setup createNext() { return new NByNModeCombosSetup(); }
    }
    class NByNModeCombosSetup extends Setup {
	String getName() { return "TMnn Mode Combos"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int maxmode = 2;
	    if (resBar.getValue() >= 70)
		maxmode++;
	    if (resBar.getValue() >= 100)
		maxmode++;
	    int ny = windowHeight/maxmode-2;
	    int nx = windowWidth/maxmode-2;
	    int gx, gy;
	    for (gx = 1; gx <= maxmode; gx++)
		for (gy = 1; gy <= maxmode; gy++) {
		    int mode1x = getrand(4)+1;
		    int mode1y = getrand(4)+1;
		    int mode2x, mode2y;
		    do {
			mode2x = getrand(4)+1;
			mode2y = getrand(4)+1;
		    } while (mode1x == mode2x && mode1y == mode2y);
		    int x1 = windowOffsetX + 1 + (ny+2)*(gx-1);
		    int y1 = windowOffsetY + 1 + (nx+2)*(gy-1);
		    conductDrawRect(x1-1, y1-1, x1+nx, y1+ny, 1);
		    for (i = 0; i != nx; i++)
			for (j = 0; j != ny; j++) {
			    grid[i+x1+gw*(j+y1)].az = 2*
				(Math.sin(mode1x*pi*(i+1)/(nx+1))*
				 Math.sin(mode1y*pi*(j+1)/(ny+1))*.5 +
				 Math.sin(mode2x*pi*(i+1)/(nx+1))*
				 Math.sin(mode2y*pi*(j+1)/(ny+1))*.5);
			    grid[i+x1+gw*(j+y1)].dazdt = 0;
			}
		}
	    noFilter();
	}
	Setup createNext() { return new TriangleModesSetup(); }
    }
    class TriangleModesSetup extends Setup {
	String getName() { return "Triangle Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    for (i = 0; i != 2; i++)
		for (j = 0; j != 2; j++) {
		    int x = windowOffsetX+windowWidth*i/2+1;
		    int y = windowOffsetY+windowHeight*j/2+1;
		    int w = windowWidth/2-2;
		    int h = windowHeight/2-2;
		    int k;
		    for (k = 0; k != w; k++)
			conductDrawRect(x+k+1, y+k, x+w, y+k, 1);
		    conductDrawRect(x-1, y-1, x+w, y+h, 1);
		    int mx = 0, my = 0;
		    switch (j*2+i) {
		    case 0: mx = 1; my = 2; break;
		    case 1: mx = 1; my = 3; break;
		    case 2: mx = 2; my = 3; break;
		    case 3: mx = 1; my = 4; break;
		    }
		    int xi, yi;
		    for (yi = 0; yi != h; yi++) {
			for (xi = 0; xi <= yi; xi++)
			    grid[x+xi+gw*(y+yi)].az =
				(Math.sin(mx*pi*(xi+1)/(w+2))*
				 Math.sin(my*pi*(yi+2)/(h+2)) -
				 Math.sin(my*pi*(xi+1)/(w+2))*
				 Math.sin(mx*pi*(yi+2)/(h+2)));
			    grid[x+xi+gw*(y+yi)].dazdt = 0;
			}
		}
	    brightnessBar.setValue(114);
	    noFilter();
	}
	Setup createNext() { return new CircleModes1Setup(); }
    }
    class CircleModes1Setup extends Setup {
	String getName() { return "Circular Modes 1"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    for (i = 0; i != 2; i++)
		for (j = 0; j != 2; j++) {
		    int x = windowOffsetX+windowWidth*i/2+1;
		    int y = windowOffsetY+windowHeight*j/2;
		    int w = windowWidth/2-2;
		    int h = windowHeight/2-2;
		    conductFillRect(x-1, y-1, x+w+1, y+h+1, 1);
		    int k, r = w/2;
		    double jj[] = new double[3];
		    double omega = zeroj(i, j+1)/r;
		    double mult = 1;
		    switch (j*2+i) {
		    case 1: mult = 1/.6; break;
		    case 2: mult = 2; break;
		    case 3: mult = 5/.6; break;
		    }
		    for (k = -r; k <= r; k++) {
			int yy = (int) Math.sqrt(r*r-k*k-.00001);
			conductFillRect(x+r+k, y+r-yy, x+r+k, y+r+yy, 0);
			int l;
			for (l = -yy; l <= yy; l++) {
			    double rr = Math.sqrt(k*k+l*l);
			    double r0 = rr*omega;
			    double angfunc = (i == 0) ? 1 : l/rr;
			    if (rr == 0)
				angfunc = (i == 0) ? 1 : 0;
			    bess(i, r0, jj);
			    grid[x+r+k+gw*(y+r+l)].az = jj[i+1]*angfunc*mult;
			}
		    }
		}
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new CircleModes2Setup(); }
    }
    class CircleModes2Setup extends Setup {
	String getName() { return "Circular Modes 2"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int x = windowOffsetX+1;
	    int y = windowOffsetY+1;
	    int w = windowWidth-2;
	    int h = windowHeight-2;
	    conductFillRect(x-1, y-1, x+w+1, y+h+1, 1);
	    int k, r = w/2;
	    double jj[] = new double[3];
	    double omega = zeroj(1, 1)/r;
	    double tmult = 2*r/16.;
	    for (k = -r; k <= r; k++) {
		int yy = (int) Math.sqrt(r*r-k*k-.00001);
		conductFillRect(x+r+k, y+r-yy, x+r+k, y+r+yy, 0);
		int l;
		for (l = -yy; l <= yy; l++) {
		    double rr = Math.sqrt(k*k+l*l);
		    double r0 = rr*omega;
		    double angfunc1 = l/rr;
		    double angfunc2 = k/rr;
		    if (rr == 0)
			angfunc1 = angfunc2 = 0;
		    bess(1, r0, jj);
		    grid[x+r+k+gw*(y+r+l)].az = jj[2]*angfunc1*tmult;
		    grid[x+r+k+gw*(y+r+l)].dazdt = jj[2]*angfunc2;
		}
	    }
	    brightnessBar.setValue(200);
	}
	Setup createNext() { return new Waveguides1Setup(); }
    }

    double zeroj( int m_order, int n_zero) {
	// Zeros of the Bessel function J(x)
	// Inputs
	//   m_order   Order of the Bessel function
	//   n_zero    Index of the zero (first, second, etc.)
	// Output
	//   z         The "n_zero"th zero of the Bessel function
	
	//* Use asymtotic formula for initial guess
	double beta = (n_zero + 0.5*m_order - 0.25)*(3.141592654);
	double mu = 4*m_order*m_order;
	double beta8 = 8*beta;
	double z = beta - (mu-1)/beta8 
	    - 4*(mu-1)*(7*mu-31)/(3*beta8*beta8*beta8);
	
	//* Use Newton's method to locate the root
	double jj[] = new double[m_order+3];
	int i;  double deriv;
	for( i=1; i<=5; i++ ) {
	    bess( m_order+1, z, jj );  // Remember j(1) is J_0(z)     
	    // Use the recursion relation to evaluate derivative
	    deriv = -jj[m_order+2] + m_order/z * jj[m_order+1];
	    z -= jj[m_order+1]/deriv;  // Newton's root finding  
	}
	return(z);
    }

    void bess( int m_max, double x, double jj[] ) {
	// Bessel function
	// Inputs
	//    m_max  Largest desired order
	//    x = Value at which Bessel function J(x) is evaluated
	// Output
	//    jj = Vector of J(x) for order m = 0, 1, ..., m_max
	
	//* Perform downward recursion from initial guess
	int maxmx = (m_max > x) ? m_max : ((int)x);  // Max(m,x)
	// Recursion is downward from m_top (which is even)
	int m_top = 2*((int)( (maxmx+15)/2 + 1 ));   
	double j[] = new double[m_top+2];
	j[m_top+1] = 0.0;
	j[m_top] = 1.0;
	double tinyNumber = 1e-16;
	int m;
	for( m=m_top-2; m>=0; m--)       // Downward recursion
	    j[m+1] = 2*(m+1)/(x+tinyNumber)*j[m+2] - j[m+3];
	
	//* Normalize using identity and return requested values
	double norm = j[1];        // NOTE: Be careful, m=0,1,... but
	for( m=2; m<=m_top; m+=2 ) // vector goes j(1),j(2),...
	    norm += 2*j[m+1];
	for( m=0; m<=m_max; m++ )  // Send back only the values for
	    jj[m+1] = j[m+1]/norm;   // m=0,...,m_max and discard values
    }                            // for m=m_max+1,...,m_top


    class Waveguides1Setup extends Setup {
	String getName() { return "Waveguides 1"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 5;
	    int y1 = windowOffsetY + 2;
	    while (x + nx < windowWidth) {
		int x1 = x + windowOffsetX;
		conductDrawRect(x1-1,  y1-1, x1-1,  gridSizeY-1, 1);
		conductDrawRect(x1+nx, y1-1, x1+nx, gridSizeY-1, 1);
		nx += 2;
		x += nx;
	    }
	    conductDrawRect(x-1+windowOffsetX, y1-1, gridSizeX-1, y1-1, 1);
	    brightnessBar.setValue(140);
	    setForceBar(28);
	}
	Setup createNext() { return new Waveguides2Setup(); }
    }
    class Waveguides2Setup extends Waveguides1Setup {
	String getName() { return "Waveguides 2"; }
	void select() {
	    super.select();
	    setForceBar(17);
	}
	Setup createNext() { return new Waveguides3Setup(); }
    }
    class Waveguides3Setup extends Setup {
	String getName() { return "Waveguides 3"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 8;
	    int y1 = windowOffsetY + 2;
	    conductDrawRect(windowOffsetX+1,             y1-1,
			    windowOffsetX+windowWidth-1, y1-1, 1);
	    x = 1;
	    j = 0;
	    while (x + nx < windowWidth && j < nx) {
		int x1 = x + windowOffsetX;
		conductDrawRect(x1-1,  y1-1, x1-1,  gridSizeY-1, 1);
		conductDrawRect(x1+nx, y1-1, x1+nx, gridSizeY-1, 1);
		addConductor(x1+j++, y1-1, 0);
		x += nx+2;
		if (resBar.getValue() == 32 && j == 2)
		    j++;
	    }
	    brightnessBar.setValue(1000);
	    setForceBar(32);
	}
	Setup createNext() { return new Waveguides4Setup(); }
    }
    class Waveguides4Setup extends Waveguides3Setup {
	String getName() { return "Waveguides 4"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i;
	    int x = 1;
	    int nx = 9;
	    int y1 = windowOffsetY + 2;
	    int ny = windowHeight-1;
	    x = 1;
	    while (x + nx < windowWidth) {
		int x1 = x + windowOffsetX;
		conductDrawRect(x1-1,  y1-1, x1-1,  y1+ny-2, 1);
		conductDrawRect(x1+nx, y1-1, x1+nx, y1+ny-2, 1);
		x += nx+2;
	    }
	    brightnessBar.setValue(480);
	    setForceBar(40);
	}
	void doStep() {
	    int y = windowOffsetY;
	    int nx = 9;
	    int x = 1;
	    int g = 1;
	    while (x + nx < windowWidth) {
		int x1 = x + windowOffsetX;
		int j;
		int n1 = 1;
		int n2 = 1;
		switch (g) {
		case 1: n1 = n2 = 1; break;
		case 2: n1 = n2 = 2; break;
 		case 3: n1 = n2 = 3; break;
		case 4: n1 = 1; n2 = 2; break;
		case 5: n1 = 1; n2 = 3; break;
		case 6: n1 = 2; n2 = 3; break;
		default: n1 = n2 = 0; break;
		}
		for (j = 0; j != nx; j++) {
		    grid[x1+j+gw*y].az = grid[x1+j+gw*y].jz *
			(Math.sin(pi*n1*(j+1)/(nx+1)) +
			 Math.sin(pi*n2*(j+1)/(nx+1)));
		    grid[x1+j+gw*y].jz = 0;
		}
		x += nx+2;
		g++;
	    }
	}
	Setup createNext() { return new ResonantCavitiesSetup(); }
    }

    class ResonantCavitiesSetup extends Setup {
	String getName() { return "Resonant Cavities"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 5;
	    int y1 = windowOffsetY + 11;
	    while (x + nx < windowWidth) {
		int ny = ((x+nx)*(windowHeight-18)/windowWidth)+6;
		int x1 = x + windowOffsetX;
		for (i = 0; i != ny+2; i++) {
		    addConductor(x1- 1, y1+i-1, 1);
		    addConductor(x1+nx, y1+i-1, 1);
		}
		for (j = 0; j != nx+2; j++) {
		    addConductor(x1+j-1, y1-1, 1);
		    addConductor(x1+j-1, y1+ny, 1);
		}
		addConductor(x1+nx/2, y1-1, 0);
		x += nx+2;
	    }
	    x--;
	    for (; x < windowWidth; x++)
		addConductor(x+windowOffsetX, y1-1, 1);
	    brightnessBar.setValue(300);
	    setForceBar(38);
	}
	Setup createNext() { return new SingleSlitSetup(); }
    }

    class SingleSlitSetup extends Setup {
	String getName() { return "Single Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = gridSizeX/2;
	    int y = windowOffsetY+4;
	    conductFillRect(0, y, gridSizeX-1, y+2, 1);
	    conductFillRect(x-7, y, x+7, y+2, 0);
	    brightnessBar.setValue(275);
	    setForceBar(35);
	}
	Setup createNext() { return new DoubleSlitSetup(); }
    }
    class DoubleSlitSetup extends Setup {
	String getName() { return "Double Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = gridSizeX/2;
	    int y = windowOffsetY+4;
	    conductFillRect(0, y, gridSizeX-1, y+2, 1);
	    conductFillRect(x-7, y, x-5, y+2, 0);
	    conductFillRect(x+5, y, x+7, y+2, 0);
	    brightnessBar.setValue(366);
	    setForceBar(35);
	}
	Setup createNext() { return new TripleSlitSetup(); }
    }
    class TripleSlitSetup extends Setup {
	String getName() { return "Triple Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = gridSizeX/2;
	    int y = windowOffsetY+4;
	    conductFillRect(0, y, gridSizeX-1, y+2, 1);
	    conductFillRect(x-13, y, x-11, y+2, 0);
	    conductFillRect(x -1, y, x +1, y+2, 0);
	    conductFillRect(x+11, y, x+13, y+2, 0);
	    brightnessBar.setValue(310);
	    setForceBar(35);
	}
	Setup createNext() { return new ObstacleSetup(); }
    }
    class ObstacleSetup extends Setup {
	String getName() { return "Obstacle"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = gridSizeX/2;
	    int y = windowOffsetY+6;
	    conductFillRect(x-7, y, x+7, y+2, 1);
	    brightnessBar.setValue(400);
	    setForceBar(35);
	}
	Setup createNext() { return new HalfPlaneSetup(); }
    }
    class HalfPlaneSetup extends Setup {
	String getName() { return "Half Plane"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = windowOffsetX+windowWidth/2;
	    int i;
	    conductFillRect(windowOffsetX+windowWidth*2/3, windowOffsetY+3,
			    windowOffsetY+windowWidth-1, windowOffsetY+5, 1);
	    brightnessBar.setValue(150);
	    setForceBar(35);
	}
	Setup createNext() { return new LloydsMirrorSetup(); }
    }
    class LloydsMirrorSetup extends Setup {
	String getName() { return "Lloyd's Mirror"; }
	void select() {
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY + windowHeight*3/4;
	    brightnessBar.setValue(120);
	    setForceBar(40);
	    conductDrawRect(0, windowOffsetY+windowHeight-1,
			    gridSizeX-1, windowOffsetY+windowHeight-1, 1);
	}
	void doSetupSources() {}
	Setup createNext() { return null; }
    }

    void addThickWire(int cx, int cy, int r, double j) {
	int res = 4;
	cx *= res;
	cy *= res;
	r *= res;
	j /= (res*res);
	int x, y;
	for (x = -r; x <= r; x++) {
	    int yd = (int) Math.sqrt(r*r-x*x);
	    for (y = -yd; y <= yd; y++)
		grid[( x+cx)/res+gw*((y+cy)/res)].jz += j;
	}
    }

    void addWireCircle(int cx, int cy, int r, double j, int deg1, int deg2) {
	int res = 4;
	r *= res;
	j /= (res*res);
	int th;
	for (th = deg1; th != deg2; th++) {
	    int x = cx+(int) (Math.cos(th*pi/180)*r/res);
	    int y = cy-(int) (Math.sin(th*pi/180)*r/res);
	    grid[x+gw*y].jz += j;
	}
    }

    void addConductor(int x, int y, double cv) {
	OscElement oe = grid[x+gw*y];
	oe.conductivity = (float) cv;
	if (cv == 1)
	    oe.az = oe.dazdt = 0;
    }
    void addPerm(int x, int y, double pm) {
	OscElement oe = grid[x+gw*y];
	oe.perm = (float) pm;
	oe.conductivity = (pm == 1) ? 0 : .5f;
    }
    void conductFillRect(int x, int y, int x2, int y2, double cv) {
	int i, j;
	for (i = x; i <= x2; i++)
	    for (j = y; j <= y2; j++)
		addConductor(i, j, (float) cv);
    }
    void conductDrawRect(int x, int y, int x2, int y2, double cvd) {
	int i;
	float cv = (float) cvd;
	for (i = x; i <= x2; i++) {
	    addConductor(i, y, cv);
	    addConductor(i, y2, cv);
	}
	for (i = y; i <= y2; i++) {
	    addConductor(x,  i, cv);
	    addConductor(x2, i, cv);
	}
    }
    void permDrawRect(int x, int y, int x2, int y2, double pm) {
	int i;
	for (i = x; i <= x2; i++) {
	    addPerm(i, y,  pm);
	    addPerm(i, y2, pm);
	}
	for (i = y; i <= y2; i++) {
	    addPerm(x,  i, pm);
	    addPerm(x2, i, pm);
	}
    }
    void permFillRect(int x, int y, int x2, int y2, double pm) {
	int i, j;
	for (i = x; i <= x2; i++)
	    for (j = y; j <= y2; j++)
		addPerm(i, j, pm);
    }
}