// Gas.java (C) 2001 by Paul Falstad, www.falstad.com import java.io.InputStream; import java.awt.*; import java.awt.image.ImageProducer; import java.applet.Applet; import java.applet.AudioClip; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.File; import java.net.URL; import java.util.Random; import java.awt.image.MemoryImageSource; import java.lang.Math; import java.awt.event.*; import java.text.DecimalFormat; import java.text.NumberFormat; class GasCanvas extends Canvas { Gas pg; GasCanvas(Gas p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateGas(g); } public void paint(Graphics g) { pg.updateGas(g); } }; class HistogramCanvas extends Canvas { Gas pg; HistogramCanvas(Gas p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(125,50); } public void update(Graphics g) { pg.updateHistogram(g); } }; class GasLayout implements LayoutManager { public GasLayout() {} public void addLayoutComponent(String name, Component c) {} public void removeLayoutComponent(Component c) {} public Dimension preferredLayoutSize(Container target) { return new Dimension(500, 500); } public Dimension minimumLayoutSize(Container target) { return new Dimension(100,100); } public void layoutContainer(Container target) { int cw = target.size().width * 2/3; target.getComponent(0).move(0, 0); target.getComponent(0).resize(cw, target.size().height-100); target.getComponent(1).move(0, target.size().height-100); target.getComponent(1).resize(cw, 100); int i; int h = 0; for (i = 2; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (m instanceof Scrollbar) d.width = target.size().width - cw; int c = 0; if (m instanceof Label) { h += d.height/3; c = (target.size().width-cw-d.width)/2; } m.move(cw+c, h); m.resize(d.width, d.height); h += d.height; } } } }; public class Gas extends Applet implements ComponentListener, ActionListener, AdjustmentListener, ItemListener { Thread engine = null; int molCount; Dimension winSize; Image dbimage; public static final int defaultPause = 10; int heaterSize; int pause; Random random; public String getAppletInfo() { return "Gas Molecules by Paul Falstad"; } public static int gridEltWidth = 10; // was 60 public static int gridEltHeight = 10; int gridWidth; int gridHeight; Molecule mols[]; Molecule grid[][]; Molecule bigmol; Button resetButton; Button expandButton; Checkbox stoppedCheck; Checkbox heaterCheck; Checkbox energyCheck; Scrollbar heaterTempBar; Scrollbar gravityBar; Scrollbar speedBar; Scrollbar molCountBar; Scrollbar colorBar; Choice setupChooser; Vector setupList; Setup setup; double gravity; double colorMult; int upperBound; double topWallPos; double topWallVel; int areaHeight; double heatstate; double heaterTemp; double heaterMove; double wallF, wallFMeasure; Color heaterColor; Color colors[]; int heaterTop; int heaterLeft; int heaterRight; final int maxMolCount = 1000; NumberFormat showFormat; int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } GasCanvas cv; HistogramCanvas hist_cv; public void init() { setupList = new Vector(); Setup s = new Setup1Random(); while (s != null) { setupList.addElement(s); s = s.createNext(); } showFormat = DecimalFormat.getInstance(); showFormat.setMaximumFractionDigits(3); int ci = 0; heatstate = 0; colors = new Color[16]; colors[ci++] = new Color(46,120,255); colors[ci++] = new Color(79,140,254); colors[ci++] = new Color(113,142,253); colors[ci++] = new Color(147,145,252); colors[ci++] = new Color(181,105,178); colors[ci++] = new Color(215,64,103); colors[ci++] = new Color(249,23,28); colors[ci++] = new Color(250,101,44); colors[ci++] = new Color(251,139,33); colors[ci++] = new Color(252,178,22); colors[ci++] = new Color(253,216,11); colors[ci++] = new Color(255,255,0); colors[ci++] = new Color(255,255,63); colors[ci++] = new Color(255,255,127); colors[ci++] = new Color(255,255,191); colors[ci++] = new Color(255,255,255); gravity = 0; //setLayout(new GridLayout(3, 1, 10, 10)); setLayout(new GasLayout()); //setLayout(new GridBagLayout()); cv = new GasCanvas(this); cv.addComponentListener(this); add(cv); hist_cv = new HistogramCanvas(this); hist_cv.addComponentListener(this); add(hist_cv); setupChooser = new Choice(); int i; for (i = 0; i != setupList.size(); i++) setupChooser.add("Setup: " + ((Setup) setupList.elementAt(i)).getName()); setupChooser.addItemListener(this); add(setupChooser); stoppedCheck = new Checkbox("Stopped"); stoppedCheck.addItemListener(this); add(stoppedCheck); heaterCheck = new Checkbox("Heater"); heaterCheck.addItemListener(this); add(heaterCheck); energyCheck = new Checkbox("Energy Distribution"); energyCheck.addItemListener(this); add(energyCheck); add(resetButton = new Button("Reset")); resetButton.addActionListener(this); add(expandButton = new Button("Expand")); expandButton.addActionListener(this); add(new Label("Simulation Speed", Label.CENTER)); add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 0, 100)); speedBar.addAdjustmentListener(this); add(new Label("Molecule Count", Label.CENTER)); add(molCountBar = new Scrollbar(Scrollbar.HORIZONTAL, 500, 1, 1, maxMolCount)); molCountBar.addAdjustmentListener(this); add(new Label("Color Scale", Label.CENTER)); add(colorBar = new Scrollbar(Scrollbar.HORIZONTAL, 150, 1, 1, 300)); colorBar.addAdjustmentListener(this); add(new Label("Heater Temperature", Label.CENTER)); add(heaterTempBar = new Scrollbar(Scrollbar.HORIZONTAL, 35, 1, 0, 100)); heaterTempBar.addAdjustmentListener(this); add(new Label("Gravity", Label.CENTER)); add(gravityBar = new Scrollbar(Scrollbar.HORIZONTAL, 20, 1, 0, 100)); gravityBar.addAdjustmentListener(this); cv.setBackground(Color.black); cv.setForeground(heaterColor = Color.lightGray); hist_cv.setBackground(Color.black); hist_cv.setForeground(Color.lightGray); random = new Random(); pause = defaultPause; adjustColors(); adjustHeaterTemp(); enableItems(); try { String param = getParameter("PAUSE"); if (param != null) pause = Integer.parseInt(param); } catch (Exception e) { } reinit(true); repaint(); } static final int SPEED_RANDOM = 0; static final int SPEED_EQUAL = 1; static final int SPEED_EXTREME = 2; void reinit(boolean newsetup) { if (cv.getSize().width == 0 || gravityBar == null || setupChooser == null) return; System.out.println("winsize " + winSize); bigmol = null; setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex()); gravityBar.setValue(0); if (newsetup) { speedBar.setValue(20); molCountBar.setValue(500); colorBar.setValue(160); setup.select(); } setup.reinit(); adjustColors(); } void expand() { topWallPos -= 50; if (topWallPos < 0) topWallPos = 0; enableItems(); } void initMolecules(int speed) { Dimension d = winSize = cv.getSize(); molCount = molCountBar.getValue(); upperBound = (int) (winSize.height*(1-setup.getVolume())-1); topWallPos = upperBound; areaHeight = winSize.height-upperBound; mols = new Molecule[maxMolCount]; dbimage = createImage(d.width, d.height); gridWidth = d.width /gridEltWidth+1; gridHeight = d.height/gridEltHeight+1; grid = new Molecule[gridWidth][gridHeight]; int i, j; for (i = 0; i != gridWidth; i++) for (j = 0; j != gridHeight; j++) { grid[i][j] = new Molecule(); grid[i][j].listHead = true; } for (i = 0; i != maxMolCount; i++) { Molecule m = new Molecule(); mols[i] = m; m.x = getrand(winSize.width*10)*.1; m.y = getrand(areaHeight*10)*.1+upperBound; m.dx = (getrand(100)/99.0-.5); m.dy = java.lang.Math.sqrt(1-m.dx*m.dx); if (getrand(10) > 4) m.dy = -m.dy; if (speed == SPEED_EXTREME) { double q = ((i & 2) > 0) ? 3 : .1; m.dx *= q; m.dy *= q; } if (speed == SPEED_RANDOM) { double q = getrand(101)/50.; m.dx *= q; m.dy *= q; } if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) System.out.println("nan1"); setColor(m); if (i < molCount) gridAdd(m); } heaterTop = winSize.height-5; heaterSize = winSize.width/4; heaterLeft = (winSize.width-heaterSize*3)/2; heaterRight = (winSize.width+heaterSize*3)/2; enableItems(); cv.repaint(); hist_cv.repaint(); } void setMoleculeTypes(double mult, int typeCount) { int i, j; for (i = 0; i != maxMolCount; i++) { Molecule m = mols[i]; m.r *= mult; m.mass *= mult*mult; if (typeCount > 1) { int n = (i % typeCount); m.type = n; if (n == 2) { m.r *= 3; m.mass *= 9; // was 27 } else if (n == 1) { m.r *= 2; m.mass *= 4; // was 8 } } setColor(m); } } long secTime, lastTime; double t, lastSecT, totalKE, temp, totalV; public void updateGas(Graphics realg) { if (winSize == null) return; Graphics g = dbimage.getGraphics(); g.setColor(cv.getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); int j; double dt = speedBar.getValue()/100.; if (!stoppedCheck.getState()) { long sysTime = System.currentTimeMillis(); if (lastTime != 0) { int inc = (int) (sysTime-lastTime); dt *= inc/8.; } if (sysTime-secTime >= 1000) { if (t > 0) wallF /= t-lastSecT; wallFMeasure = wallF; wallF = 0; secTime = sysTime; lastSecT = t; } lastTime = sysTime; } else lastTime = 0; for (short i = 0; i != molCount; i++) { Molecule m = mols[i]; boolean bounce = false; int ix = (int) m.x; int iy = (int) m.y; j = (stoppedCheck.getState()) ? 5 : 0; for (; j < 5; j++) { m.dy += gravity*dt; m.x += m.dx*dt; m.y += m.dy*dt; if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) System.out.println("nan2"); int r = m.r; if (m.x < r || m.x >= winSize.width-r) { wallF += Math.abs(m.dx)*m.mass; m.dx = -m.dx; if (m.x < m.r) m.x = m.r; if (m.x >= winSize.width-r) m.x = winSize.width-r-1; setColor(m); bounce = true; } if (m.y < upperBound+r || m.y >= winSize.height-r) { wallF += Math.abs(m.dy)*m.mass; if (m.y < upperBound+r) m.y = upperBound+r; if (m.y >= winSize.height-r) m.y = winSize.height-r-1; if (m.y == upperBound+r && m.dy < 0 && false) { double wallMass = 1000; double totmass = m.mass + wallMass; double comdy = (m.mass*m.dy+wallMass*topWallVel)/totmass; double chg = (m.dy-comdy); //System.out.print("< " + m.dy + " " + topWallVel + "\n"); m.dy -= 2*chg; topWallVel += 2*chg*m.mass/wallMass; //System.out.print("> " + m.dy + " " + topWallVel + "\n"); } else m.dy = -m.dy; setColor(m); bounce = true; } int nix = (int) m.x; int niy = (int) m.y; if (!bounce && nix >= heaterLeft && nix <= heaterRight && niy >= heaterTop-1 && heaterCheck.getState()) { double v = java.lang.Math.sqrt(m.dx*m.dx+m.dy*m.dy); double oldy = m.dy; // calculate velocity of particle if it were at heater temp double mxv = Math.sqrt(3*heaterTemp/m.mass); // mix this velocity with particle's velocity randomly double mix = getrand(100)/99.0; mix = 0; double newv = v*mix + mxv*(1-mix); // randomize direction m.dx = getrand(101)/50.0-1; m.dy = -Math.sqrt(1-m.dx*m.dx)*newv; m.dx *= newv; if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) System.out.println("nan3"); wallF += (oldy-m.dy)*m.mass; setColor(m); bounce = true; m.y = heaterTop-2; niy = (int) m.y; } Molecule m2 = (bounce) ? null : checkCollision(m); if (m2 != null) { // handle a collision // first, find exact moment they collided by solving // a quadratic equation: // [(x1-x2)+t(dx1-dx2)]^2 + [(y1-y2)+...]^2 = mindist^2 // (first deal with degenerate case where molecules are on top // of each other) if (m.dx == m2.dx && m.dy == m2.dy) { if (m.dx == 0 && m.dy == 0) continue; m.dx += .001; } double sdx = m.dx-m2.dx; double sx = m.x -m2.x; double sdy = m.dy-m2.dy; double sy = m.y -m2.y; int mindist = m.r + m2.r; double a = sdx*sdx + sdy*sdy; double b = 2*(sx*sdx+sy*sdy); double c = sx*sx + sy*sy - mindist*mindist; double t = (-b-java.lang.Math.sqrt(b*b-4*a*c))/a; double t2 = (-b+java.lang.Math.sqrt(b*b-4*a*c))/a; if (java.lang.Math.abs(t) > java.lang.Math.abs(t2)) t = t2; if (Double.isNaN(t)) System.out.print("nan " + m.dx + " " + m.dy + " " + m2.dx + " " + m2.dy + " " + a + " " + b + " " +c + " " + t + " " + t2 + "\n"); // backtrack m to where they collided. // (t is typically negative.) m.x += t*m.dx; m.y += t*m.dy; // ok, so now they are just touching. find vector // separating their centers and normalize it. sx = m.x-m2.x; sy = m.y-m2.y; double sxynorm = java.lang.Math.sqrt(sx*sx+sy*sy); double sxn = sx/sxynorm; double syn = sy/sxynorm; // find velocity of center of mass double totmass = m.mass + m2.mass; double comdx = (m.mass*m.dx+m2.mass*m2.dx)/totmass; double comdy = (m.mass*m.dy+m2.mass*m2.dy)/totmass; //System.out.print("x " + (m.dx-comdx) + " " + (m2.dx-comdx) + "\n"); //System.out.print(">y " + (m.dy-comdy) + " " + (m2.dy-comdy) + "\n"); // send m on its way if (t < 0) { m.x -= t*m.dx; m.y -= t*m.dy; } if (m.x < r) m.x = r; if (m.x >= winSize.width-r) m.x = winSize.width-r; if (m.y < upperBound+r) m.y = upperBound+r; if (m.y >= winSize.height-r) m.y = winSize.height-r-1; if (Double.isNaN(m.dx) || Double.isNaN(m.dy)) System.out.println("nan4"); if (Double.isNaN(m2.dx) || Double.isNaN(m2.dy)) System.out.println("nan5"); setColor(m); setColor(m2); } // this line may not be reached } g.setColor(m.color); g.fillOval((int)m.x-m.r, (int)m.y-m.r, m.r*2, m.r*2); // XXX //g.fillRect((int)m.x-m.r, (int)m.y-m.r, m.r*2, m.r*2); gridRemove(m); gridAdd(m); } t += dt*5; totalKE = 0; totalV = 0; for (short i = 0; i != molCount; i++) { Molecule m = mols[i]; totalKE += m.ke; totalV += m.r*m.r; } totalV *= Math.PI; temp = totalKE/molCount; // T = K.E./k in 2-d //topWallVel += volumeBar.getValue()*.01; if (topWallVel > .5) topWallVel = .5; topWallPos += topWallVel*5; if (topWallPos < 0) { topWallPos = 0; if (topWallVel < 0) topWallVel = 0; } if (topWallPos > (winSize.height*4/5)) { topWallPos = (winSize.height*4/5); if (topWallVel > 0) topWallVel = 0; } upperBound = (int) topWallPos; int heatstateint = ((int) heatstate); if (heaterCheck.getState()) { for (j = 0; j != heaterSize; j++, heatstateint++) { int x = heaterLeft + j*3; int y = heatstateint & 3; if ((heatstateint & 4) == 4) y = 4-y; g.setColor(heaterColor); g.fillRect(x, heaterTop+y, 2, 2); } } g.setColor(Color.lightGray); g.drawRect(0, upperBound, winSize.width-1, winSize.height-1-upperBound); g.fillRect(winSize.width/2 - 20, 0, 40, upperBound); realg.drawImage(dbimage, 0, 0, this); if (!stoppedCheck.getState()) { heatstate += heaterMove; cv.repaint(pause); hist_cv.repaint(pause); } } void gridAdd(Molecule m) { int gx = (int) (m.x/gridEltWidth); int gy = (int) (m.y/gridEltHeight); Molecule g = grid[gx][gy]; m.next = g; m.prev = g.prev; g.prev = m; m.prev.next = m; } void gridRemove(Molecule m) { m.next.prev = m.prev; m.prev.next = m.next; } Molecule checkCollision(Molecule m) { if (bigmol != null) { Molecule q = checkCollision(m, grid[(int) (bigmol.x/gridEltWidth)] [(int) (bigmol.y/gridEltHeight)]); if (q != null) return q; } int gx = (int) (m.x/gridEltWidth); int gy = (int) (m.y/gridEltHeight); int i, j; // check grid squares around the molecule for collisions for (i = -1; i <= 1; i++) for (j = -1; j <= 1; j++) { if (gx+i < 0 || gy+j < 0 || gx+i >= gridWidth || gy+j >= gridHeight) continue; Molecule n = checkCollision(m, grid[gx+i][gy+j]); if (n != null) return n; } return null; } Molecule checkCollision(Molecule m, Molecule list) { Molecule l = list.next; int count = 0; for (; !l.listHead; l = l.next) { if (m == l) continue; count++; int mindist = m.r+l.r; double dx = m.x-l.x; double dy = m.y-l.y; //System.out.print("ck " + dx + " " + dy + "\n"); if (dx > mindist || dy > mindist || dx < -mindist || dy < -mindist) continue; double dist = java.lang.Math.sqrt(dx*dx+dy*dy); if (dist > mindist) continue; //System.out.print("COLL " + m + " " + l + "\n"); return l; } return null; } void setColor(Molecule m) { m.vel = Math.sqrt(m.dx*m.dx+m.dy*m.dy); m.ke = .5*m.mass*m.vel*m.vel; int col = (int) (m.ke*colorMult); int maxcol = colors.length-1; if (col > maxcol) col = maxcol; m.color = colors[col]; } int graphmax = 20; public void updateHistogram(Graphics realg) { if (winSize == null) return; Dimension d = hist_cv.size(); Graphics g = dbimage.getGraphics(); g.setColor(hist_cv.getBackground()); g.fillRect(0, 0, d.width, d.height); g.setColor(hist_cv.getForeground()); int i; int slots = d.width/2; int graph[] = new int[slots]; int gi; int mg = 0; int gicount = setup.getHistogramCount(); boolean energy = energyCheck.getState(); for (gi = 0; gi != gicount; gi++) { int ymin = d.height*gi/gicount; int ymax = d.height*(gi+1)/gicount-1; int yheight = ymax-ymin; double maxke = energy ? 70 : 15; for (i = 0; i != slots; i++) graph[i] = 0; double mass = 1; int mcount = 0; for (i = 0; i != molCount; i++) { Molecule m = mols[i]; if (m.type != gi) continue; mcount++; mass = m.mass; double value = (energy ? m.ke : m.vel); int r = (int) (value*slots/maxke); if (r >= slots) continue; graph[r]++; } maxke += .5; int maxcol = colors.length-1; for (i = 0; i != slots; i++) { if (graph[i] == 0) continue; if (graph[i] > mg) mg = graph[i]; int y = ymax-(graph[i] * yheight / graphmax); if (y < ymin) y = ymin; double value = i*maxke/slots; if (!energy) value *= mass*value; int col = (int) (value*colorMult); if (col > maxcol) col = maxcol; g.setColor(colors[col]); g.fillRect(i*2, y, 2, ymax-y+1); } int ox = -1, oy = -1; g.setColor(Color.lightGray); // not sure if maxwell energy distribution is right, // comment it out for now if (!energyCheck.getState()) { for (i = 0; i != slots; i++) { double v = i*maxke/slots; double dv = maxke/slots; double distdv = .5*mcount*(maxwellDist(v, mass)+maxwellDist(v+dv, mass))*dv; int v0 = (int) distdv; int y = (ymax-(v0 * yheight / graphmax)); if (y < ymin) y = ymin; int x = i*2; if (ox != -1 && !(y == oy && oy == ymax)) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } } if (mg > graphmax) graphmax = mg; if (mg < graphmax/2 && graphmax > 1) graphmax /= 2; FontMetrics fm = g.getFontMetrics(); g.setColor(Color.white); int x = winSize.width*2/3; double vadj = 4e-4; double v = (winSize.width-2)*(winSize.height-upperBound-2)*vadj; g.drawString("V = " + showFormat.format(v), x, fm.getAscent()); g.drawString("n = " + molCount, x, fm.getAscent()+fm.getHeight()); double a = 2*(winSize.width+(winSize.height-upperBound)-4); double p = 1e4 * wallFMeasure/(3*a); g.drawString("P = " + showFormat.format(p), x, fm.getAscent()+2*fm.getHeight()); g.drawString("kT = " + showFormat.format(temp), x, fm.getAscent()+3*fm.getHeight()); g.drawString("PV/nkT = " + showFormat.format(p*v/(molCount*temp)), x, fm.getAscent()+4*fm.getHeight()); g.drawString("P(V-nb)/nkT = " + showFormat.format(p*(v-totalV*vadj)/(molCount*temp)), x, fm.getAscent()+5*fm.getHeight()); realg.drawImage(dbimage, 0, 0, this); } // 2-D Maxwell distribution of molecular speeds double maxwellDist(double v, double mass) { if (energyCheck.getState()) return Math.exp(-v/temp)/temp; return (mass/temp)*v*Math.exp(-mass*v*v/(2*temp)); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e){} public void componentResized(ComponentEvent e) { reinit(false); cv.repaint(100); hist_cv.repaint(100); } public void actionPerformed(ActionEvent e) { System.out.println(e); if (e.getSource() == resetButton) { reinit(false); cv.repaint(); } if (e.getSource() == expandButton) { expand(); cv.repaint(); } } public void adjustmentValueChanged(AdjustmentEvent e) { System.out.println(((Scrollbar) e.getSource()).getValue()); /*if (e.getSource() == volumeBar) { upperBound = winSize.height * (100-volumeBar.getValue()) / 100; areaHeight = winSize.height - upperBound; }*/ // XXXXXX if (e.getSource() == gravityBar) gravity = gravityBar.getValue() * (.001/20); if (e.getSource() == heaterTempBar) adjustHeaterTemp(); if (e.getSource() == molCountBar) adjustMolCount(); if (e.getSource() == colorBar) adjustColors(); } void adjustHeaterTemp() { heaterTemp = (heaterTempBar.getValue() * .029111971)*30 + .01; heaterMove = (heaterTempBar.getValue() * .029111971) + .3; heaterMove /= 2; double value = 1.5*heaterTemp; int col = (int) (value*colorMult); int maxcol = colors.length-1; if (col > maxcol) col = maxcol; heaterColor = colors[col]; System.out.println("htemp = " + heaterTemp); } void adjustColors() { int i; double c = colorBar.getValue() / 150.; colorMult = Math.exp((c-1)*4)*.7; for (i = 0; i != molCount; i++) setColor(mols[i]); } void enableItems() { heaterTempBar.setEnabled(heaterCheck.getState()); expandButton.setEnabled(topWallPos > 0); } public void itemStateChanged(ItemEvent e) { enableItems(); if (e.getItemSelectable() == stoppedCheck) { cv.repaint(); return; } if (e.getItemSelectable() == setupChooser) reinit(true); } void adjustMolCount() { int oldcount = molCount; molCount = molCountBar.getValue(); if (molCount == oldcount) return; if (oldcount > molCount) { int i; for (i = molCount; i != oldcount; i++) gridRemove(mols[i]); } else { int i; for (i = oldcount; i != molCount; i++) gridAdd(mols[i]); } } class Molecule { public double x, y, dx, dy, mass, ke, vel; public int r, type; public Color color; public Molecule next, prev; public boolean listHead; Molecule() { r = 2; type = 0; mass = 2; next = prev = this; } }; abstract class Setup { abstract String getName(); void select() {} void reinit() {} void deselect() {} int getHistogramCount() { return 1; } double getVolume() { return 1; } abstract Setup createNext(); }; class Setup1Random extends Setup { String getName() { return "1 Gas, Random Speeds"; } void reinit() { initMolecules(SPEED_RANDOM); setMoleculeTypes(2, 1); } Setup createNext() { return new Setup1Equal(); } } class Setup1Equal extends Setup { String getName() { return "1 Gas, Equal Speeds"; } void select() { speedBar.setValue(3); } void reinit() { initMolecules(SPEED_EQUAL); setMoleculeTypes(2, 1); } Setup createNext() { return new Setup1Extreme(); } } class Setup1Extreme extends Setup { String getName() { return "1 Gas, Extreme Speeds"; } void select() { speedBar.setValue(3); } void reinit() { initMolecules(SPEED_EXTREME); setMoleculeTypes(2, 1); } Setup createNext() { return new Setup1Single(); } } class Setup1Single extends Setup { String getName() { return "1 Gas, One Moving Molecule"; } void select() { speedBar.setValue(10); } void reinit() { initMolecules(SPEED_EQUAL); int i, j; for (i = 1; i != maxMolCount; i++) mols[i].dx = mols[i].dy = 0; mols[0].dx *= Math.sqrt(molCount); mols[0].dy *= Math.sqrt(molCount); setMoleculeTypes(2, 1); } Setup createNext() { return new Setup1Small(); } } class Setup1Small extends Setup { String getName() { return "1 Gas, Small"; } void select() { colorBar.setValue(215); speedBar.setValue(36); } void reinit() { initMolecules(SPEED_RANDOM); setMoleculeTypes(1, 1); } Setup createNext() { return new Setup2Random(); } } class Setup2Random extends Setup { String getName() { return "2 Gases, Random Speeds"; } void reinit() { initMolecules(SPEED_RANDOM); setMoleculeTypes(1, 2); } int getHistogramCount() { return 2; } Setup createNext() { return new Setup2Equal(); } } class Setup2Equal extends Setup { String getName() { return "2 Gases, Equal Speeds"; } void select() { speedBar.setValue(3); } void reinit() { initMolecules(SPEED_EQUAL); setMoleculeTypes(1, 2); } int getHistogramCount() { return 2; } Setup createNext() { return new Setup3Random(); } } class Setup3Random extends Setup { String getName() { return "3 Gases, Random Speeds"; } void reinit() { initMolecules(SPEED_RANDOM); setMoleculeTypes(1, 3); } int getHistogramCount() { return 3; } Setup createNext() { return new Setup3Equal(); } } class Setup3Equal extends Setup { String getName() { return "3 Gases, Equal Speeds"; } void select() { speedBar.setValue(3); } void reinit() { initMolecules(SPEED_EQUAL); setMoleculeTypes(1, 3); } int getHistogramCount() { return 3; } Setup createNext() { return new SetupBrownian(); } } class SetupBrownian extends Setup { String getName() { return "Brownian Motion"; } void select() { speedBar.setValue(70); colorBar.setValue(210); } void reinit() { initMolecules(SPEED_RANDOM); bigmol = mols[0]; bigmol.r = 30; bigmol.mass = bigmol.r*bigmol.r/2; bigmol.dx = bigmol.dy = 0; } Setup createNext() { return new SetupExpansion(); } } class SetupExpansion extends Setup { String getName() { return "Free Expansion"; } void select() { molCountBar.setValue(250); speedBar.setValue(45); colorBar.setValue(210); } void reinit() { initMolecules(SPEED_RANDOM); setMoleculeTypes(1, 1); } double getVolume() { return .5; } Setup createNext() { return null; } } }