// EMWave1.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.Math;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.awt.event.*;

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

class EMWave1Layout implements LayoutManager {
    public EMWave1Layout() {}
    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 EMWave1 extends Applet implements ComponentListener {
    static EMWave1Frame 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 EMWave1Frame(null);
        ogf.init();
    }

    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new EMWave1Frame(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 EMWave1Frame 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 "EMWave1 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 EBrightnessBar;
    Scrollbar lineDensityBar;
    Scrollbar auxBar;
    Label auxLabel;
    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_M_POS = 1;
    static final int MODE_M_NEG = 2;
    static final int MODE_CLEAR = 3;
    
    static final int VIEW_E = 0;
    static final int VIEW_E_LINES = 1;
    static final int VIEW_B = 2;
    static final int VIEW_Q = 3;
    static final int VIEW_J = 4;
    static final int VIEW_E_B = 5;
    static final int VIEW_E_LINES_B = 6;
    static final int VIEW_E_B_Q_J = 7;
    static final int VIEW_E_LINES_B_Q_J = 8;
    static final int VIEW_E_Q = 9;
    static final int VIEW_E_LINES_Q = 10;
    static final int VIEW_E_B_J = 11;
    static final int VIEW_E_LINES_B_J = 12;
    static final int VIEW_POYNTING = 13;
    static final int VIEW_ENERGY = 14;
    static final int VIEW_POYNTING_ENERGY = 15;
    static final int VIEW_DISP_CUR = 16;
    static final int VIEW_DISP_J = 17;
    static final int VIEW_DISP_J_B = 18;
    static final int VIEW_DB_DT = 19;
    static final int VIEW_NONE = -1;
    static final int TYPE_CONDUCTOR = 1;
    static final int TYPE_CURRENT = 2;
    static final int TYPE_NONE = 0;
    int dragX, dragY;
    int selectedSource;
    int forceBarValue;
    boolean dragging;
    boolean dragClear;
    boolean dragSet;
    double t;
    int pause;
    MemoryImageSource imageSource;
    int pixels[];
    int sourceCount = -1;
    int sourceType;
    int auxFunction;
    boolean sourcePacket = false;
    static final int SRC_NONE = 0;
    static final int SRC_1PLANE = 1;
    static final int SRC_2PLANE = 2;
    static final int SRC_1PLANE_PACKET = 3;
    static final int SRC_1ANTENNA = 4;
    static final int SRC_2ANTENNA = 5;
    static final int SRC_1LOOP = 6;
    static final int SRC_1LOOP_PACKET = 7;
    static final int SRC_PLANE = 1;
    static final int SRC_ANTENNA = 2;
    static final int SRC_LOOP = 3;

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

    EMWave1Frame(EMWave1 a) {
	super("TE Electrodynamics Applet v1.4a");
	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 PlaneWaveSetup();
	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");
	int res = 40; // was 34

	sources = new OscSource[4];
	setLayout(new EMWave1Layout());
	cv = new EMWave1Canvas(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());
	setupChooser.select(4);
	setup = (Setup) setupList.elementAt(4);
	setupChooser.addItemListener(this);
	add(setupChooser);

	sourceChooser = new Choice();
	sourceChooser.add("No Sources");
	sourceChooser.add("1 Plane Src");
	sourceChooser.add("2 Plane Srcs");
	sourceChooser.add("1 Plane Src (Packets)");
	sourceChooser.add("1 Antenna Src");
	sourceChooser.add("2 Antenna Srcs");
	sourceChooser.add("1 Loop Src");
	sourceChooser.add("1 Loop Src (Packets)");
	sourceChooser.select(SRC_1PLANE);
	sourceChooser.addItemListener(this);
	add(sourceChooser);

	modeChooser = new Choice();
	modeChooser.add("Mouse = Add Perf. Conductor");
	modeChooser.add("Mouse = Clear");
	modeChooser.addItemListener(this);
	add(modeChooser);

	viewChooser = new Choice();
	viewChooser.add("Show Electric Field (E)");
	viewChooser.add("Show E lines");
	viewChooser.add("Show Magnetic Field (B)");
	viewChooser.add("Show Charge (rho)");
	viewChooser.add("Show Current (j)");
	viewChooser.add("Show E/B");
	viewChooser.add("Show E lines/B");
	viewChooser.add("Show E/B/rho/j");
	viewChooser.add("Show E lines/B/rho/j");
	viewChooser.add("Show E/rho");
	viewChooser.add("Show E lines/rho");
	viewChooser.add("Show E/B/j");
	viewChooser.add("Show E lines/B/j");
	viewChooser.add("Show Poynting Vector");
	viewChooser.add("Show Energy Density");
	viewChooser.add("Show Poynting/Energy");
	viewChooser.add("Show Disp Current");
	viewChooser.add("Show Disp + j");
	viewChooser.add("Show Disp + j/B");
	viewChooser.add("Show dB/dt");
	viewChooser.addItemListener(this);
	add(viewChooser);
	viewChooser.select(VIEW_E_B_Q_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, res, 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("E Field Brightness", Label.CENTER));
	add(EBrightnessBar = new Scrollbar(Scrollbar.HORIZONTAL,
				    100, 1, 1, 800));
	EBrightnessBar.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(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;
	grid = new OscElement[gridSizeXY];
	int i, j;
	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;
	for (i = 0; i != windowOffsetX; i++)
	    for (j = 0; j != gridSizeX; j++) {
		double da = Math.exp(-(windowOffsetX-i)*.0022);
		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;
	for (x = 0; x != gridSizeXY; x++)
	    grid[x].az = grid[x].dazdt = 0;
	t = 0;
	doFilter();
    }

    void doClearAll() {
	int x, y;
	for (x = 0; x != gridSizeXY; x++) {
	    grid[x].jx = grid[x].jy = 0;
	    grid[x].az = grid[x].dazdt = 0;
	    grid[x].boundary = false;
	    grid[x].gray = false;
	    grid[x].conductor = false;
	    grid[x].col = 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+y*gw].conductor = grid[x+windowOffsetY*gw].conductor;
		grid[x+gw*(gridSizeY-y-1)].conductor =
		    grid[x+gw*(gridSizeY-windowOffsetY-1)].conductor;
	    }
	for (y = 0; y < gridSizeY; y++)
	    for (x = 0; x < windowOffsetX; x++) {
		grid[x+gw*y].conductor = grid[windowOffsetX+gw*y].conductor;
		grid[gridSizeX-x-1+gw*y].conductor =
		    grid[gridSizeX-windowOffsetX-1+gw*y].conductor;
	    }
	for (x = 1; x < gridSizeX-1; x++)
	    for (y = 1; y < gridSizeY-1; y++) {
		int gi = x+gw*y;
		OscElement oe = grid[gi];
		boolean cond = oe.conductor;
		OscElement e1 = grid[gi-1];
		OscElement e2 = grid[gi+1];
		OscElement e3 = grid[gi-gw];
		OscElement e4 = grid[gi+gw];

		oe.gray = oe.conductor;
		    
		// mark all grid squares where the permeability, medium,
		// or magnetization is different from one of the neighbors
		if (e1.conductor != cond || e2.conductor != cond ||
		    e3.conductor != cond || e4.conductor != cond) {
		    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 updateEMWave1(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; // 5; //speedBar.getValue();
	    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*tadd;
		double forcecoef = 1;
		int curMedium = 0;
		OscElement oew, oee, oen, oes, oe;
		double previ, nexti, prevj, nextj, basis, a, b, o;
		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];
			if (oe.conductor)
			    continue;

			oen = grid[gi-gw];
			oes = grid[gi+gw];

			if (oe.boundary) {
			    double az = oe.az;
			    previ = oew.az-az;
			    if (oew.conductor)
				previ = (oee.conductor) ? 0 : oee.az-az;
			    nexti = oee.az-az;
			    if (oee.conductor)
				nexti = (oew.conductor) ? 0 : oew.az-az;
			    prevj = oen.az-az;
			    if (oen.conductor)
				prevj = (oes.conductor) ? 0 : oes.az-az;
			    nextj = oes.az-az;
			    if (oes.conductor)
				nextj = (oen.conductor) ? 0 : oen.az-az;
			    basis = (nexti+previ+nextj+prevj)*.25;

			    double jj = oes.jx - oen.jx + oew.jy - oee.jy;
			    a = basis + jj;
			} else {
			    // easy way
			    previ = oew.az;
			    nexti = oee.az;
			    prevj = oen.az;
			    nextj = oes.az;
			    basis = (nexti+previ+nextj+prevj)*.25;
			    a = oes.jx - oen.jx + oew.jy - oee.jy
				- (oe.az - basis);
			}
			o = oe.dazdt;
			oe.dazdt = (oe.dazdt * oe.damp) + a * forcecoef;
			oe.dazdt2 = oe.dazdt-o;
		    }
		}
		for (j = 1; j != mxy; j++) {
		    int gi = j*gw+1;
		    int giEnd = gi-1+mxx;
		    for (; gi != giEnd; gi++) {
			oe = grid[gi];
			oe.az += oe.dazdt * tadd2;
		    }
		}
		t += tadd;
		filterGrid();
		long tm = System.currentTimeMillis();
		/*System.out.println(tm-lastTime);
		  System.out.println(speedValue*1000/(tm-lastTime));*/
		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();
	    int col = 0xFFFFFFFF;
	    if (sourceType == SRC_ANTENNA && (i % 2) == 0)
		col = 0xFFFFFF00;
	    plotSource(i, xx, yy, col);
	}
	
	if (imageSource != null)
	    imageSource.newPixels();
	
	realg.drawImage(dbimage, 0, 0, this);
	if (!stoppedCheck.getState())
	    cv.repaint(pause);
    }

    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 col) {
	int rad = sourceRadius;
	int j;
	if (n == selectedSource)
	    col ^= 0x00808080;
	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 = EBrightnessBar.getValue() / 100.0;
	int ix = 0;
	int i, j, k, l;

	int viewScalar, viewVector, viewScalarCond, viewVectorCond;
	int v = viewChooser.getSelectedIndex();
	boolean showLines = false;
	viewScalar = viewScalarCond =
	    viewVector = viewVectorCond = VIEW_NONE;
	switch (v) {
	case VIEW_Q:
	case VIEW_B:
	case VIEW_DB_DT:
	case VIEW_ENERGY:
	    viewScalar = viewScalarCond = v;
	    break;
	case VIEW_E:
	case VIEW_J:
	case VIEW_POYNTING:
	case VIEW_DISP_CUR:
	case VIEW_DISP_J:
	    viewVector = viewVectorCond = v;
	    break;
	case VIEW_DISP_J_B:
	    viewVector = viewVectorCond = VIEW_DISP_J;
	    viewScalar = VIEW_B;
	    break;
	case VIEW_E_LINES:
	    showLines = true;
	    break;
	case VIEW_E_B:
	    viewScalar = viewScalarCond = VIEW_B;
	    viewVector = viewVectorCond = VIEW_E;
	    break;
	case VIEW_E_LINES_B:
	    viewScalar = viewScalarCond = VIEW_B;
	    showLines = true;
	    break;
	case VIEW_E_Q:
	    viewScalar = viewScalarCond = VIEW_Q;
	    viewVector = viewVectorCond = VIEW_E;
	    break;
	case VIEW_E_LINES_Q:
	    viewScalar = viewScalarCond = VIEW_Q;
	    showLines = true;
	    break;
	case VIEW_E_B_J:
	    viewScalar = viewScalarCond = VIEW_B;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    break;
	case VIEW_E_LINES_B_J:
	    viewScalar = viewScalarCond = VIEW_B;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    break;
	case VIEW_E_LINES_B_Q_J:
	    viewScalar = VIEW_B;
	    viewScalarCond = VIEW_Q;
	    viewVectorCond = VIEW_J;
	    showLines = true;
	    break;
	case VIEW_E_B_Q_J:
	    viewScalar = VIEW_B;
	    viewScalarCond = VIEW_Q;
	    viewVector = VIEW_E;
	    viewVectorCond = VIEW_J;
	    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.jx != 0 || oe.jy != 0) {
		    col_r = col_g = col_b = 64;
		    vv = viewVectorCond;
		    vs = viewScalarCond;
		}
		if (vs != VIEW_NONE) {
		    double dy = 0;
		    switch (vs) {
		    case VIEW_B:   dy = oe.az * .2; break;
		    case VIEW_DB_DT: dy = oe.dazdt; break;
		    case VIEW_Q:
			dy = 0;
			if (oe.conductor) {
			    if (!grid[gi+gw].conductor)
				dy = getEField(grid[gi+gw],
					       grid[gi+gw-1],
					       grid[gi+gw+1]);
			    if (!grid[gi-gw].conductor)
				dy += getEField(grid[gi-gw],
						grid[gi-gw+1],
						grid[gi-gw-1]);
			    if (!grid[gi+1].conductor)
				dy += getEField(grid[gi+1],
						grid[gi+gw+1],
						grid[gi-gw+1]);
			    if (!grid[gi-1].conductor)
				dy += getEField(grid[gi-1],
						grid[gi-gw-1],
						grid[gi+gw-1]);
			    dy *= .6;
			}
			break;
		    case VIEW_ENERGY:
			{
			    double dx =
				getEField(oe, grid[gi+gw], grid[gi-gw]);
			    dy = getEField(oe, grid[gi-1], grid[gi+1]);
			    dy = .4*(Math.sqrt(dx*dx+dy*dy)*3 +
				     oe.az * oe.az * .05);
			    break;
			}
		    }
		    dy *= mult;
		    if (dy < -1)
			dy = -1;
		    if (dy > 1)
			dy = 1;
		    if (vs == VIEW_Q) {
			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, mm;
		    final double jmult = .2;
		    switch (vv) {
		    case VIEW_E:
			dx = getEField(oe, grid[gi+gw], grid[gi-gw])*emult;
			dy = getEField(oe, grid[gi-1], grid[gi+1])*emult;
			break;
		    case VIEW_DISP_CUR:
		    case VIEW_DISP_J:
			dx = getdEdt(oe, grid[gi+gw], grid[gi-gw])*100;
			dy = getdEdt(oe, grid[gi-1], grid[gi+1])*100;
			if (vv == VIEW_DISP_CUR)
			    break;
			// fallthrough
		    case VIEW_J:
			if (oe.conductor) {
			    if (!grid[gi+gw].conductor)
				dx += -grid[gi+gw].az*jmult;
			    if (!grid[gi-gw].conductor)
				dx += grid[gi-gw].az*jmult;
			    if (!grid[gi+1].conductor)
				dy += grid[gi+1].az*jmult;
			    if (!grid[gi-1].conductor)
				dy += -grid[gi-1].az*jmult;
			} else {
			    dx += oe.jx*jmult;
			    dy += oe.jy*jmult;
			}
			break;
		    case VIEW_POYNTING:
			mm = 3.6*oe.az;
			dy = -mm *
			    getEField(oe, grid[gi-gw], grid[gi+gw]);
			dx = mm *
			    getEField(oe, grid[gi+1], grid[gi-1]);
			break;
			
		    }
		    double dn = Math.sqrt(dx*dx+dy*dy);
		    if (dn > 0) {
			dx /= dn;
			dy /= dn;
		    }
		    dn *= mult;
		    if (vv == VIEW_J) {
			if (dn > 1) {
			    if (dn > 2)
				dn = 2;
			    dn -= 1;
			    col_r = col_g = 255;
			    col_b = col_b+(int) (dn*(255-col_b));
			} else {
			    col_r = col_r+(int) (dn*(255-col_r));
			    col_g = col_g+(int) (dn*(255-col_g));
			}
		    } else {
			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;
	}
    }
    
    // calculate E field with some extra work to do one-sided
    // derivatives at conductor and dielectric boundaries.
    // ge = square we want the E field for, gn = next square,
    // gp = previous square.
    double getEField(OscElement ge, OscElement gp, OscElement gn) {
	if (ge.conductor)
	    return 0;
	if (gp.conductor)
	    return .66*(ge.dazdt-gn.dazdt);
	if (gn.conductor)
	    return .66*(gp.dazdt-ge.dazdt);
	return .33*(-gn.dazdt + gp.dazdt);
    }

    double getdEdt(OscElement ge, OscElement gp, OscElement gn) {
	if (ge.conductor)
	    return 0;
	if (gp.conductor)
	    return 2*(ge.dazdt2-gn.dazdt2);
	if (gn.conductor)
	    return 2*(gp.dazdt2-ge.dazdt2);
	return -gn.dazdt2 + gp.dazdt2;
    }

    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;
	boolean skip = false;
	
	// 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);
	
	double v = 0;
	double v2 = 0;
	if (!sourcePacket) {
	    v = Math.sin(w);
	    if (sourceCount >= 2)
		v2 = Math.sin(w2);
	} else {
	    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();
	}
	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 (sourceType == SRC_PLANE) {
	    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 if (sourceType == SRC_ANTENNA) {
	    for (j = 0; j != sourceCount/2; j++) {
		OscSource src1 = sources[j*2];
		OscSource src2 = sources[j*2+1];
		OscSource src3 = sources[j];
		drawAntennaSource(src1.x, src1.y,
				  src2.x, src2.y, src3.v*.1);
	    }
	} else if (sourceType == SRC_LOOP) {
	    int x1 = min(sources[0].x, sources[1].x);
	    int x2 = max(sources[0].x, sources[1].x);
	    int y1 = min(sources[0].y, sources[1].y);
	    int y2 = max(sources[0].y, sources[1].y);
	    int ix, iy;
	    double vx, vy;
	    vx = vy = sources[0].v*.1;
	    if (x1 == x2)
		vx = 0;
	    if (y1 == y2)
		vy = 0;
	    for (ix = x1+1; ix < x2; ix++) {
		grid[ix+gw*y1].jx =  vx;
		grid[ix+gw*y2].jx = -vx;
	    }
	    grid[x1+gw*y1].jx =  .5*vx;
	    grid[x2+gw*y1].jx =  .5*vx;
	    grid[x1+gw*y2].jx = -.5*vx;
	    grid[x2+gw*y2].jx = -.5*vx;
	    for (iy = y1+1; iy < y2; iy++) {
		grid[x1+gw*iy].jy = -vy;
		grid[x2+gw*iy].jy =  vy;
	    }
	    grid[x1+gw*y1].jy = -.5*vy;
	    grid[x1+gw*y2].jy = -.5*vy;
	    grid[x2+gw*y1].jy =  .5*vy;
	    grid[x2+gw*y2].jy =  .5*vy;
	}
    }

    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;
	}
	double len = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
	double xmult = (x2-x1)/len;
	double ymult = (y2-y1)/len;
	// need to draw a line from x1,y1 to x2,y2
	if (x1 == x2 && y1 == y2) {
	    //grid[x1][y1].jrot = 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+y*gw].jx = v*xmult;
		grid[x+y*gw].jy = v*ymult;
	    }
	} 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+y*gw].jx = v*xmult;
		grid[x+y*gw].jy = v*ymult;
	    }
	}
    }

    void drawAntennaSource(int x1, int y1, int x2, int y2, double v) {
        double k = forceBar.getValue() * .0224; // determined by trial-and-error
	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;
	}
	double len = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
	double ph = 0; // -len;
	double xmult = (x2-x1)/len;
	double ymult = (y2-y1)/len;
	// need to draw a line from x1,y1 to x2,y2
	if (x1 == x2 && y1 == y2) {
	    //grid[x1][y1].jrot = 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);
		double q = Math.sin((ph+(y-y1)/ymult)*k)*v;
		grid[x+y*gw].jx = q*xmult;
		grid[x+y*gw].jy = q*ymult;
	    }
	} 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);
		double q = Math.sin((ph+(x-x1)/xmult)*k)*v;
		grid[x+y*gw].jx = q*xmult;
		grid[x+y*gw].jy = q*ymult;
	    }
	}
    }

    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() *
	    (double) EBrightnessBar.getValue() / 5000.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];
	    if (oe.gray || oe.jx != 0 || oe.jy != 0) {
		x = 0;
		continue;
	    }
	    double dx = getEField(oe, grid[gi+gw], grid[gi-gw]);
	    double dy = getEField(oe, grid[gi-1],  grid[gi+1]);
	    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);
		}
	    }
	}
    }

    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 &&
			!sourcePacket) ? 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.jx != 0 || oe.jy != 0 || oe.boundary || oe.conductor)
		    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 (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.conductor || oe.jx != 0 || oe.jy != 0;
	    dragSet = !dragClear;
	}

	oe.conductor = false;

	if (dragClear)
	    return;

	switch (modeChooser.getSelectedIndex()) {
	case MODE_PERF_CONDUCTOR: addConductor(xp, yp, 1); 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) {
	    if (resBar.getValue() == windowWidth)
		return;
	    setResolution();
	    reinit();
	    cv.repaint(pause);
	}
	if (e.getSource() == brightnessBar ||
	    e.getSource() == EBrightnessBar ||
	    e.getSource() == lineDensityBar)
	    cv.repaint(pause);
	if (e.getSource() == lineDensityBar)
	    linegrid = null;
	if (e.getSource() == forceBar)
	    setForce();
    }

    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 = 30;
	gridSizeX = windowWidth + windowOffsetX*2;
	gridSizeY = windowHeight + windowOffsetY*2;
	gridSizeXY = gridSizeX*gridSizeY;
	gw = gridSizeX;
	linegrid = null;
    }

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

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

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

    void addMedium() {
    }

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

    void setSources() {
	if (sourceCount > 0)
	    doSources(1, true);
	sourceMult = 1;
	int oldSCount = sourceCount;
	sourceCount = 0;
	sourceType = SRC_PLANE;
	sourcePacket = false;
	switch (sourceChooser.getSelectedIndex()) {
	case SRC_NONE  : sourceCount = 0; break;
	case SRC_1PLANE: sourceCount = 1; break;
	case SRC_2PLANE: sourceCount = 2; break;
	case SRC_1PLANE_PACKET: sourceCount = 1; sourcePacket = true; break;
	case SRC_1ANTENNA: sourceCount = 1; sourceType = SRC_ANTENNA; break;
	case SRC_2ANTENNA: sourceCount = 2; sourceType = SRC_ANTENNA; break;
	case SRC_1LOOP       : sourceCount = 1; sourceType = SRC_LOOP; break;
	case SRC_1LOOP_PACKET: sourceCount = 1; sourceType = SRC_LOOP; sourcePacket = true;
	}
	if (sourceCount == 2) {
	    auxBar.setValue(1);
	    auxLabel.setText("Phase Difference");
	    auxBar.show();
	    auxLabel.show();
	} else {
	    auxBar.hide();
	    auxLabel.hide();
	}
	validate();
	
	sourceCount *= 2;
	if (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);
	}
    }

    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 {
	// true if perfect conductor
	boolean conductor;
	// current
	double jx, jy;
	// 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, dazdt2;
	// 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;
	int getType() {
	    if (conductor)
		return TYPE_CONDUCTOR;
	    else if (jx != 0 || jy != 0)
		return TYPE_CURRENT;
	    return TYPE_NONE;
	}
    };

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

    class PlaneWaveSetup extends Setup {
	String getName() { return "Plane Wave"; }
	void select() {
	    sourceChooser.select(SRC_1PLANE);
	    brightnessBar.setValue(225);
	    setForceBar(30);
	}
	Setup createNext() { return new IntersectingPlaneWavesSetup(); }
    }
    class IntersectingPlaneWavesSetup extends Setup {
	String getName() { return "Intersecting Planes"; }
	void select() {
	    brightnessBar.setValue(126);
	    setForceBar(34);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2PLANE);
	    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 ConductReflectSetup(); }
    }
    class ConductReflectSetup extends Setup {
	String getName() { return "Reflection At Conductor"; }
	void select() {
	    sourceChooser.select(SRC_1LOOP_PACKET);
	    addCondMedium(1);
	    setForceBar(4);
	    brightnessBar.setValue(1600);
	}
	void doSetupSources() {
	    setSources();
	    sources[0].x = gridSizeX/2-1;
	    sources[1].x = gridSizeX/2+1;
	    sources[0].y = windowOffsetY;
	    sources[1].y = windowOffsetY+2;
	}
	Setup createNext() { return new OscDipoleSetup(); }
    }
    class OscDipoleSetup extends Setup {
	String getName() { return "Oscillating Dipole"; }
	void select() {
	    setForceBar(10);
	    brightnessBar.setValue(1066);
	    EBrightnessBar.setValue(300);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_1PLANE);
	    setSources();
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = cx;
	    sources[0].y = cy-1;
	    sources[1].y = cy+1;
	}
	Setup createNext() { return new HalfWaveAnt1Setup(); }
    }
    class HalfWaveAnt1Setup extends Setup {
	String getName() { return "Half Wave Antenna"; }
	void select() {
	    setForceBar(10);
	    brightnessBar.setValue(390);
	    EBrightnessBar.setValue(350);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_1ANTENNA);
	    setSources();
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = cx;
	    sources[0].y = cy+7;
	    sources[1].y = cy-7;
	}
	Setup createNext() { return new FullWaveAnt1Setup(); }
    }
    class FullWaveAnt1Setup extends Setup {
	String getName() { return "Full Wave Ant (End-Driven)"; }
	void select() {
	    setForceBar(25);
	    brightnessBar.setValue(390);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_1ANTENNA);
	    setSources();
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = cx;
	    sources[0].y = cy+6;
	    sources[1].y = cy-5;
	}
	Setup createNext() { return new FullWaveAnt2Setup(); }
    }
    class FullWaveAnt2Setup extends Setup {
	String getName() { return "Full Wave Ant (Center-Driven)"; }
	void select() {
	    setForceBar(25);
	    brightnessBar.setValue(390);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2ANTENNA);
	    setSources();
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    sources[0].x = sources[1].x = cx;
	    sources[2].x = sources[3].x = cx;
	    sources[0].y = cy+1;
	    sources[1].y = cy+6;
	    sources[2].y = cy;
	    sources[3].y = cy-5;
	    auxBar.setValue(40);
	}
	Setup createNext() { return new OscCurrentLoop(); }
    }
    class OscCurrentLoop extends Setup {
	String getName() { return "Current Loop"; }
	void select() {
	    sourceChooser.select(SRC_1LOOP);
	    setSources();
	    sources[0].x = gridSizeX/2-1;
	    sources[0].y = gridSizeY/2-1;
	    sources[1].x = gridSizeX/2+1;
	    sources[1].y = gridSizeY/2+1;
	    brightnessBar.setValue(270);
	    setForceBar(34);
	}
	Setup createNext() { return new BigMode01Setup(); }
    }
    class BigMode01Setup extends Setup {
	String getName() { return "Big TE01 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, 0, 1);
	    brightnessBar.setValue(200);
	    EBrightnessBar.setValue(400);
	}
	Setup createNext() { return new BigMode10Setup(); }
    }
    class BigMode10Setup extends Setup {
	String getName() { return "Big TE10 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, 0);
	    brightnessBar.setValue(200);
	    EBrightnessBar.setValue(400);
	}
	Setup createNext() { return new BigMode1001Setup(); }
    }
    class BigMode1001Setup extends Setup {
	String getName() { return "Big TE10+TE01 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, 0);
	    addMode(x, y, n, n, 0, 1);
	    brightnessBar.setValue(200);
	    EBrightnessBar.setValue(250);
	}
	Setup createNext() { return new BigMode1001iSetup(); }
    }
    class BigMode1001iSetup extends Setup {
	String getName() { return "Big TE10+TE01i 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, 0);
	    addModeI(x, y, n, n, 0, 1);
	    brightnessBar.setValue(200);
	    EBrightnessBar.setValue(250);
	}
	Setup createNext() { return new BigMode2Setup(); }
    }
    class BigMode2Setup extends Setup {
	String getName() { return "Big TE11 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);
	    EBrightnessBar.setValue(300);
	}
	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.cos(pi*nx*i/(sx-1))*
		     Math.cos(pi*ny*j/(sy-1)));
		grid[i+x+gw*(j+y)].dazdt = 0;
	    }
	noFilter();
    }
    void addMode(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.cos(pi*nx*i/(sx-1))*
		     Math.cos(pi*ny*j/(sy-1)));
	    }
	noFilter();
    }
    void addModeI(int x, int y, int sx, int sy, int nx, int ny) {
	int i, j;
	double mult = pi*4*Math.sqrt(nx*nx/((double) (sx-1)*(sx-1)) +
					       ny*ny/((double) (sy-1)*(sy-1)));
	for (i = 0; i < sx; i++)
	    for (j = 0; j < sy; j++) {
		grid[i+x+gw*(j+y)].dazdt += mult*
		    (Math.cos(pi*nx*i/(sx-1))*
		     Math.cos(pi*ny*j/(sy-1)));
	    }
	noFilter();
    }
    class OneByOneModesSetup extends Setup {
	String getName() { return "TE10 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, 0);
		y += ny+2;
	    }
	    brightnessBar.setValue(250);
	    EBrightnessBar.setValue(300);
	}
	Setup createNext() { return new NByZeroModesSetup(); }
    }
    class NByZeroModesSetup extends Setup {
	String getName() { return "TEn0 Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 6;
	    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, 0);
		y += ny+2;
		mode++;
	    }
	    brightnessBar.setValue(200);
	    EBrightnessBar.setValue(128);
	}
	Setup createNext() { return new NByOneModesSetup(); }
    }
    class NByOneModesSetup extends Setup {
	String getName() { return "TEn1 Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 10;
	    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++;
	    }
	    brightnessBar.setValue(150);
	}
	Setup createNext() { return new NByNModesSetup(); }
    }
    class NByNModesSetup extends Setup {
	String getName() { return "TEnn 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++) {
		    if (modex == 1 && modey == 1)
			continue;
		    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-1, modey-1);
		}
	    brightnessBar.setValue(300);
	}
	Setup createNext() { return new ZeroByNModeCombosSetup(); }
    }
    class ZeroByNModeCombosSetup extends Setup {
	String getName() { return "TEn0 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);
		int my = (this instanceof ZeroByNModeCombosSetup) ? 0 : 1;
		for (i = 0; i != nx; i++)
		    for (j = 0; j != ny; j++) {
			grid[i+x1+gw*(j+y1)].az = (float) 2*
			    (Math.cos(mode1*pi*i/(nx-1))*
			     Math.cos(pi*my*j/(ny-1))*.5 +
			     Math.cos(mode2*pi*i/(nx-1))*
			     Math.cos(pi*my*j/(ny-1))*.5);
			grid[i+x1+gw*(j+y1)].dazdt = 0;
		    }
		y += ny+2;
	    }
	    noFilter();
	    brightnessBar.setValue(310);
	}
	Setup createNext() { return new OneByNModeCombosSetup(); }
    }
    class OneByNModeCombosSetup extends ZeroByNModeCombosSetup {
	String getName() { return "TEn1 Mode Combos"; }
	Setup createNext() { return new NByNModeCombosSetup(); }
    }
    class NByNModeCombosSetup extends Setup {
	String getName() { return "TEnn 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);
		    int mode1y = getrand(4)+1;
		    int mode2x, mode2y;
		    do {
			mode2x = getrand(4)+1;
			mode2y = getrand(4);
		    } 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.cos(mode1x*pi*i/(nx-1))*
				 Math.cos(mode1y*pi*j/(ny-1))*.5 +
				 Math.cos(mode2x*pi*i/(nx-1))*
				 Math.cos(mode2y*pi*j/(ny-1))*.5);
			    grid[i+x1+gw*(j+y1)].dazdt = 0;
			}
		}
	    brightnessBar.setValue(370);
	    noFilter();
	}
	Setup createNext() { return new Waveguides1Setup(); }
    }
    class Waveguides1Setup extends Setup {
	String getName() { return "Waveguides"; }
	void select() {
	    sourceChooser.select(SRC_1PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 5;
	    int y1 = windowOffsetY + 1;
	    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, gridSizeX-1, y1, 1);
	    brightnessBar.setValue(215);
	    setForceBar(28);
	}
	Setup createNext() { return new CapacitorSetup(); }
    }
    class CapacitorSetup extends Setup {
	String getName() { return "Capacitor"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i;
	    int sz = (windowWidth > 45) ? 45 : windowWidth;
	    int n = sz*3/4;
	    int cx = windowOffsetX+windowWidth/2;
	    int cy = windowOffsetY+windowHeight/2;
	    int x = cx-n/2;
	    int y = cy-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, 0);

	    // fill in top and bottom
	    conductFillRect(x, y, x+n, y+4, 1);
	    conductFillRect(x, y+n-4, x+n, y+n-1, 1);

	    // conductors leading to plates
	    int sep = 4;
	    conductFillRect(cx-2, y, cx+2, cy-sep, 1);
	    conductFillRect(cx-2, cy+sep, cx+2, y+n, 1);

	    // plates
	    conductFillRect(cx-5, cy-(sep+1), cx+5, cy-(sep-1), 1);
	    conductFillRect(cx-5, cy+(sep-1), cx+5, cy+(sep+1), 1);
	    brightnessBar.setValue(700);
	    EBrightnessBar.setValue(200);
	    findMode(x, y, x+n, y+n);
	    noFilter();
	}
	Setup createNext() { return new ResonantCavitiesSetup(); }
    }

    void findMode(int x1, int y1, int x2, int y2) {
	int iter;
	double delta = 0;
	int iic = 1000; // 500; // 1000; // 2000;
	for (iter = 0; iter != iic; iter++) {
	    int i, j;
	    int ct = 0;
	    for (i = x1; i <= x2; i++)
		for (j = y1; j <= y2; j++) {
		    int gi = i+j*gw;
		    OscElement oew = grid[gi-1];
		    OscElement oee = grid[gi+1];
		    OscElement oen = grid[gi-gw];
		    OscElement oes = grid[gi+gw];
		    OscElement oe = grid[gi];
		    if (oe.conductor)
			continue;
		    if (oe.col != 0) {
			oe.dazdt = oe.az;
			continue;
		    }

		    double az = oe.az;
		    double previ = oew.az;
		    if (oew.conductor)
			previ = (oee.conductor) ? az : oee.az;
		    double nexti = oee.az;
		    if (oee.conductor)
			nexti = (oew.conductor) ? az : oew.az;
		    double prevj = oen.az;
		    if (oen.conductor)
			prevj = (oes.conductor) ? az : oes.az;
		    double nextj = oes.az;
		    if (oes.conductor)
			nextj = (oen.conductor) ? az : oen.az;
		    oe.dazdt = .125*(nexti+previ+nextj+prevj+4*az);
		    delta += Math.abs(oe.dazdt-az);
		    ct++;
		}
	    delta /= ct;
	    for (i = x1; i <= x2; i++)
		for (j = y1; j <= y2; j++) {
		    OscElement oe = grid[i+j*gw];
		    oe.az = oe.dazdt;
		    oe.dazdt = 0;
		}
	}
    }

    class ResonantCavitiesSetup extends Setup {
	String getName() { return "Resonant Cavities"; }
	void select() {
	    sourceChooser.select(SRC_1PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 3;
	    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++)
		    grid[x1-1+gw*(y1+i-1)].conductor =
			grid[x1+nx+gw*(y1+i-1)].conductor = true;
		for (j = 0; j != nx+2; j++)
		    grid[x1+j-1+gw*(y1-1)].conductor =
			grid[x1+j-1+gw*(y1+ny)].conductor = true;
		grid[x1+nx/2+gw*(y1-1)].conductor = false;
		x += nx+2;
	    }
	    x--;
	    for (; x < windowWidth; x++)
		grid[x+windowOffsetX+gw*(y1-1)].conductor = true;
	    brightnessBar.setValue(120);
	    setForceBar(15);
	}
	Setup createNext() { return new SingleSlitSetup(); }
    }

    class SingleSlitSetup extends Setup {
	String getName() { return "Single Slit"; }
	void select() {
	    sourceChooser.select(SRC_1PLANE);
	    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_1PLANE);
	    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_1PLANE);
	    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_1PLANE);
	    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_1PLANE);
	    int x = windowOffsetX+windowWidth/2;
	    int i;
	    conductFillRect(windowOffsetX+windowWidth*2/3, windowOffsetY+3,
			    windowOffsetY+windowWidth-1, windowOffsetY+5, 1);
	    brightnessBar.setValue(250);
	    setForceBar(35);
	}
	Setup createNext() { return new LloydsMirrorSetup(); }
    }
    class LloydsMirrorSetup extends Setup {
	String getName() { return "Lloyd's Mirror"; }
	void select() {
	    sourceChooser.select(SRC_1LOOP);
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY + windowHeight*3/4-1;
	    sources[1].x = windowOffsetX+2;
	    sources[1].y = windowOffsetY + windowHeight*3/4+1;
	    brightnessBar.setValue(250);
	    setForceBar(40);
	    conductDrawRect(0, windowOffsetY+windowHeight-1,
			    gridSizeX-1, windowOffsetY+windowHeight-1, 1);
	}
	void doSetupSources() {}
	Setup createNext() { return null; }
    }

    void addConductor(int x, int y, double cv) {
	OscElement oe = grid[x+gw*y];
	oe.conductor = (cv == 0) ? false : true;
	if (cv == 1)
	    oe.az = oe.dazdt = 0;
    }
    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);
	}
    }
}
