// Wave2d.java (c) 2004 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.io.File; import java.util.Random; import java.util.Arrays; import java.lang.Math; import java.text.NumberFormat; import java.awt.event.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; class Wave2dCanvas extends Canvas { Wave2dFrame pg; Wave2dCanvas(Wave2dFrame p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateWave2d(g); } public void paint(Graphics g) { pg.updateWave2d(g); } }; class Wave2dLayout implements LayoutManager { public Wave2dLayout() {} 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* 7/10; 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 Wave2d extends Applet implements ComponentListener { static Wave2dFrame 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 Wave2dFrame(null); ogf.init(); } void showFrame() { if (ogf == null) { started = true; ogf = new Wave2dFrame(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 Wave2dFrame extends Frame implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener, ItemListener { Thread engine = null; Dimension winSize; Image dbimage; Random random; int windowWidth = 50; int windowHeight = 50; int wallWidth; public String getAppletInfo() { return "Wave2d by Paul Falstad"; } Checkbox stoppedCheck; Checkbox intensityCheck; Checkbox triChromaticCheck; Checkbox symmCheck; Checkbox infoCheck; Choice setupChooser; Vector setupList; Setup setup; Button resetTimeButton; Scrollbar zoomBar; Scrollbar angleBar; Scrollbar freqBar; Scrollbar resBar; Scrollbar speedBar; Scrollbar brightnessBar; double colorMult; double wavelength; Label auxLabels[]; Scrollbar auxBars[]; static final int auxBarCount = 5; static final double pi = 3.14159265358979323846; static final int apertureHeight = 16; float colorFunc[]; double apertureR[]; double apertureI[]; int dragX, dragY; boolean dragging; boolean dragClear; boolean dragSet; boolean recompute; double t; int pause; MemoryImageSource imageSource; int pixels[]; String muString = "u"; int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } Wave2dCanvas cv; Wave2d applet; Wave2dFrame(Wave2d a) { super("Wave2d Applet v1.2c"); applet = a; } boolean useBufferedImage = false; public void init() { setupList = new Vector(); Setup s = new SingleSlitSetup(); while (s != null) { setupList.addElement(s); s = s.createNext(); } String os = System.getProperty("os.name"); int res = 120; String jv = System.getProperty("java.class.version"); double jvf = new Double(jv).doubleValue(); muString = "u"; if (jvf >= 48) { muString = "\u03bc"; useBufferedImage = true; } setLayout(new Wave2dLayout()); cv = new Wave2dCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); add(cv); setupChooser = new Choice(); int i; for (i = 0; i != setupList.size(); i++) setupChooser.add("Setup: " + ((Setup) setupList.elementAt(i)).getName()); setup = (Setup) setupList.elementAt(0); setupChooser.addItemListener(this); add(setupChooser); intensityCheck = new Checkbox("Show Intensity", true); intensityCheck.addItemListener(this); add(intensityCheck); stoppedCheck = new Checkbox("Stopped"); stoppedCheck.addItemListener(this); stoppedCheck.disable(); add(stoppedCheck); triChromaticCheck = new Checkbox("Tri-Chromatic", false); triChromaticCheck.addItemListener(this); add(triChromaticCheck); /*symmCheck = new Checkbox("symm", false); symmCheck.addItemListener(this); add(symmCheck);*/ infoCheck = new Checkbox("Show Units", false); infoCheck.addItemListener(this); add(infoCheck); add(resetTimeButton = new Button("Reset Time")); resetTimeButton.addActionListener(this); add(new Label("Incidence Angle", Label.CENTER)); add(angleBar = new Scrollbar(Scrollbar.HORIZONTAL, 90, 1, 10, 170)); angleBar.addAdjustmentListener(this); add(new Label("Speed", Label.CENTER)); add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 70, 1, 1, 200)); speedBar.addAdjustmentListener(this); speedBar.disable(); add(new Label("Zoom Out", Label.CENTER)); add(zoomBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 10, 200)); zoomBar.addAdjustmentListener(this); add(new Label("Resolution", Label.CENTER)); add(resBar = new Scrollbar(Scrollbar.HORIZONTAL, res, 5, 120, 600)); resBar.addAdjustmentListener(this); add(new Label("Source Frequency", Label.CENTER)); add(freqBar = new Scrollbar(Scrollbar.HORIZONTAL, 120, 1, 1, 236)); freqBar.addAdjustmentListener(this); add(new Label("Brightness", Label.CENTER)); add(brightnessBar = new Scrollbar(Scrollbar.HORIZONTAL, 27, 1, 1, 1000)); brightnessBar.addAdjustmentListener(this); auxLabels = new Label[auxBarCount]; auxBars = new Scrollbar[auxBarCount]; for (i = 0; i != auxBarCount; i++) { add(auxLabels[i] = new Label("Aux " + (i+1), Label.CENTER)); add(auxBars[i] = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 1, 100)); auxBars[i].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(); setResolution(); reinit(); setup = (Setup) setupList.elementAt(0); cv.setBackground(Color.black); cv.setForeground(Color.lightGray); resize(750, 600); handleResize(); Dimension x = getSize(); Dimension screen = getToolkit().getScreenSize(); setLocation((screen.width - x.width)/2, (screen.height - x.height)/2); show(); } void reinit() { doSetup(); } void apertureChanged() { clearAperture(); setup.doAperture(); recompute = true; } 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 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) { } boolean calculateNotice; long lastTime; public void updateWave2d(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 sometimes. handleResize(); return; } if (!calculateNotice && recompute) { FontMetrics fm = realg.getFontMetrics(); realg.setColor(Color.black); String cs = "Calculating..."; realg.fillRect(0, winSize.height-30, 20+fm.stringWidth(cs), 30); realg.setColor(Color.white); realg.drawString(cs, 10, winSize.height-10); cv.repaint(0); calculateNotice = true; return; } calculateNotice = false; double tadd = 0; if (!stoppedCheck.getState()) { int val = speedBar.getValue(); tadd = Math.exp(val/20.)*(.1/5); long sysTime = System.currentTimeMillis(); if (lastTime == 0) lastTime = sysTime; tadd *= (sysTime-lastTime)*(1/50.); tadd *= freqBar.getValue() / 34.; t += tadd; lastTime = sysTime; } else lastTime = 0; double trotr = Math.cos(t); double troti = Math.sin(t); int i, j; boolean stopFunc = false; if (stoppedCheck.getState()) stopFunc = true; boolean obstacle = setup instanceof ObstacleSetup; double zoom = (zoomBar.getValue()/10.); if (recompute) { recompute = false; double fm = (angleBar.getValue()-90) * pi/180; int apStart = -1, apEnd = -1; // find width of area we need results for int compWidth = (int) (zoom*windowWidth); // find width of aperture boolean symm0 = true; boolean symm1 = true; for (i = 0; i != wallWidth; i++) { if (apertureR[i] != 0 || apertureI[i] != 0) { if (apertureR[i] != apertureR[wallWidth-1-i] || apertureI[i] != apertureI[wallWidth-1-i]) symm1 = false; if (i == 0 || apertureR[i] != apertureR[wallWidth-i] || apertureI[i] != apertureI[wallWidth-i]) symm0 = false; apEnd = i; if (apStart == -1) apStart = i; } } boolean symmetric = (symm0 || symm1) && !obstacle && fm == 0; if (apStart == -1) apStart = apEnd = wallWidth/2; // compute how much wavefront data we need to compute for each line. // -waveEnd, waveEnd = start and end of the wavefront data (where 0 is // the center, directly under the aperture point). We need enough data // for the right side of the aperture to reach the left side of the // screen, and vice versa. We assume that the aperture is symmetric. int waveEnd = wallWidth/2+compWidth/2-apStart; // compute how big the FFT arrays have to be. They must be big enough // to hold the result, plus enough offscreen space so that the waves do // not wrap around to the other side of the screen. And they must be // a power of 2. int spaceNeeded = apEnd-apStart+1; int symmStop = spaceNeeded/2; if (symmetric) compWidth = compWidth/2 + (apEnd-apStart)/2; int sz = 1; while (sz < compWidth + spaceNeeded) sz *= 2; /*System.out.println(symm0 + " " + symm1 + " " + symmetric); System.out.println("ap " + apStart + " " + apEnd + " " + "we " + waveEnd + " " + "cw " + compWidth + " " + sz + " " + zoom + " sn " + spaceNeeded);*/ int symmReflect = (symm0) ? sz : sz-1; int szm = sz*2-1; float line1[] = new float[sz*2]; float line2[] = new float[sz*2]; for (i = 0; i != wallWidth; i++) if (apertureR[i] != 0 || apertureI[i] != 0) { int ii = (i - wallWidth/2)*2; int i0 = i - wallWidth/2; double a = Math.cos(fm*i0); double b = Math.sin(fm*i0); line2[ii & szm] = (float) (a * apertureR[i] - b * apertureI[i]); line2[(ii+1) & szm] = (float) (a * apertureI[i] + b * apertureR[i]); } FFT fft = new FFT(sz); fft.transform(line2, false); //System.out.print("computing..."); int f = 0; int fstart = triChromaticCheck.getState() ? 0 : 1; int fend = triChromaticCheck.getState() ? 2 : 1; if (intensityCheck.getState()) t = 1e8; else triChromaticCheck.setState(false); int cfoff = 0; for (f = fstart; f <= fend; f++) { double m = freqBar.getValue()/50.; if (f == 0) m /= (650/510.); // red, 650 nm (green = 510 nm) if (f == 2) m /= (475/510.); // blue, 475 nm double m0 = Math.sqrt(m); wavelength = 2*pi/m; double freq = 1/(2*pi); double speed = wavelength * freq; // if wavelength is smaller than 2 pixels, don't bother trying // to draw waves. boolean noWaves = wavelength < 2*zoom; // if the reset time button gets hit, we have to recompute every // frame until the wave this the edges of the screen. // 1.23 =~ sqrt(1 + 1/2) if (t < resBar.getValue()*zoom*1.23/speed) recompute = true; for (j = 0; j != windowHeight; j++) { for (i = 0; i != sz*2; i++) line1[i] = 0; int jj = j+1; double jz2 = jj*jj*zoom*zoom; int i2; int sz2 = sz*2; // if reset time button was hit, limit wave train double maxdist = t*speed-wavelength/8; for (i = i2 = 0; i <= waveEnd; i++, i2 += 2) { double dist1 = Math.sqrt(i*i+jz2); double dist2 = dist1*m; computeBessel(dist2); if (dist1 > maxdist) bessj0 = bessy0 = 0; float f1 = (float) (bessj0*m0); float f2 = (float) (-bessy0*m0); if (i > 0) { line1[sz2-i2] = f1; line1[sz2-i2+1] = f2; } if (!symmetric || i < spaceNeeded) { line1[i2] = f1; line1[i2+1] = f2; } } long t1 = System.currentTimeMillis(); fft.transform(line1, false); for (i = 0; i != sz; i++) { int ii = i*2; float a = line1[ii]*line2[ii] - line1[ii+1]*line2[ii+1]; float b = line1[ii+1]*line2[ii] + line1[ii]*line2[ii+1]; line1[ii] = a; line1[ii+1] = b; } fft.transform(line1, true); float qmult = 400.f/sz; if (obstacle) { float oaddr = (float)(Math.cos(jj*zoom*m)*800/m0); float oaddi = -(float)(Math.sin(jj*zoom*m)*800/m0); int ww = (int) (windowWidth * zoom); for (i = 0; i != ww; i++) { int ii = i-ww/2; float a = (float)Math.cos(fm*ii); float b = (float)Math.sin(fm*ii); float aa = (a * oaddr - b * oaddi) / qmult; float bb = (a * oaddi + b * oaddr) / qmult; line1[szm & (ii*2) ] = aa-line1[szm & (ii*2)]; line1[szm & (ii*2)+1] = bb-line1[szm & (ii*2)+1]; } } for (i = 0; i != windowWidth; i++) { double ir1 = (int) ((i-windowWidth/2 )*zoom); double ir2 = (int) ((i-windowWidth/2+1)*zoom); int ii1 = (int) ir1; int ii2 = (int) ir2; int iic = ii2-ii1; if (intensityCheck.getState()) colorFunc[cfoff] = 0; else { colorFunc[cfoff] = 0; colorFunc[cfoff+1] = 0; } int ii; for (ii = ii1; ii <= ii2; ii++) { double q1 = 0; double q2 = 0; if (symmetric && ii >= symmStop) { q1 = line1[szm & ((symmReflect-ii)*2)]*qmult; q2 = line1[szm & ((symmReflect-ii)*2+1)]*qmult; } else { q1 = line1[szm & (ii*2 )]*qmult; q2 = line1[szm & (ii*2+1)]*qmult; } double mu = .001; if (ii == ii1) mu *= 1-(ir1-ii1); else if (ii == ii2) mu *= ir2-ii2; if (intensityCheck.getState()) colorFunc[cfoff] += (q1*q1+q2*q2) * mu; else if (noWaves) { // need to replace q1, q2 with the magnitude, or // else the waves average out and cancel when // using zoom colorFunc[cfoff] += Math.sqrt(q1*q1+q2*q2) * mu; } else { colorFunc[cfoff ] += q1*mu; colorFunc[cfoff+1] += q2*mu; } } if (intensityCheck.getState()) { colorFunc[cfoff++] /= ir2-ir1; } else { colorFunc[cfoff++] /= ir2-ir1; colorFunc[cfoff++] /= ir2-ir1; } } } } //System.out.println("done"); } colorMult = 30*Math.exp(brightnessBar.getValue()/50.-10); if (!intensityCheck.getState()) colorMult *= 1.5; int ix = 0; int k, l; int height = winSize.height - apertureHeight; int cfoff = 0; int grncfadd = windowWidth*windowHeight; int blucfadd = grncfadd*2; double m = freqBar.getValue()/50.; wavelength = 2*pi/m; boolean noWaves = wavelength < 2*zoom; for (j = 0; j != windowHeight; j++) { for (i = 0; i != windowWidth; i++, cfoff++) { int x = i*winSize.width/windowWidth; int y = j*height/windowHeight + apertureHeight; int x2 = (i+1)*winSize.width/windowWidth; int y2 = (j+1)*height/windowHeight + apertureHeight; int colval = 0; if (intensityCheck.getState()) { if (triChromaticCheck.getState()) colval = 0xFF000000 + (getColorValue(cfoff) << 16) | (getColorValue(cfoff+grncfadd) << 8) | (getColorValue(cfoff+blucfadd)); else colval = 0xFF000000 + (getColorValue(cfoff) << 8); } else { double q1 = colorFunc[cfoff++]; double q2 = colorFunc[cfoff]; if (noWaves) { int qq = (int) (q1*255); if (qq > 255) qq = 255; colval = 0xFF000000 + qq*0x10100; } else { double q = (q1*trotr - q2*troti)*colorMult; if (q > 0) { int qq = (int) (q*255); if (qq > 255) qq = 255; colval = 0xFF000000 + (qq << 8); } else { int qq = (int) (-q*255); if (qq > 255) qq = 255; colval = 0xFF000000 + (qq << 16); } } } for (k = x; k < x2; k++) for (l = y; l < y2; l++) pixels[k+l*winSize.width] = colval; } } //System.out.println(zoom + " " + windowWidth + " " + winSize.width); for (i = 0; i != windowWidth; i++) { double ir1 = (int) ((i-windowWidth/2 )*zoom); double ir2 = (int) ((i-windowWidth/2+1)*zoom); int ii1 = (int) ir1; int ii2 = (int) ir2; int iic = ii2-ii1; int ii; double funcr = 0, funcb = 0; for (ii = ii1; ii <= ii2; ii++) { //System.out.println(i + " " + ii + " " + zoom); int ij = ii+wallWidth/2; double mu = 1; if (ii == ii1) mu *= 1-(ir1-ii1); else if (ii == ii2) mu *= ir2-ii2; if (ij < 0 || ij >= wallWidth) { funcb += mu; continue; } funcb += (1-(apertureR[ij]*apertureR[ij] + apertureI[ij]*apertureI[ij]))*mu; double ph = Math.atan2(apertureI[ij], apertureR[ij])/pi; if (ph < 0) funcr += (2+ph)*mu; else funcr += ph*mu; } funcb /= ir2-ir1; funcr /= (ir2-ir1)*2; //System.out.println(i + " " + funcb); if (obstacle) funcb = 1-funcb; int valb = (int) (funcb*255); int valr = (int) (funcr*255); if (valb < 0) valb = 0; if (valb > 255) valb = 255; if (valr < 0) valr = 0; if (valb > 255) valb = 255; int colval = 0xFF000000 + (valr << 16) | valb; int x = i*winSize.width/windowWidth; int x2 = (i+1)*winSize.width/windowWidth; int y = 0; int y2 = apertureHeight; for (k = x; k < x2; k++) for (l = y; l < y2; l++) pixels[k+l*winSize.width] = colval; } if (imageSource != null) imageSource.newPixels(); realg.drawImage(dbimage, 0, 0, this); if (infoCheck.getState()) { int aw = auxBars[0].getValue(); String s1 = setup.getInfo(0); String s2 = setup.getInfo(1); String s3 = "Screen height = " + getLength(zoom*windowHeight); realg.setColor(Color.black); FontMetrics fm = realg.getFontMetrics(); int ms = s1 == null ? 0 : fm.stringWidth(s1); ms = s2 == null ? 0 : max(fm.stringWidth(s2), ms); ms = max(fm.stringWidth(s3), ms); int x = 20+ms; int h = s1 == null ? (s2 == null ? 30 : 50) : 70; realg.fillRect(winSize.width-x, winSize.height-h, x, h); realg.setColor(Color.white); if (s1 != null) realg.drawString(s1, winSize.width-x+10, winSize.height-50); if (s2 != null) realg.drawString(s2, winSize.width-x+10, winSize.height-30); realg.drawString(s3, winSize.width-x+10, winSize.height-10); } if (!intensityCheck.getState() && !stoppedCheck.getState()) cv.repaint(pause); } int max(int m1, int m2) { return (m1 > m2) ? m1 : m2; } String getLength(double pix) { NumberFormat nf = NumberFormat.getInstance(); nf.setMaximumFractionDigits(2); double l = pix*510/wavelength; if (l < 1e3) return nf.format(l) + " nm"; if (l < 1e6) return nf.format(l*1e-3) + " " + muString + "m"; if (l < 1e9) return nf.format(l*1e-6) + " mm"; return nf.format(l*1e-9) + " m"; } int getColorValue(int i) { int val = (int) (colorFunc[i] * colorMult); if (val > 255) val = 255; return val; } int abs(int x) { return x < 0 ? -x : x; } int sign(int x) { return (x < 0) ? -1 : (x == 0) ? 0 : 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() == resetTimeButton) { t = 0; recompute = true; cv.repaint(); } } public void adjustmentValueChanged(AdjustmentEvent e) { System.out.print(((Scrollbar) e.getSource()).getValue() + "\n"); if (e.getSource() == resBar && resBar.getValue() != resBarValue) setResolution(); int i; for (i = 0; i != auxBarCount; i++) if (e.getSource() == auxBars[i]) { apertureChanged(); break; } if (e.getSource() == zoomBar || e.getSource() == angleBar || e.getSource() == freqBar) recompute = true; setResetTimeButton(); cv.repaint(pause); } void setResetTimeButton() { // Reset Time button doesn't work well with obstacle or lo freqs if (setup instanceof ObstacleSetup || freqBar.getValue() < 8 || intensityCheck.getState()) resetTimeButton.disable(); else resetTimeButton.enable(); } int resBarValue = -1; void setResolution() { resBarValue = windowWidth = windowHeight = resBar.getValue(); if ((windowWidth & 1) == 1) windowWidth = windowHeight = resBarValue-1; colorFunc = new float[windowWidth*windowHeight*3]; wallWidth = 512; apertureR = new double[wallWidth]; apertureI = new double[wallWidth]; System.out.print(windowWidth + " " + windowHeight + "\n"); apertureChanged(); } void setResolution(int x) { resBar.setValue(x); setResolution(); } public void mouseDragged(MouseEvent e) { dragging = true; //edit(e); cv.repaint(pause); } 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; } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; 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(); if (e.getItemSelectable() == symmCheck) { recompute = true; } if (e.getItemSelectable() == triChromaticCheck || e.getItemSelectable() == intensityCheck) { setResolution(); recompute = true; if (intensityCheck.getState()) { stoppedCheck.disable(); speedBar.disable(); triChromaticCheck.enable(); } else { stoppedCheck.enable(); speedBar.enable(); triChromaticCheck.disable(); } setResetTimeButton(); return; } if (e.getItemSelectable() == setupChooser) doSetup(); } void clearAperture() { int i; for (i = 0; i != wallWidth; i++) apertureR[i] = apertureI[i] = 0; } void doSetup() { t = 1e8; int i; for (i = 0; i != auxBarCount; i++) auxBars[i].setValue(10); freqBar.setValue(120); zoomBar.setValue(10); angleBar.setValue(90); setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex()); angleBar.enable(); setResetTimeButton(); setup.select(); apertureChanged(); for (i = 0; i < setup.getAuxBarCount(); i++) { auxLabels[i].show(); auxBars[i].show(); } for (; i < auxBarCount; i++) { auxLabels[i].hide(); auxBars[i].hide(); } validate(); } abstract class Setup { abstract String getName(); abstract void select(); abstract void doAperture(); abstract Setup createNext(); String getInfo(int x) { return null; } int getAuxBarCount() { return 2; } }; class SingleSlitSetup extends Setup { String getName() { return "Single Slit"; } void select() { auxLabels[0].setText("Slit Width"); brightnessBar.setValue(440); } int w; void doAperture() { int x; w = auxBars[0].getValue(); //System.out.println("width = " + w); for (x = 0; x != w; x++) apertureR[wallWidth/2 - w/2 + x] = 1; } int getAuxBarCount() { return 1; } String getInfo(int x) { if (x == 1) return "Aperture width = " + getLength(w); return null; } Setup createNext() { return new DoubleSlitSetup(); } } class DoubleSlitSetup extends Setup { String getName() { return "Double Slit"; } void select() { auxLabels[0].setText("Slit Width"); auxLabels[1].setText("Separation"); auxLabels[2].setText("Balance"); auxLabels[3].setText("Phase Difference"); auxBars[2].setValue(50); auxBars[3].setValue(1); brightnessBar.setValue(380); } void doAperture() { int x; int w = auxBars[0].getValue(); int s = auxBars[1].getValue(); double bal2 = auxBars[2].getValue() / 100.; double bal1 = 1-bal2; if (bal1 > bal2) { bal2 /= bal1; bal1 = 1; } else { bal1 /= bal2; bal2 = 1; } double ph = (auxBars[3].getValue()-1) * pi / 50.; double a2r = bal2 * Math.cos(ph); double a2i = bal2 * Math.sin(ph); for (x = 0; x != w; x++) { apertureR[wallWidth/2 - s - x] = bal1; apertureR[wallWidth/2 + s + x] = a2r; apertureI[wallWidth/2 + s + x] = a2i; } } String getInfo(int x) { if (x == 0) return "Slit width = " + getLength(auxBars[0].getValue()); return "Separation = " + getLength(2*auxBars[1].getValue()-1); } int getAuxBarCount() { return 4; } Setup createNext() { return new GratingSetup(); } } class GratingSetup extends Setup { String getName() { return "Multiple Slits"; } void select() { auxLabels[0].setText("Slit Count"); auxLabels[1].setText("Slit Width"); auxLabels[2].setText("Separation"); brightnessBar.setValue(345); } int getAuxBarCount() { return 3; } int w, s; void doAperture() { int x; w = auxBars[1].getValue(); s = auxBars[2].getValue()+3; if (w > s-1) w = s-1; int i; int n = auxBars[0].getValue()/5+1; int sub = 0; while (true) { sub = (s*(n-1)+w)/2; if (-sub+s*(n-1)+w < wallWidth/2) break; n--; } for (x = 0; x != w; x++) for (i = 0; i != n; i++) apertureR[wallWidth/2-sub+s*i+x] = 1; } String getInfo(int x) { if (x == 0) return "Slit width = " + getLength(w); return "Separation = " + getLength(s-w); } Setup createNext() { return new ObstacleSetup(); } } class ObstacleSetup extends Setup { String getName() { return "Obstacle"; } void select() { auxLabels[0].setText("Width"); // waves with an incidence angle don't work for some reason // with obstacles, so disable them angleBar.disable(); angleBar.setValue(90); // don't feel like getting Reset Time to work with Obstacle resetTimeButton.disable(); brightnessBar.setValue(310); } int getAuxBarCount() { return 1; } int w; void doAperture() { int x; w = (int) (auxBars[0].getValue()*1.5); for (x = 0; x != w; x++) apertureR[wallWidth/2-w/2+x] = 1; } String getInfo(int x) { if (x == 1) return "Width = " + getLength(w); return null; } Setup createNext() { return new ZonePlateEvenSetup(); } } class ZonePlateEvenSetup extends Setup { String getName() { return "Zone Plate (Even)"; } int evenOdd = 0; boolean phase = false; boolean blazed = false; void select() { auxLabels[0].setText("Intended Frequency"); auxBars[0].setValue(freqBar.getValue() * 100 / 236); auxLabels[1].setText("Focal Length"); auxBars[1].setValue(20); auxLabels[2].setText("Plate Width"); auxBars[2].setValue(100); brightnessBar.setValue(111); } int getAuxBarCount() { return 3; } void doAperture() { int i; // freqBar ranges from 0 to 4.72, so we match that here double m = auxBars[0].getValue() * .0472; double halfwave = pi/m; int pw = auxBars[2].getValue() * 3; int dy = auxBars[1].getValue()*5; int cx = wallWidth/2; for (i = 1; i != wallWidth; i++) { int dx = cx-i; if (dx < -pw || dx > pw) continue; double dist = Math.sqrt(dx*dx+dy*dy); dist = (dist-dy); if (blazed) { double ph = dist/halfwave * pi; apertureR[i] = Math.cos(ph); apertureI[i] = Math.sin(ph); } else { int zone = (int) (dist / halfwave); apertureR[i] = ((zone & 1) == evenOdd) ? 1 : (phase) ? -1 : 0; } } } Setup createNext() { return new ZonePlateOddSetup(); } } class ZonePlateOddSetup extends ZonePlateEvenSetup { ZonePlateOddSetup() { evenOdd = 1; } String getName() { return "Zone Plate (Odd)"; } Setup createNext() { return new ZonePlatePhaseSetup(); } } class ZonePlatePhaseSetup extends ZonePlateOddSetup { ZonePlatePhaseSetup() { phase = true; } String getName() { return "Phase-Reversal Zone Plate"; } Setup createNext() { return new ZonePlateBlazedSetup(); } } class ZonePlateBlazedSetup extends ZonePlateOddSetup { ZonePlateBlazedSetup() { blazed = true; } String getName() { return "Blazed Zone Plate"; } Setup createNext() { return new Hologram1Setup(); } } class Hologram1Setup extends Setup { String getName() { return "Absorption Hologram"; } void select() { auxLabels[0].setText("Intended Frequency"); auxBars[0].setValue(freqBar.getValue() * 100 / 236); auxLabels[1].setText("X 1"); auxLabels[2].setText("Y 1"); auxLabels[3].setText("X 2"); auxLabels[4].setText("Y 2"); auxBars[1].setValue(40); auxBars[2].setValue(15); auxBars[3].setValue(70); auxBars[4].setValue(40); brightnessBar.setValue(285); zoomBar.setValue(15); } int getAuxBarCount() { return 5; } void doAperture() { int i; // freqBar ranges from 0 to 4.72, so we match that here double m = auxBars[0].getValue() * .0472; double halfwave = pi/m; int px1 = auxBars[1].getValue() * 2 - 100; int py1 = auxBars[2].getValue() * 3+5; int px2 = auxBars[3].getValue() * 2 - 100; int py2 = auxBars[4].getValue() * 3+5; int cx = wallWidth/2; int pw = 300; double maxf = 0; double aradd = .75; for (i = 0; i != wallWidth; i++) { if (i-cx < -pw || i-cx > pw) continue; int dx = px1-(i-cx); double dist = Math.sqrt(dx*dx+py1*py1); computeBessel(dist*m); double ar = bessj0, ai = bessy0; dx = px2-(i-cx); dist = Math.sqrt(dx*dx+py2*py2); computeBessel(dist*m); ar += bessj0; ai += bessy0; ar += aradd; double q = apertureR[i] = Math.sqrt(ar*ar+ai*ai); if (q > maxf) maxf = q; } maxf = Math.sqrt(maxf); for (i = 0; i != wallWidth; i++) apertureR[i] /= maxf; } Setup createNext() { return new Hologram2Setup(); } } class Hologram2Setup extends Setup { String getName() { return "Phase Hologram"; } void select() { auxLabels[0].setText("Intended Frequency"); auxBars[0].setValue(freqBar.getValue() * 100 / 236); auxLabels[1].setText("X 1"); auxLabels[2].setText("Y 1"); auxLabels[3].setText("X 2"); auxLabels[4].setText("Y 2"); auxBars[1].setValue(40); auxBars[2].setValue(15); auxBars[3].setValue(70); auxBars[4].setValue(40); brightnessBar.setValue(150); zoomBar.setValue(15); } int getAuxBarCount() { return 5; } void doAperture() { int i; // freqBar ranges from 0 to 4.72, so we match that here double m = auxBars[0].getValue() * .0472; double halfwave = pi/m; int px1 = auxBars[1].getValue() * 2 - 100; int py1 = auxBars[2].getValue() * 3+5; int px2 = auxBars[3].getValue() * 2 - 100; int py2 = auxBars[4].getValue() * 3+5; int cx = wallWidth/2; int pw = 300; double maxf = 0; for (i = 0; i != wallWidth; i++) { if (i-cx < -pw || i-cx > pw) continue; int dx = px1-(i-cx); double dist = Math.sqrt(dx*dx+py1*py1); computeBessel(dist*m); apertureR[i] = bessj0; apertureI[i] = bessy0; dx = px2-(i-cx); dist = Math.sqrt(dx*dx+py2*py2); computeBessel(dist*m); apertureR[i] += bessj0; apertureI[i] += bessy0; double q = apertureR[i]*apertureR[i] + apertureI[i]*apertureI[i]; if (q > maxf) maxf = q; } maxf = Math.sqrt(maxf); for (i = 0; i != wallWidth; i++) { apertureR[i] /= maxf; apertureI[i] /= maxf; } } Setup createNext() { return null; } } double bessj0, bessy0; void computeBessel(double x) { double ax = x,z; double xx,y,ans,ans1,ans2; if (x < 8.0) { y=x*x; ans1=57568490574.0+y*(-13362590354.0+y*(651619640.7 +y*(-11214424.18+y*(77392.33017+y*(-184.9052456))))); ans2=57568490411.0+y*(1029532985.0+y*(9494680.718 +y*(59272.64853+y*(267.8532712+y*1.0)))); bessj0 = ans=ans1/ans2; ans1 = -2957821389.0+y*(7062834065.0+y*(-512359803.6 +y*(10879881.29+y*(-86327.92757+y*228.4622733)))); ans2=40076544269.0+y*(745249964.8+y*(7189466.438 +y*(47447.26470+y*(226.1030244+y*1.0)))); bessy0=(ans1/ans2)+0.636619772*bessj0*Math.log(x); } else { z=8.0/ax; y=z*z; xx=ax-0.785398164; if (x > 83) { ans1 = 1; ans2 = -.0156249; } else { ans1=1.0+y*(-0.1098628627e-2+y*(0.2734510407e-4 +y*(-0.2073370639e-5+y*0.2093887211e-6))); ans2 = -0.1562499995e-1+y*(0.1430488765e-3 +y*(-0.6911147651e-5+y*(0.7621095161e-6 -y*0.934935152e-7))); } double sax = Math.sqrt(0.636619772/ax); double cosxx = Math.cos(xx); double sinxx = Math.sin(xx); ans2 *= z; bessj0 = sax * (cosxx*ans1-sinxx*ans2); bessy0 = sax * (sinxx*ans1+cosxx*ans2); } } } class FFT { float wtabf[]; float wtabi[]; int size; FFT(int sz) { size = sz; if ((size & (size-1)) != 0) System.out.println("size must be power of two!"); calcWTable(); } void calcWTable() { // calculate table of powers of w wtabf = new float[size]; wtabi = new float[size]; int i; for (i = 0; i != size; i += 2) { double pi = 3.1415926535; double th = pi*i/size; wtabf[i ] = (float)Math.cos(th); wtabf[i+1] = (float)Math.sin(th); wtabi[i ] = wtabf[i]; wtabi[i+1] = -wtabf[i+1]; } } void transform(float data[], boolean inv) { int i; int j = 0; int size2 = size*2; if ((size & (size-1)) != 0) System.out.println("size must be power of two!"); // bit-reversal float q; int bit; for (i = 0; i != size2; i += 2) { if (i > j) { q = data[i]; data[i] = data[j]; data[j] = q; q = data[i+1]; data[i+1] = data[j+1]; data[j+1] = q; } // increment j by one, from the left side (bit-reversed) bit = size; while ((bit & j) != 0) { j &= ~bit; bit >>= 1; } j |= bit; } // amount to skip through w table int tabskip = size << 1; float wtab[] = (inv) ? wtabi : wtabf; int skip1, skip2, ix, j2; float wr, wi, d1r, d1i, d2r, d2i, d2wr, d2wi; // unroll the first iteration of the main loop for (i = 0; i != size2; i += 4) { d1r = data[i]; d1i = data[i+1]; d2r = data[i+2]; d2i = data[i+3]; data[i ] = d1r+d2r; data[i+1] = d1i+d2i; data[i+2] = d1r-d2r; data[i+3] = d1i-d2i; } tabskip >>= 1; // unroll the second iteration of the main loop int imult = (inv) ? -1 : 1; for (i = 0; i != size2; i += 8) { d1r = data[i]; d1i = data[i+1]; d2r = data[i+4]; d2i = data[i+5]; data[i ] = d1r+d2r; data[i+1] = d1i+d2i; data[i+4] = d1r-d2r; data[i+5] = d1i-d2i; d1r = data[i+2]; d1i = data[i+3]; d2r = data[i+6]*imult; d2i = data[i+7]*imult; data[i+2] = d1r-d2i; data[i+3] = d1i+d2r; data[i+6] = d1r+d2i; data[i+7] = d1i-d2r; } tabskip >>= 1; for (skip1 = 16; skip1 <= size2; skip1 <<= 1) { // skip2 = length of subarrays we are combining // skip1 = length of subarray after combination skip2 = skip1 >> 1; tabskip >>= 1; for (i = 0; i != 1000; i++); // for each subarray for (i = 0; i < size2; i += skip1) { ix = 0; // for each pair of complex numbers (one in each subarray) for (j = i; j != i+skip2; j += 2, ix += tabskip) { wr = wtab[ix]; wi = wtab[ix+1]; d1r = data[j]; d1i = data[j+1]; j2 = j+skip2; d2r = data[j2]; d2i = data[j2+1]; d2wr = d2r*wr - d2i*wi; d2wi = d2r*wi + d2i*wr; data[j] = d1r+d2wr; data[j+1] = d1i+d2wi; data[j2 ] = d1r-d2wr; data[j2+1] = d1i-d2wi; } } } } }