// Diffraction.java (c) 2001 by Paul Falstad, www.falstad.com.
// Algorithm used for circular apertures is from "Simulation and Study
// of Fresnel Diffraction for Arbitrary Two-Dimensional Apertures", by
// Dean Dauger, published in Computers In Physics, Nov/Dec 1996,
// except I use ray tracing to find edges instead of the (more general)
// method he describes.

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
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.awt.image.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.Math;
import java.text.NumberFormat;
import java.awt.event.*;

class DiffractionCanvas extends Canvas {
    static DiffractionFrame pg;
    DiffractionCanvas(DiffractionFrame p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateDiffraction(g);
    }
    public void paint(Graphics g) {
	pg.updateDiffraction(g);
    }
};

class DiffractionLayout implements LayoutManager {
    public DiffractionLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(550, 400);
    }
    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 * 7/10;
	if (target.getComponentCount() == 1)
	    cw = targetw;
	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 h = insets.top;
	int i;
	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 Label) {
		    h += d.height/5;
		    d.width = barwidth;
		}
		m.move(cw, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};

public class Diffraction extends Applet {
    static DiffractionFrame mf;
    void destroyFrame() {
	if (mf != null)
	    mf.dispose();
	mf = null;
    }
    public void init() {
	mf = new DiffractionFrame(this);
	mf.init();
    }
    public static void main(String args[]) {
        mf = new DiffractionFrame(null);
        mf.init();
    }

    public void destroy() {
	if (mf != null)
	    mf.dispose();
	mf = null;
    }
};

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

    Dimension winSize, fullWinSize;
    Image dbimage;
    
    Random random;
    int gridSizeX = 200;
    int gridSizeY = 200;
    
    public String getAppletInfo() {
	return "Diffraction by Paul Falstad";
    }

    Button defaultsButton;
    Checkbox colorCheck;
    Checkbox reversedCheck;
    Checkbox sizeCheck;
    Choice apertureChooser;
    Scrollbar gridBar;
    Scrollbar lengthBar;
    Scrollbar zoomBar;
    Scrollbar brightnessBar;
    double colorMult;
    double zbase;
    static final double pi = 3.14159265358979323846;
    static final double pi2 = pi*2;
    double func[][][];
    boolean functionChanged;
    boolean dragging = false;
    MemoryImageSource imageSource;
    int pixels[];
    
    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    DiffractionCanvas cv;
    Vector apertureList;
    Aperture aperture;
    Diffraction applet;

    boolean useBufferedImage = false;
    
    DiffractionFrame(Diffraction a) {
	super("Diffraction Applet v1.1a");
	applet = a;
    }

    public void init() {
	apertureList = new Vector();
	Aperture a = new CircularAperture();
	while (a != null) {
	    apertureList.addElement(a);
	    a = a.createNext();
	}
	setLayout(new DiffractionLayout());
	cv = new DiffractionCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	add(defaultsButton = new Button("Set to Defaults"));
	defaultsButton.addActionListener(this);
	
	colorCheck = new Checkbox("Tri-chromatic");
	colorCheck.addItemListener(this);
	add(colorCheck);
	
	reversedCheck = new Checkbox("Reversed");
	reversedCheck.addItemListener(this);
	add(reversedCheck);
	
	sizeCheck = new Checkbox("Show Dimensions");
	sizeCheck.addItemListener(this);
	add(sizeCheck);
	
	String os = System.getProperty("os.name");
        String jv = System.getProperty("java.class.version");
        double jvf = new Double(jv).doubleValue();
        if (jvf >= 48)
	    useBufferedImage = true;

	apertureChooser = new Choice();
	int i;
	for (i = 0; i != apertureList.size(); i++)
	    apertureChooser.add("Aperture: " +
		((Aperture) apertureList.elementAt(i)).getName());
	add(apertureChooser);
	aperture = (Aperture) apertureList.elementAt(0);
	apertureChooser.addItemListener(this);

	add(new Label("Aperture Scale", Label.CENTER));
	add(lengthBar = new Scrollbar(Scrollbar.HORIZONTAL, 260, 1, 35, 500));
	lengthBar.addAdjustmentListener(this);

	add(new Label("Zoom", Label.CENTER));
	add(zoomBar = new Scrollbar(Scrollbar.HORIZONTAL, 200, 1, 30, 400));
	zoomBar.addAdjustmentListener(this);

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

	add(new Label("Image Resolution", Label.CENTER));
	add(gridBar = new Scrollbar(Scrollbar.HORIZONTAL, 250, 2, 10, 600));
	gridBar.addAdjustmentListener(this);

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

	random = new Random();
	functionChanged = true;
	reinit();
	cv.setBackground(Color.black);
	cv.setForeground(Color.white);
	zbase = 1/java.lang.Math.exp(200/50);

	resize(750, 547);
	handleResize();
	show();
    }

    void reinit() {
	handleResize();
    }

    void handleResize() {
        winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	Dimension d = fullWinSize = cv.getSize();
        int w = (winSize.width > winSize.height) ? winSize.height : winSize.width;
        winSize.width = winSize.height = w;
	pixels = null;
	d = winSize;
	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);
	}
	imageSource = new MemoryImageSource(d.width, d.height, pixels, 0,
                                            d.width);
    }
    
    int angleSteps;
    int angleStepsMask;
    double angcos1[], angsin1[];
    long angcos2[], angsin2[];
    int angleSteps2;
    int angleSteps2Mask;
    static final int fixedPoint = 256;
    int accumR[], accumI[];
    double apertureArgMult;
    double apertureArgMultRed;
    double apertureArgMultBlue;
    double colorLenMults[];
    boolean reversed;
    boolean color;
    int selection = -1;
    double zoomFactor;
    int oldZoom = 200;

    void computeFunction() {
	accumR = new int[3];
	accumI = new int[3];
	aperture = (Aperture)
	    apertureList.elementAt(apertureChooser.getSelectedIndex());
	gridSizeX = gridSizeY = (gridBar.getValue() & ~1);
	if (aperture.oneDimensional()) {
	    gridSizeX *= 2;
	    gridSizeY = 1;
	}
	func = new double[gridSizeX][gridSizeY][3];
	int i, j;
	color = colorCheck.getState();
	angleSteps = (gridBar.getValue() >= 195) ? 1024 : 256;
	if (aperture.oneDimensional())
	    angleSteps = (gridBar.getValue() >= 195) ? 2048 : 1024;
	angleStepsMask = angleSteps-1;
	zoomFactor = java.lang.Math.exp(zoomBar.getValue()/50.)*zbase;
	double baselen = java.lang.Math.exp(lengthBar.getValue()/110.)
	    /zoomFactor;
	angcos1 = new double[angleSteps];
	angsin1 = new double[angleSteps];
	// precompute sines and cosines, one for each angle step
	for (i = 0; i != angleSteps; i++) {
	    angcos1[i] = java.lang.Math.cos(i*pi2/angleSteps);
	    angsin1[i] = java.lang.Math.sin(i*pi2/angleSteps);
	}
	angleSteps2 = 4096;
	angleSteps2Mask = angleSteps2-1;
	angcos2 = new long[angleSteps2];
	angsin2 = new long[angleSteps2];
	reversed = reversedCheck.getState();
	float sign = (reversed) ? -1 : 1;
	colorLenMults = new double[3];

	// precompute another set of sines and cosines.  this one
	// is used to generate the complex number we add into the
	// accumulator in apertureStart/apertureStop.  The array is
	// reversed if the reversed check is set.  That causes all
	// the apertureStart() calls to act like apertureStop() and
	// vice versa.  We used fixed-point integer arithmetic for
	// all this.
	for (i = 0; i != angleSteps2; i++) {
	    angcos2[i] = (long)
		(java.lang.Math.cos(i*pi2/angleSteps2) * fixedPoint*sign);
	    angsin2[i] = (long)
		(java.lang.Math.sin(i*pi2/angleSteps2) * fixedPoint*sign);
	}

	// generate different multipliers for red and blue light.
	// red = 650 nm, green = 510 nm, blue = 475 nm
	colorLenMults[0] = baselen / (650./510.);
	colorLenMults[1] = baselen;
	colorLenMults[2] = baselen / (475./510.);
	
	apertureArgMult = angleSteps2*.25;
	apertureArgMult *= baselen*baselen;

	apertureArgMultRed  = apertureArgMult / (650*650./(510*510));
	apertureArgMultBlue = apertureArgMult / (475*475./(510*510));

	aperture.compute();

	// take advantage of symmetry
	int maxx = aperture.hasXSymmetry() ? gridSizeX/2 : gridSizeX;
	int maxy = aperture.hasYSymmetry() ? gridSizeY/2 : gridSizeY;
	int mink = (color) ? 0 : 1;
	int maxk = (color) ? 2 : 1;
	int k;
	if (aperture.hasDiagonalSymmetry())
	    for (k = mink; k <= maxk; k++)
		for (i = 0; i != maxx; i++)
		    for (j = 0; j <= i; j++)
			func[j][i][k] = func[i][j][k];
	if (aperture.hasXSymmetry()) 
	    for (k = mink; k <= maxk; k++)
		for (i = 0; i != maxx; i++)
		    for (j = 0; j != maxy; j++)
			func[gridSizeX-1-i][j][k] = func[i][j][k];
	if (aperture.hasYSymmetry())
	    for (k = mink; k <= maxk; k++)
		for (i = 0; i != gridSizeX; i++)
		    for (j = 0; j != maxy; j++)
			func[i][gridSizeX-1-j][k] = func[i][j][k];
	functionChanged = false;
    }

    void setFunction(int i, int j) {
	// by default we use only green light.
	int mink = 1, maxk = 1;
	if (color) {
	    mink = 0;
	    maxk = 2;
	}
	int k;
	for (k = mink; k <= maxk; k++) {
	    double ard = ((double) accumR[k])/(angleSteps*fixedPoint);
	    double aid = ((double) accumI[k])/(angleSteps*fixedPoint);
	    double mag = ard*ard + aid*aid;
	    func[i][j][k] = mag;
	}
    }

    void clearAccum() {
	int i;
	for (i = 0; i != 3; i++)
	    accumR[i] = accumI[i] = 0;
    }

    void apertureStart(double r) {
	// report the fact that we found the start of an opening
	// at radius r.
	double r2 = r*r;
	int arg = ((int) (r2*apertureArgMult)) & angleSteps2Mask;
	accumR[1] -= angcos2[arg];
	accumI[1] -= angsin2[arg];
	if (color) {
	    // we did green, now do red and blue
	    arg = ((int) (r2*apertureArgMultRed)) & angleSteps2Mask;
	    accumR[0] -= angcos2[arg];
	    accumI[0] -= angsin2[arg];
	    arg = ((int) (r2*apertureArgMultBlue)) & angleSteps2Mask;
	    accumR[2] -= angcos2[arg];
	    accumI[2] -= angsin2[arg];
	}
    }

    void apertureStop(double r) {
	// report the fact that we found the end of an opening
	// at radius r.
	double r2 = r*r;
	int arg = ((int) (r2*apertureArgMult)) & angleSteps2Mask;
	accumR[1] += angcos2[arg];
	accumI[1] += angsin2[arg];
	if (color) {
	    arg = ((int) (r2*apertureArgMultRed)) & angleSteps2Mask;
	    accumR[0] += angcos2[arg];
	    accumI[0] += angsin2[arg];
	    arg = ((int) (r2*apertureArgMultBlue)) & angleSteps2Mask;
	    accumR[2] += angcos2[arg];
	    accumI[2] += angsin2[arg];
	}
    }

    void apertureStartOrigin(boolean x) {
	// if x is true, that means the origin is at a transparent
	// spot.  Otherwise it's at an opaque spot.
	if (reversed)
	    x = !x;
	if (x) {
	    accumR[1] -= fixedPoint*angleSteps;
	    if (color) {
		accumR[0] -= fixedPoint*angleSteps;
		accumR[2] -= fixedPoint*angleSteps;
	    }
	}
    }

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

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

    public void updateDiffraction(Graphics realg) {
	boolean hideFunction = dragging && aperture.hideWhileDragging();
	if (functionChanged) {
	    realg.setColor(cv.getBackground());
	    FontMetrics fm = realg.getFontMetrics();
	    String cs = "Calculating...";
	    realg.fillRect(0, fullWinSize.height-30,
			   20+fm.stringWidth(cs), 30);
	    realg.setColor(Color.white);
	    realg.drawString(cs, 10, fullWinSize.height-10);
	    computeFunction();
	}

	Graphics g = null;
	if (winSize == null || winSize.width == 0)
	    return;
	g = dbimage.getGraphics();
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, fullWinSize.width, fullWinSize.height);
	g.setColor(cv.getForeground());

	int i, j;
	colorMult = 70*java.lang.Math.exp(brightnessBar.getValue()/50.);
	if (!hideFunction) {
	    for (i = 0; i != gridSizeX; i++)
		for (j = 0; j != gridSizeY; j++) {
		    int x = i*winSize.width/gridSizeX;
		    int y = j*winSize.height/gridSizeY;
		    int x2 = (i+1)*winSize.width/gridSizeX;
		    int y2 = (j+1)*winSize.height/gridSizeY;
		    int colval = 0;
		    if (!color) {
			int col = getColorValue(i, j, 1);
			colval = 0xFF000000 | (col*0X010101);
		    } else {
			colval = 0xFF000000 +
			    (getColorValue(i, j, 0) << 16) |
			    (getColorValue(i, j, 1) << 8) |
			    (getColorValue(i, j, 2));
		    }
		    int k, l;
		    for (k = x; k < x2; k++)
			for (l = y; l < y2; l++)
			    pixels[k+l*winSize.width] = colval;
		}
	}

	if (imageSource != null)
	    imageSource.newPixels();
	if (!hideFunction)
	    g.drawImage(dbimage, 0, 0, null);
	    
	g.setColor(Color.red);
	aperture.drawGeometricShadow(g);
	if (sizeCheck.getState()) {
	    g.setColor(cv.getBackground());
	    FontMetrics fm = realg.getFontMetrics();
	    double wl = 510e-9;
	    NumberFormat nf = NumberFormat.getInstance();
	    nf.setMaximumFractionDigits(2);
	    double baselen = java.lang.Math.exp(lengthBar.getValue()/110.)/
		zoomFactor;
	    double dim = aperture.getDimension()*baselen*
		java.lang.Math.sqrt(wl*2);
	    String cs = "width = ";
	    if (dim > .001)
		cs += nf.format(dim*1000) + " mm";
	    else if (dim > 1e-6)
		cs += nf.format(dim*1e6) + " \u00b5m";
	    else
		cs += nf.format(dim*1e9) + " nm";
	    int sw = fm.stringWidth(cs);
	    if (dim > 0) {
		g.fillRect(fullWinSize.width-(20+sw), fullWinSize.height-30,
			   20+sw, 30);
		g.setColor(Color.white);
		g.drawString(cs, fullWinSize.width-(10+sw),
			     fullWinSize.height-10);
	    }
	}
	realg.drawImage(dbimage, 0, 0, this);
    }

    int getColorValue(int i, int j, int k) {
	int val = (int) (func[i][j][k] * colorMult);
	if (val > 255)
	    val = 255;
	return val;
    }

    void doZoom() {
	double z = java.lang.Math.exp(zoomBar.getValue()/50.)*zbase;
	double oz = java.lang.Math.exp(oldZoom/50.)*zbase;
	double zoomChange = z/oz;
	oldZoom = zoomBar.getValue();
	aperture.rezoom(zoomChange);
    }

    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() == defaultsButton) {
	    colorCheck.setState(false);
	    reversedCheck.setState(false);
	    lengthBar.setValue(260);
	    gridBar.setValue(90);
	    zoomBar.setValue(oldZoom = 200);
	    functionChanged = true;
	    brightnessBar.setValue(aperture.defaultBrightness());
	    aperture.setToDefaults();
	    cv.repaint();
	}
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
	if (e.getSource() != brightnessBar)
	    functionChanged = true;
	if (e.getSource() == zoomBar)
	    doZoom();
	cv.repaint(100);
    }

    public void mouseDragged(MouseEvent e) {
	if (selection != -1) {
	    dragging = true;
	    if (aperture.drag(e.getX(), e.getY()))
		cv.repaint();
	}
    }
    public void mouseMoved(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
	    if (selection != -1) {
		dragging = true;
		if (aperture.drag(e.getX(), e.getY()))
		    cv.repaint();
	    }
	    return;
	}
	int sel = selection;
	selection = aperture.getSelection(e.getX(), e.getY());
	if (selection != sel)
	    cv.repaint();
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
	if (!dragging && selection != -1) {
	    selection = -1;
	    cv.repaint();
	}
    }
    public void mousePressed(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	if (selection != -1) {
	    dragging = true;
	    if (aperture.drag(e.getX(), e.getY()))
		cv.repaint();
	}
    }
    public void mouseReleased(MouseEvent e) {
	if (dragging) {
	    functionChanged = true;
	    cv.repaint();
	}
	dragging = false;
    }
    public void itemStateChanged(ItemEvent e) {
	if (e.getSource() != sizeCheck)
	    functionChanged = true;
	if (e.getSource() == apertureChooser) {
	    aperture = (Aperture)
		apertureList.elementAt(apertureChooser.getSelectedIndex());
	    brightnessBar.setValue(aperture.defaultBrightness());
	    zoomBar.setValue(oldZoom = 200);
	}
	cv.repaint(100);
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            if (applet == null) dispose(); else applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }
    
    abstract class Aperture {
	abstract String getName();
	abstract void compute();
	abstract Aperture createNext();
	abstract void drawGeometricShadow(Graphics g);
	abstract int getSelection(int x, int y);
	abstract boolean drag(int x, int y);
	abstract void setToDefaults();
	int defaultBrightness() { return 50; }
	boolean oneDimensional() { return false; }
	boolean hasXSymmetry() { return false; }
	boolean hasYSymmetry() { return false; }
	boolean hasDiagonalSymmetry() { return false; }
	boolean hideWhileDragging() { return true; }
	void rezoom(double x) {}
	abstract double getDimension();
	Aperture() { setToDefaults(); }
    };

    class CircularAperture extends Aperture {
	double radius;
	String getName() { return "circle"; }
	Aperture createNext() { return new HalfPlaneAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() { radius = .25; }
	void rezoom(double z) { radius *= z; }
	void compute() {
	    int i, j;
	    for (i = 0; i != gridSizeX/2; i++) {
		for (j = 0; j <= i; j++) {
		    clearAccum();
		    double x0 = (i/(double) gridSizeX)-.5;
		    double y0 = (j/(double) gridSizeY)-.5;
		    int th;
		    double cx = -x0;
		    double cy = -y0;
		    double c2 = cx*cx+cy*cy;
		    double cr = radius;
		    double c = c2 - cr*cr;
		    double ac4 = c*4;
		    double th1 = 0;
		    double th2 = pi2;
		    if (c <= 0) {
			// need to do all of them
		    } else {
			// figure out what angle range we need to do
			double r = java.lang.Math.sqrt(c2);
			double cx1 = cx/r;
			double cy1 = cy/r;
			double a1 = java.lang.Math.atan2(cy-cx1*cr, cx+cy1*cr);
			double a2 = java.lang.Math.atan2(cy+cx1*cr, cx-cy1*cr);
			th1 = (a1 < a2) ? a1 : a2;
			th2 = (a1 > a2) ? a1 : a2;
			if (th2-th1 > pi) {
			    th1 = (a1 > a2) ? a1 : a2;
			    th2 = (a1 < a2) ? a1 : a2;
			    th2 += pi2;
			}
		    }
		    int th1i = (int) ((th1*angleSteps)/pi2);
		    int th2i = (int) ((th2*angleSteps)/pi2);
		    while (th1i < 0) {
			th1i += angleSteps;
			th2i += angleSteps;
		    }
		    for (th = th1i; th < th2i; th++) {
			// find intersection of this ray with circle
			double costh = angcos1[th & angleStepsMask];
			double sinth = angsin1[th & angleStepsMask];
			double b = -2*(costh*cx+sinth*cy);
			double discrim = b*b-ac4;
			if (discrim < 0)
			    continue;
			discrim = java.lang.Math.sqrt(discrim);
			double r1 = .5*(-b-discrim);
			double r2 = .5*(-b+discrim);
			if (r1 < 0 && r2 < 0)
			    continue;
			if (r1 > 0)
			    apertureStart(r1);
			apertureStop(r2);
		    }
		    apertureStartOrigin(c < 0);
		    setFunction(i, j);
		}
	    }
	}

	void drawGeometricShadow(Graphics g) {
	    if (selection == 1)
		g.setColor(Color.yellow);
	    int r = (int) (winSize.width*radius);
	    g.drawOval(winSize.width/2-r, winSize.height/2-r,
		       r*2, r*2);
	}

	int getSelection(int x, int y) {
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    return (java.lang.Math.abs(r-radius) < 5./winSize.width) ? 1 : -1;
	}

	boolean drag(int x, int y) {
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    if (r == radius)
		return false;
	    radius = r;
	    return true;
	}
	
	double getDimension() { return radius*2; }
    }

    // general class to describe an aperture consisting entirely
    // of vertical lines.
    abstract class OneDimensionalAperture extends Aperture {
	public double lineLocations[];
	public int lineCount;
	boolean oneDimensional() { return true; }

	void compute() {
	    int i, j;
	    double result[] = new double[2];
	    // by default we use only green light.
	    int mink = 1, maxk = 1;
	    if (color) {
		mink = 0;
		maxk = 2;
	    }
	    int k;
	    int xlim = (hasXSymmetry()) ? gridSizeX/2 : gridSizeX;
	    double astart = (reversedCheck.getState()) ? -1 : 0;

	    // add in a right side at infinity if only one line
	    if (lineCount == 1)
		astart += .5;

	    for (i = 0; i != xlim; i++) {
		double x0 = (i/(double) gridSizeX)-.5;
		for (k = mink; k <= maxk; k++) {
		    double mult = colorLenMults[k];
		    double ar = astart, ai = astart;
		    int d = 1;
		    for (j = 0; j != lineCount; j++) {
			fresnl((x0-lineLocations[j])*mult, result);
			ar += d*result[0];
			ai += d*result[1];
			d = -d;
		    }
		    func[i][0][k] = .5*(ar*ar + ai*ai);
		}
	    }
	}

	double getDimension() {
	    return lineLocations[lineCount-1]-lineLocations[0];
	}

	void drawGeometricShadow(Graphics g) {
	    int i;
	    int symsel = -1;
	    if (selection != -1 && hasXSymmetry())
		symsel = lineCount-1-selection;
	    for (i = 0; i != lineCount; i++) {
		int x = (int) ((lineLocations[i]+.5) * winSize.width);
		g.setColor((selection == i || symsel == i) ?
			   Color.yellow : Color.red);
		g.drawLine(x, 0, x, winSize.height-1);
	    }
	}

	int getSelection(int x, int y) {
	    double xf = ((double) x)/winSize.width - .5;
	    double thresh = 3./winSize.width;
	    int sel = -1;
	    int i;
	    for (i = 0; i != lineCount; i++) {
		double dist = java.lang.Math.abs(lineLocations[i]-xf);
		if (dist < thresh) {
		    sel = i;
		    thresh = dist;
		}
	    }
	    return sel;
	}

	boolean drag(int x, int y) {
	    double xf = ((double) x)/winSize.width - .5;
	    if (selection > 0 && xf <= lineLocations[selection-1])
		return false;
	    if (selection < lineCount-1 && xf >= lineLocations[selection+1])
		return false;
	    if (hasXSymmetry() &&
		sign(lineLocations[selection]) != sign(xf))
		return false;
	    lineLocations[selection] = xf;
	    if (hasXSymmetry()) {
		int symsel = lineCount-1-selection;
		lineLocations[symsel] = -xf;
	    }
	    functionChanged = true;
	    return true;
	}

	boolean hideWhileDragging() { return false; }

	void rezoom(double z) {
	    int i;
	    for (i = 0; i != lineCount; i++)
		lineLocations[i] *= z;
	}
    }

    class HalfPlaneAperture extends OneDimensionalAperture {
	void setToDefaults() {
	    lineLocations = new double[lineCount = 1];
	    lineLocations[0] = 0;
	}
	String getName() { return "half plane"; }
	Aperture createNext() { return new SlitAperture(); }
	double getDimension() { return .5-lineLocations[0]; }
    }

    class SlitAperture extends OneDimensionalAperture {
	void setToDefaults() {
	    lineLocations = new double[lineCount = 2];
	    lineLocations[0] = -.06;
	    lineLocations[1] =  .06;
	}
	int defaultBrightness() { return 200; }
	String getName() { return "slit"; }
	Aperture createNext() { return new DoubleSlitAperture(); }
	boolean hasXSymmetry() { return true; }
    }

    class DoubleSlitAperture extends OneDimensionalAperture {
	void setToDefaults() {
	    lineLocations = new double[lineCount = 4];
	    lineLocations[0] = -.17;
	    lineLocations[1] = -1/8.0;
	    lineLocations[2] =  1/8.0;
	    lineLocations[3] =  .17;
	}
	int defaultBrightness() { return 140; }
	String getName() { return "double slit"; }
	Aperture createNext() { return new TripleSlitAperture(); }
	boolean hasXSymmetry() { return true; }
    }

    class TripleSlitAperture extends OneDimensionalAperture {
	void setToDefaults() {
	    lineLocations = new double[lineCount = 6];
	    lineLocations[0] = -.1533;
	    lineLocations[1] = -.1133;
	    lineLocations[2] = -.02;
	    lineLocations[3] =  .02;
	    lineLocations[4] =  .1133;
	    lineLocations[5] =  .1533;
	}
	int defaultBrightness() { return 210; }
	String getName() { return "triple slit"; }
	Aperture createNext() { return new SquareAperture(); }
	boolean hasXSymmetry() { return true; }
    }

    class SquareAperture extends BlockAperture {
	String getName() { return "square"; }
	Aperture createNext() { return new RectangularAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    double sqdim = .25;
	    blockCountX = blockCountY = 5;
	    blocks = new boolean[blockCountX][blockCountY];
	    blocks[2][2] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -sqdim;
	    lineXLocations[3] =  sqdim;
	    lineYLocations[1] = -sqdim;
	    lineYLocations[3] =  sqdim;
	}
	void setupRects() {
	    rectCount = 1;
	    rects = new double[1][5];
	    double sqdim = lineXLocations[3];
	    rects[0][0] = -sqdim;
	    rects[0][1] = -sqdim;
	    rects[0][2] = sqdim;
	    rects[0][3] = sqdim;
	    rects[0][4] = .5;
	}
    }

    class RectangularAperture extends BlockAperture {
	String getName() { return "rectangle"; }
	Aperture createNext() { return new CornerAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = blockCountY = 5;
	    blocks = new boolean[blockCountX][blockCountY];
	    blocks[2][2] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -.25;
	    lineXLocations[3] =  .25;
	    lineYLocations[1] = -.4;
	    lineYLocations[3] =  .4;
	}
	void setupRects() {
	    rectCount = 1;
	    rects = new double[1][5];
	    rects[0][0] = lineXLocations[1];
	    rects[0][1] = lineYLocations[1];
	    rects[0][2] = lineXLocations[3];
	    rects[0][3] = lineYLocations[3];
	    rects[0][4] = .5;
	}
    }

    class CornerAperture extends BlockAperture {
	String getName() { return "corner"; }
	Aperture createNext() { return new CrossAperture(); }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = blockCountY = 3;
	    blocks = new boolean[blockCountX][blockCountY];
	    blocks[2][2] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = 0;
	    lineYLocations[1] = 0;
	}
	void setupRects() {
	    rectCount = 1;
	    rects = new double[1][5];
	    double sqdim = lineXLocations[1];
	    rects[0][0] = sqdim;
	    rects[0][1] = sqdim;
	    rects[0][2] = 1e8;
	    rects[0][3] = 1e8;
	    rects[0][4] = .5;
	}
	double getDimension() { return .5-lineXLocations[1]; }
    }

    class CrossAperture extends BlockAperture {
	String getName() { return "cross"; }
	Aperture createNext() { return new RectanglesAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    double sqdim = 1/16.0;
	    blockCountX = blockCountY = 5;
	    blocks = new boolean[blockCountX][blockCountY];
	    blocks[0][2] = blocks[2][2] = blocks[4][2] =
		blocks[2][0] = blocks[2][4] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -sqdim;
	    lineXLocations[3] =  sqdim;
	    lineYLocations[1] = -sqdim;
	    lineYLocations[3] =  sqdim;
	}
	void setupRects() {
	    rectCount = 3;
	    rects = new double[3][5];
	    double sqdim = lineXLocations[3];

	    // the cross aperture is made up of two intersecting rectangles
	    // and then a third rectangle (their intersection) which prevents
	    // that area from being counted twice

	    rects[0][0] = -sqdim;
	    rects[0][1] = -1e8;
	    rects[0][2] =  sqdim;
	    rects[0][3] = 1e8;
	    rects[0][4] = .5;

	    rects[1][0] = -1e8;
	    rects[1][1] = -sqdim;
	    rects[1][2] = 1e8;
	    rects[1][3] = sqdim;
	    rects[1][4] = .5;

	    rects[2][0] = -sqdim;
	    rects[2][1] = -sqdim;
	    rects[2][2] = sqdim;
	    rects[2][3] = sqdim;
	    rects[2][4] = -.5;
	}
	double getDimension() { return 1; }
    }

    class RectanglesAperture extends BlockAperture {
	String getName() { return "2 rectangles"; }
	Aperture createNext() { return new FrameAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = 9;
	    blockCountY = 5;
	    blocks = new boolean[blockCountX][blockCountY];
	    blocks[2][2] = blocks[6][2] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -.375;
	    lineXLocations[3] = -.125;
	    lineXLocations[5] =  .125;
	    lineXLocations[7] =  .375;
	    lineYLocations[1] = -.25;
	    lineYLocations[3] =  .25;
	}
	void setupRects() {
	    rectCount = 2;
	    rects = new double[2][5];
	    double sqdim = lineXLocations[1];
	    rects[0][0] = lineXLocations[1];
	    rects[0][1] = lineYLocations[1];
	    rects[0][2] = lineXLocations[3];
	    rects[0][3] = lineYLocations[3];
	    rects[0][4] = .5;
	    rects[1][0] = lineXLocations[5];
	    rects[1][1] = lineYLocations[1];
	    rects[1][2] = lineXLocations[7];
	    rects[1][3] = lineYLocations[3];
	    rects[1][4] = .5;
	}
    }

    class FrameAperture extends BlockAperture {
	String getName() { return "frame"; }
	Aperture createNext() { return new PlusAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = blockCountY = 9;
	    blocks = new boolean[blockCountX][blockCountY];
	    int i;
	    for (i = 2; i <= 6; i += 2)
		blocks[i][2] = blocks[i][6] =
		blocks[2][i] = blocks[6][i] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -.375;
	    lineXLocations[3] = -.125;
	    lineXLocations[5] =  .125;
	    lineXLocations[7] =  .375;
	    lineYLocations[1] = -.375;
	    lineYLocations[3] = -.125;
	    lineYLocations[5] =  .125;
	    lineYLocations[7] =  .375;
	}
	void setupRects() {
	    rectCount = 2;
	    rects = new double[2][5];
	    // outer frame
	    rects[0][0] = lineXLocations[1];
	    rects[0][1] = lineYLocations[1];
	    rects[0][2] = lineXLocations[7];
	    rects[0][3] = lineYLocations[7];
	    rects[0][4] = .5;
	    // remove inner frame rectangle
	    rects[1][0] = lineXLocations[3];
	    rects[1][1] = lineYLocations[3];
	    rects[1][2] = lineXLocations[5];
	    rects[1][3] = lineYLocations[5];
	    rects[1][4] = -.5;
	}
    }

    class PlusAperture extends BlockAperture {
	String getName() { return "plus"; }
	Aperture createNext() { return new IntersectingSquaresAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = blockCountY = 9;
	    blocks = new boolean[blockCountX][blockCountY];
	    int i;
	    for (i = 2; i <= 6; i += 2)
		blocks[i][4] = blocks[4][i] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -.375;
	    lineXLocations[3] = -.125;
	    lineXLocations[5] =  .125;
	    lineXLocations[7] =  .375;
	    lineYLocations[1] = -.375;
	    lineYLocations[3] = -.125;
	    lineYLocations[5] =  .125;
	    lineYLocations[7] =  .375;
	}
	void setupRects() {
	    rectCount = 3;
	    rects = new double[3][5];
	    rects[0][0] = lineXLocations[1];
	    rects[0][1] = lineYLocations[3];
	    rects[0][2] = lineXLocations[7];
	    rects[0][3] = lineYLocations[5];
	    rects[0][4] = .5;

	    rects[1][0] = lineXLocations[3];
	    rects[1][1] = lineYLocations[1];
	    rects[1][2] = lineXLocations[5];
	    rects[1][3] = lineYLocations[7];
	    rects[1][4] = .5;

	    rects[2][0] = lineXLocations[3];
	    rects[2][1] = lineYLocations[3];
	    rects[2][2] = lineXLocations[5];
	    rects[2][3] = lineYLocations[5];
	    rects[2][4] = -.5;

	}
    }

    class IntersectingSquaresAperture extends BlockAperture {
	String getName() { return "2 squares"; }
	Aperture createNext() { return new DoubleCircleAperture(); }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() {
	    blockCountX = blockCountY = 9;
	    blocks = new boolean[blockCountX][blockCountY];
	    int i;
	    for (i = 2; i <= 6; i += 2)
		blocks[i][4] = blocks[4][i] = true;
	    blocks[2][2] = blocks[6][6] = true;
	    lineXLocations = new double[blockCountX];
	    lineYLocations = new double[blockCountY];
	    lineXLocations[1] = -.375;
	    lineXLocations[3] = -.125;
	    lineXLocations[5] =  .125;
	    lineXLocations[7] =  .375;
	    lineYLocations[1] = -.375;
	    lineYLocations[3] = -.125;
	    lineYLocations[5] =  .125;
	    lineYLocations[7] =  .375;
	}
	void setupRects() {
	    rectCount = 3;
	    rects = new double[3][5];
	    rects[0][0] = lineXLocations[1];
	    rects[0][1] = lineYLocations[1];
	    rects[0][2] = lineXLocations[5];
	    rects[0][3] = lineYLocations[5];
	    rects[0][4] = .5;

	    rects[1][0] = lineXLocations[3];
	    rects[1][1] = lineYLocations[3];
	    rects[1][2] = lineXLocations[7];
	    rects[1][3] = lineYLocations[7];
	    rects[1][4] = .5;

	    rects[2][0] = lineXLocations[3];
	    rects[2][1] = lineYLocations[3];
	    rects[2][2] = lineXLocations[5];
	    rects[2][3] = lineYLocations[5];
	    rects[2][4] = -.5;

	}
    }

    // general class used to describe apertures that consist of
    // rectangles, some of which may extend to infinity.  Each
    // rectangle may or may not be opaque.
    abstract class BlockAperture extends Aperture {
	int blockCountX, blockCountY;

	// All these arrays use a very strange system of indices.
	// Even numbers are blocks (areas between lines), and odd
	// numbers are lines.  The blocks at index n are bounded by
	// lines n-1 and n+1.  The blocks at index 0 are bounded by
	// -infinity and line 1.

	// true if a block is transparent.  Because we are talking
	// about blocks, only even indices are used.
	boolean blocks[][];

	// Lines.  Only odd indices used.
	double lineXLocations[];
	double lineYLocations[];

	int rectCount;
	double rects[][];
	abstract void setupRects();

	void compute() {
	    setupRects();
	    int i, j;
	    double result1[] = new double[2];
	    double result2[] = new double[2];
	    double result3[] = new double[2];
	    double result4[] = new double[2];
	    // by default we use only green light.
	    int mink = 1, maxk = 1;
	    if (color) {
		mink = 0;
		maxk = 2;
	    }
	    int k;
	    double astart = (reversedCheck.getState()) ? -1 : 0;
	    int xlim = (hasXSymmetry()) ? gridSizeX/2 : gridSizeX;
	    int ylim = (hasYSymmetry()) ? gridSizeY/2 : gridSizeY;
	    for (i = 0; i != xlim; i++) {
		if (hasDiagonalSymmetry())
		    ylim = i+1;
		double x0 = (i/(double) gridSizeX)-.5;
		for (j = 0; j != ylim; j++) {
		    double y0 = (j/(double) gridSizeY)-.5;
		    for (k = mink; k <= maxk; k++) {
			double mult = colorLenMults[k];
			double ar = 0, ai = astart;
			int l;
			// add up contributions from all rectangles
			for (l = 0; l != rectCount; l++) {
			    fresnl((rects[l][0]-x0)*mult, result1);
			    fresnl((rects[l][2]-x0)*mult, result2);
			    fresnl((rects[l][1]-y0)*mult, result3);
			    fresnl((rects[l][3]-y0)*mult, result4);
			    double ar1 = result1[0]-result2[0];
			    double ai1 = result1[1]-result2[1];
			    double ar2 = result3[0]-result4[0];
			    double ai2 = result3[1]-result4[1];
			    ar += rects[l][4]*(ar1*ar2 - ai1*ai2);
			    ai += rects[l][4]*(ar1*ai2 + ai1*ar2);
			}
			func[i][j][k] = ar*ar + ai*ai;
		    }
		}
	    }
	}

	void drawGeometricShadow(Graphics g) {
	    int i, j;
	    // go through each line and determine if the blocks on either
	    // side of it have different opacity, if so then draw a
	    // line there.
	    for (i = 1; i < blockCountX; i += 2)
		for (j = 0; j < blockCountY; j += 2) {
		    if (blocks[i-1][j] == blocks[i+1][j])
			continue;
		    int x  = (int) ((lineXLocations[i]+.5)*winSize.width);
		    int y1 = 0;
		    int y2 = winSize.height;
		    try {
			y1 = (int) ((lineYLocations[j-1]+.5)*winSize.height);
		    } catch (Exception e) { }
		    try {
			y2 = (int) ((lineYLocations[j+1]+.5)*winSize.height);
		    } catch (Exception e) { }
		    g.setColor(isSelected(i, -1) ? Color.yellow : Color.red);
		    g.drawLine(x, y1, x, y2);
		}
	    for (i = 0; i < blockCountX; i += 2)
		for (j = 1; j < blockCountY; j += 2) {
		    if (blocks[i][j-1] == blocks[i][j+1])
			continue;
		    int y  = (int) ((lineYLocations[j]+.5)*winSize.height);
		    int x1 = 0;
		    int x2 = winSize.width;
		    try {
			x1 = (int) ((lineXLocations[i-1]+.5)*winSize.width);
		    } catch (Exception e) { }
		    try {
			x2 = (int) ((lineXLocations[i+1]+.5)*winSize.width);
		    } catch (Exception e) { }
		    g.setColor(isSelected(-1, j) ? Color.yellow : Color.red);
		    g.drawLine(x1, y, x2, y);
		}
	}

	boolean isSelected(int x, int y) {
	    return isSelected(x, y, 0);
	}

	boolean isSelected(int x, int y, int iter) {
	    // determine if a line is selected, accounting for
	    // symmetry.
	    if (selection == -1)
		return false;
	    if (selection == x+100 || selection == y+200)
		return true;
	    if (hasXSymmetry() && iter < 1 && blockCountX > 3 &&
		isSelected(blockCountX-1-x, y, 1))
		return true;
	    if (hasYSymmetry() && iter < 2 && blockCountY > 3 &&
		isSelected(x, blockCountY-1-y, 2))
		return true;
	    if (hasDiagonalSymmetry() && iter < 3 &&
		isSelected(y, x, 3))
		return true;
	    return false;
	}

	int getSelection(int x, int y) {
	    double xf = ((double) x)/winSize.width - .5;
	    double yf = ((double) y)/winSize.width - .5;
	    double thresh = 3./winSize.width;
	    int sel = -1;
	    int i;
	    for (i = 1; i < blockCountX; i += 2) {
		double dist = java.lang.Math.abs(lineXLocations[i]-xf);
		if (dist < thresh) {
		    sel = 100+i;
		    thresh = dist;
		}
	    }
	    for (i = 1; i < blockCountY; i += 2) {
		double dist = java.lang.Math.abs(lineYLocations[i]-yf);
		if (dist < thresh) {
		    sel = 200+i;
		    thresh = dist;
		}
	    }
	    return sel;
	}

	boolean drag(int x, int y) {
	    double xf = ((double) x)/winSize.width - .5;
	    double yf = ((double) y)/winSize.width - .5;
	    if (selection >= 200)
		return dragLine(-1, selection-200, yf, 0);
	    else
		return dragLine(selection-100, -1, xf, 0);
	}

	void rezoom(double z) {
	    int i;
	    for (i = 1; i < blockCountX; i += 2)
		lineXLocations[i] *= z;
	    for (i = 1; i < blockCountY; i += 2)
		lineYLocations[i] *= z;
	}

	boolean dragLine(int x, int y, double loc, int iter) {
	    // drag a line and all lines it is related to by symmetry.
	    if (x != -1) {
		if (hasXSymmetry() && sign(lineXLocations[x]) != sign(loc))
		    return false;
		if (x > 1 && loc <= lineXLocations[x-2])
		    return false;
		if (x < blockCountX-2 && loc >= lineXLocations[x+2])
		    return false;
	    }
	    if (y != -1) {
		if (hasYSymmetry() && sign(lineYLocations[y]) != sign(loc))
		    return false;
		if (y > 1 && loc <= lineYLocations[y-2])
		    return false;
		if (y < blockCountY-2 && loc >= lineYLocations[y+2])
		    return false;
	    }
	    if (x != -1 && hasXSymmetry() && iter < 1)
		dragLine(blockCountX-1-x, y, -loc, 1);
	    if (y != -1 && hasYSymmetry() && iter < 2)
		dragLine(x, blockCountY-1-y, -loc, 2);
	    if (hasDiagonalSymmetry() && iter < 3)
		dragLine(y, x, loc, 3);
	    if (x != -1)
		lineXLocations[x] = loc;
	    if (y != -1)
		lineYLocations[y] = loc;
	    return true;
	}

	double getDimension() { return lineXLocations[blockCountX-2]-
				    lineXLocations[1]; }
    }

    class DoubleCircleAperture extends Aperture {
	double radius;
	double offset;
	String getName() { return "2 circles"; }
	Aperture createNext() { return new RingAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	void setToDefaults() { radius = .3; offset = .25; }
	double getDimension() { return (radius+offset)*2; }
	void compute() {
	    int i, j;
	    double points[] = new double[4];
	    double dx = offset;
	    double dy = 0;
	    boolean intersecting = (offset < radius);
	    if (intersecting) {
		dy = java.lang.Math.sqrt(radius*radius-offset*offset);
		double overflow = radius-2*offset;
		if (overflow > 0)
		    dx = offset+overflow;
	    }
	    for (i = 0; i != gridSizeX/2; i++) {
		for (j = 0; j != gridSizeY/2; j++) {
		    clearAccum();
		    double x0 = (i/(double) gridSizeX)-.5;
		    double y0 = (j/(double) gridSizeY)-.5;
		    int th;
		    double cx1 = -x0+offset;
		    double cx2 = -x0-offset;
		    double cy = -y0;
		    double c21 = cx1*cx1+cy*cy;
		    double c22 = cx2*cx2+cy*cy;
		    double cr2 = radius*radius;
		    double cd1 = c21 - cr2;
		    double cd2 = c22 - cr2;
		    double ac41 = cd1*4;
		    double ac42 = cd2*4;
		    double th1 = 0;
		    double th2 = pi2;
		    int th1i = (int) ((th1*angleSteps)/pi2);
		    int th2i = (int) ((th2*angleSteps)/pi2);
		    while (th1i < 0) {
			th1i += angleSteps;
			th2i += angleSteps;
		    }
		    boolean open = cd1 < 0 || cd2 < 0;
		    apertureStartOrigin(open);
		    for (th = th1i; th < th2i; th++) {
			double costh = angcos1[th & angleStepsMask];
			double sinth = angsin1[th & angleStepsMask];
			double b1 = -2*(costh*cx1+sinth*cy);
			double b2 = -2*(costh*cx2+sinth*cy);
			double discrim1 = b1*b1-ac41;
			double discrim2 = b2*b2-ac42;
			if (discrim1 < 0 && discrim2 < 0)
			    continue;
			int ct = 0;
			if (discrim1 >= 0) {
			    discrim1 = java.lang.Math.sqrt(discrim1);
			    points[ct++] = .5*(-b1-discrim1);
			    points[ct++] = .5*(-b1+discrim1);
			}
			if (discrim2 >= 0) {
			    discrim2 = java.lang.Math.sqrt(discrim2);
			    points[ct++] = .5*(-b2-discrim2);
			    points[ct++] = .5*(-b2+discrim2);
			}
			int si, sj;
			// sort the points
			for (si = 1; si < ct; si++) {
			    double v = points[si];
			    sj = si;
			    while (points[sj-1] > v) {
				points[sj] = points[sj-1]; sj--;
				if (sj <= 0) break;
			    }
			    points[sj] = v;
			}
			// go through the sorted points and call start/stop
			// alternately for each one
			boolean inOpen = open;
			for (si = 0; si != ct; si++) {
			    double r = points[si];
			    if (r < 0) continue;
			    double x1 = x0+costh*r;
			    double y1 = y0+sinth*r;
			    if (intersecting &&
				x1 > -dx && x1 < dx &&
				y1 > -dy && y1 < dy) {
				double y12 = y1*y1;
				if ((x1-offset)*(x1-offset)+y12 < cr2 ||
				    (x1+offset)*(x1+offset)+y12 < cr2)
				    continue;
			    }
			    if (!inOpen) {
				apertureStart(points[si]);
				inOpen = true;
			    } else {
				apertureStop(points[si]);
				inOpen = false;
			    }
			}
		    }
		    setFunction(i, j);
		}
	    }
	}

	void drawGeometricShadow(Graphics g) {
	    int r = (int) (winSize.width*radius);
	    int o = (int) (winSize.width*offset);
	    if (selection != -1) {
		g.setColor(selection == 0 ? Color.yellow : Color.red);
		int or = 5;
		g.fillOval(winSize.width/2-o-or, winSize.height/2-or,
			   or*2, or*2);
		g.fillOval(winSize.width/2+o-or, winSize.height/2-or,
			   or*2, or*2);
	    }
	    g.setColor(selection > 0 ? Color.yellow : Color.red);
	    int th = 0;
	    if (offset < radius)
		th = (int) (java.lang.Math.acos(offset/radius)*(180/pi));
	    g.drawArc(winSize.width/2-r-o, winSize.height/2-r,
		      r*2, r*2, th, 360-2*th);
	    g.drawArc(winSize.width/2-r+o, winSize.height/2-r,
		      r*2, r*2, 180+th, 360-2*th);
	}

	int getSelection(int x, int y) {
	    int o = (int) (winSize.width*offset);
	    int rx1 = (winSize.width/2-o)-x;
	    int rx2 = (winSize.width/2+o)-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx1*rx1+ry*ry)/winSize.width;
	    if (java.lang.Math.abs(r-radius) < 5./winSize.width)
		return 1;
	    if (r < 5./winSize.width)
		return 0;
	    r = java.lang.Math.sqrt(rx2*rx2+ry*ry)/winSize.width;
	    if (java.lang.Math.abs(r-radius) < 5./winSize.width)
		return 2;
	    if (r < 5./winSize.width)
		return 0;
	    return -1;
	}

	boolean drag(int x, int y) {
	    if (selection == 0) {
		// modify separation of circles
		double xf = ((double) x)/winSize.width - .5;
		double o = java.lang.Math.abs(xf);
		if (o == offset)
		    return false;
		offset = o;
		return true;
	    }
	    // modify radius of circles
	    int o = (int) (winSize.width*offset);
	    int rx1 = (winSize.width/2-o)-x;
	    int rx2 = (winSize.width/2+o)-x;
	    int ry = winSize.height/2-y;
	    double rr;
	    if (selection == 2)
		rr = rx2*rx2;
	    else
		rr = rx1*rx1;
	    double r = java.lang.Math.sqrt(rr+ry*ry)/winSize.width;
	    if (r == radius)
		return false;
	    radius = r;
	    return true;
	}
	void rezoom(double z) {
	    radius *= z;
	    offset *= z;
	}
    }

    class RingAperture extends Aperture {
	double radius1, radius2;
	String getName() { return "ring"; }
	Aperture createNext() { return new HalfCircleAperture(); }
	boolean hasXSymmetry() { return true; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return true; }
	void setToDefaults() { radius1 = .15; radius2 = .25; }
	void rezoom(double z) { radius1 *= z; radius2 *= z; }
	void compute() {
	    int i, j;
	    for (i = 0; i != gridSizeX/2; i++) {
		for (j = 0; j <= i; j++) {
		    clearAccum();
		    double x0 = (i/(double) gridSizeX)-.5;
		    double y0 = (j/(double) gridSizeY)-.5;
		    int th;
		    double cx = -x0;
		    double cy = -y0;
		    double c2 = cx*cx+cy*cy;
		    double cr = radius2;
		    double c = c2 - cr*cr;
		    double ac4 = c*4;
		    double cin = c2 - radius1*radius1;
		    double ac4in = cin*4;
		    int th1 = 0;
		    if (c <= 0) {
			// need to do all angles, so start anywhere
		    } else {
			// figure out where to start
			double a = java.lang.Math.atan2(cy, cx);
			th1 = (int) ((a*angleSteps)/pi2);
		    }
		    int dir = 1;
		    for (th = th1; th != th1+angleSteps; th += dir) {
			// find intersection of this ray with circles
			double costh = angcos1[th & angleStepsMask];
			double sinth = angsin1[th & angleStepsMask];
			double b = -2*(costh*cx+sinth*cy);
			double discrim_out = b*b-ac4;
			double discrim_in  = b*b-ac4in;
			if (discrim_out < 0 && discrim_in < 0) {
			    if (dir == -1)
				break;
			    dir = -1;
			    th = th1;
			    continue;
			}
			double disc = java.lang.Math.sqrt(discrim_in);
			double r1in = .5*(-b-disc);
			double r2in = .5*(-b+disc);
			disc = java.lang.Math.sqrt(discrim_out);
			double r1out = .5*(-b-disc);
			double r2out = .5*(-b+disc);
			// r1out < r1in < r2in < r2out
			if (r1out > 0)
			    apertureStart(r1out);
			if (r1in > 0)
			    apertureStop(r1in);
			if (r2in > 0)
			    apertureStart(r2in);
			if (r2out > 0)
			    apertureStop(r2out);
		    }
		    apertureStartOrigin(c < 0 && cin >= 0);
		    setFunction(i, j);
		}
	    }
	}

	void drawGeometricShadow(Graphics g) {
	    if (selection == 1)
		g.setColor(Color.yellow);
	    int r = (int) (winSize.width*radius1);
	    g.drawOval(winSize.width/2-r, winSize.height/2-r,
		       r*2, r*2);
	    g.setColor(Color.red);
	    if (selection == 2)
		g.setColor(Color.yellow);
	    r = (int) (winSize.width*radius2);
	    g.drawOval(winSize.width/2-r, winSize.height/2-r,
		       r*2, r*2);
	}

	int getSelection(int x, int y) {
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    return (java.lang.Math.abs(r-radius1) < 5./winSize.width) ? 1 :
		   (java.lang.Math.abs(r-radius2) < 5./winSize.width) ? 2 : -1;
	}

	boolean drag(int x, int y) {
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    if (selection == 1) {
		if (r == radius1 || r >= radius2)
		    return false;
		radius1 = r;
	    } else {
		if (r == radius2 || r <= radius1)
		    return false;
		radius2 = r;
	    }
	    return true;
	}
	
	double getDimension() { return radius2*2; }
    }

    class HalfCircleAperture extends Aperture {
	double radius;
	String getName() { return "half circle"; }
	Aperture createNext() { return null; }
	boolean hasXSymmetry() { return false; }
	boolean hasYSymmetry() { return true; }
	boolean hasDiagonalSymmetry() { return false; }
	void setToDefaults() { radius = .25; }
	void rezoom(double z) { radius *= z; }
	void compute() {
	    int i, j;
	    for (i = 0; i != gridSizeX; i++) {
		for (j = 0; j != gridSizeY/2; j++) {
		    clearAccum();
		    // offset the x coord slightly to avoid problems
		    // when calculating intersection with y axis
		    double x0 = ((i+.25)/(double) gridSizeX)-.5;
		    double y0 = (j/(double) gridSizeY)-.5;
		    int th;
		    double cx = -x0;
		    double cy = -y0;
		    double c2 = cx*cx+cy*cy;
		    double cr = radius;
		    double c = c2 - cr*cr;
		    double ac4 = c*4;
		    int th1 = 0;
		    if (c <= 0) {
			// need to do all angles, so start anywhere
		    } else {
			// figure out where to start
			double a = java.lang.Math.atan2(cy, cx);
			th1 = (int) ((a*angleSteps)/pi2);
		    }
		    int dir = 1;
		    for (th = th1; th != th1+angleSteps; th += dir) {
			// find intersection of this ray with circle
			double costh = angcos1[th & angleStepsMask];
			double sinth = angsin1[th & angleStepsMask];
			double b = -2*(costh*cx+sinth*cy);
			double discrim = b*b-ac4;
			if (discrim < 0) {
			    if (dir == -1)
				break;
			    dir = -1;
			    th = th1;
			    continue;
			}
			// find intersection of this ray with y axis
			// solve x+t*costh = 0
			double ry = -x0/costh;
			discrim = java.lang.Math.sqrt(discrim);
			double r1 = .5*(-b-discrim);
			double r2 = .5*(-b+discrim);

			// if intersections are left of y axis,
			// replace with y axis intersection
			if (x0+r1*costh < 0)
			    r1 = ry;
			if (x0+r2*costh < 0) {
			    r2 = ry;
			    if (r1 == ry) {
				if (dir == -1)
				    break;
				dir = -1;
				th = th1;
				continue;
			    }
			}
			if (r1 < 0 && r2 < 0)
			    continue;
			if (r1 > 0)
			    apertureStart(r1);
			apertureStop(r2);
		    }
		    apertureStartOrigin(c < 0 && x0 >= 0);
		    setFunction(i, j);
		}
	    }
	}

	void drawGeometricShadow(Graphics g) {
	    if (selection == 1)
		g.setColor(Color.yellow);
	    int r = (int) (winSize.width*radius);
	    g.drawArc(winSize.width/2-r, winSize.height/2-r,
		      r*2, r*2, -90, 180);
	    g.drawLine(winSize.width/2, winSize.height/2-r,
		       winSize.width/2, winSize.height/2+r);
	}

	int getSelection(int x, int y) {
	    if (x < 0)
		return -1;
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    return (java.lang.Math.abs(r-radius) < 5./winSize.width) ? 1 : -1;
	}

	boolean drag(int x, int y) {
	    int rx = winSize.width/2-x;
	    int ry = winSize.height/2-y;
	    double r = java.lang.Math.sqrt(rx*rx+ry*ry)/winSize.width;
	    if (r == radius)
		return false;
	    radius = r;
	    return true;
	}
	
	double getDimension() { return radius*2; }
    }

    // fresnel integral routines from Cephes Math Library

    /* S(x) for small x */
    static final double sn[] = {
	-2.99181919401019853726E3,
	7.08840045257738576863E5,
	-6.29741486205862506537E7,
	2.54890880573376359104E9,
	-4.42979518059697779103E10,
	3.18016297876567817986E11,
    };
    static final double sd[] = {
	2.81376268889994315696E2,
	4.55847810806532581675E4,
	5.17343888770096400730E6,
	4.19320245898111231129E8,
	2.24411795645340920940E10,
	6.07366389490084639049E11,
    };
    
    static final double cn[] = {
	-4.98843114573573548651E-8,
	9.50428062829859605134E-6,
	-6.45191435683965050962E-4,
	1.88843319396703850064E-2,
	-2.05525900955013891793E-1,
	9.99999999999999998822E-1,
    };
    static final double cd[] = {
	3.99982968972495980367E-12,
	9.15439215774657478799E-10,
	1.25001862479598821474E-7,
	1.22262789024179030997E-5,
	8.68029542941784300606E-4,
	4.12142090722199792936E-2,
	1.00000000000000000118E0,
    };

    /* Auxiliary function f(x) */
    static final double fn[] = {
	4.21543555043677546506E-1,
	1.43407919780758885261E-1,
	1.15220955073585758835E-2,
	3.45017939782574027900E-4,
	4.63613749287867322088E-6,
	3.05568983790257605827E-8,
	1.02304514164907233465E-10,
	1.72010743268161828879E-13,
	1.34283276233062758925E-16,
	3.76329711269987889006E-20,
    };
    static final double fd[] = {
	/*  1.00000000000000000000E0, */
	7.51586398353378947175E-1,
	1.16888925859191382142E-1,
	6.44051526508858611005E-3,
	1.55934409164153020873E-4,
	1.84627567348930545870E-6,
	1.12699224763999035261E-8,
	3.60140029589371370404E-11,
	5.88754533621578410010E-14,
	4.52001434074129701496E-17,
	1.25443237090011264384E-20,
    };
    
    /* Auxiliary function g(x) */
    static final double gn[] = {
	5.04442073643383265887E-1,
	1.97102833525523411709E-1,
	1.87648584092575249293E-2,
	6.84079380915393090172E-4,
	1.15138826111884280931E-5,
	9.82852443688422223854E-8,
	4.45344415861750144738E-10,
	1.08268041139020870318E-12,
	1.37555460633261799868E-15,
	8.36354435630677421531E-19,
	1.86958710162783235106E-22,
    };

    static final double gd[] = {
	/*  1.00000000000000000000E0, */
	1.47495759925128324529E0,
	3.37748989120019970451E-1,
	2.53603741420338795122E-2,
	8.14679107184306179049E-4,
	1.27545075667729118702E-5,
	1.04314589657571990585E-7,
	4.60680728146520428211E-10,
	1.10273215066240270757E-12,
	1.38796531259578871258E-15,
	8.39158816283118707363E-19,
	1.86958710162783236342E-22,
    };

    static final double PI = pi;
    static final double PIBYTWO = pi/2;

    int fresnl(double xxa, double result[]) {
	double f, g, cc, ss, c, s, t, u;
	double x, x2;

	while (true) {
	    x = java.lang.Math.abs(xxa);
	    x2 = x * x;
	    if (x2 < 2.5625) {
		t = x2 * x2;
		ss = x * x2 * polevl(t, sn, 5) / p1evl(t, sd, 6);
		cc = x * polevl(t, cn, 5) / polevl(t, cd, 6);
		break;
	    }
	    if (x > 36974.0) {
		cc = 0.5;
		ss = 0.5;
		break;
	    }
	    /* Auxiliary functions for large argument  */
	    x2 = x * x;
	    t = PI * x2;
	    u = 1.0 / (t * t);
	    t = 1.0 / t;
	    f = 1.0 - u * polevl(u, fn, 9) / p1evl(u, fd, 10);
	    g = t * polevl(u, gn, 10) / p1evl(u, gd, 11);
	    t = PIBYTWO * x2;
	    c = java.lang.Math.cos(t);
	    s = java.lang.Math.sin(t);
	    t = PI * x;
	    cc = 0.5 + (f * s - g * c) / t;
	    ss = 0.5 - (f * c + g * s) / t;
	    break;
	}
	
	if (xxa < 0.0) {
	    cc = -cc;
	    ss = -ss;
	}
	result[0] = cc;
	result[1] = ss;
	return (0);
    }

    double polevl(double x, double coef[], int N) {
	double ans;
	int i;
	int p = 0;
	
	ans = coef[p++];
	i = N;
	
	do
	    ans = ans * x + coef[p++];
	while (--i > 0);
	
	return (ans);
    }

    double p1evl(double x, double coef[], int N) {
	double ans;
	int p = 0;
	int i;
	
	ans = x + coef[p++];
	i = N - 1;
	
	do
	    ans = ans * x + coef[p++];
	while (--i > 0);
	
	return (ans);
    }
}
