// 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.*; 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); int i; int h = 0; 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 = 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 { Thread engine = null; int molcount; Dimension winSize; Image dbimage; public static final int defaultPause = 10; public static final int wallSegments = 64; int heaterSize; int pause; Random random; public String getAppletInfo() { return "Gas Molecules by Paul Falstad"; } double mx[]; double my[]; double mdx[]; double mdy[]; double walltemps_left[]; double walltemps_right[]; double walltemps_top[]; double walltemps_bottom[]; short grid[][]; boolean stopped; Button stopButton; Button startButton; Button resetButton; Button equalButton; Button extremeButton; Button wallTempButton; Scrollbar volumeBar; Scrollbar tempBar; Scrollbar gravityBar; double gravity; int upperBound; int areaHeight; double heatstate; double temp; double tempMove; Color tempcolor; Color colors[]; int heaterTop; int heaterLeft; int heaterRight; int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } GasCanvas cv; HistogramCanvas hist_cv; public void init() { int ci = 0; heatstate = 0; colors = new Color[16]; colors[ci++] = new Color(46,60,255); colors[ci++] = new Color(79,88,254); colors[ci++] = new Color(113,116,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); stopped = false; gravity = .001; //setLayout(new GridLayout(3, 1, 10, 10)); setLayout(new GasLayout()); //setLayout(new GridBagLayout()); cv = new GasCanvas(this); cv.addComponentListener(this); add(cv); add(stopButton = new Button("Stop")); stopButton.addActionListener(this); add(startButton = new Button("Start")); startButton.addActionListener(this); add(resetButton = new Button("Reset")); resetButton.addActionListener(this); add(equalButton = new Button("Reset to Equal")); equalButton.addActionListener(this); add(extremeButton = new Button("Reset to Extreme")); extremeButton.addActionListener(this); add(wallTempButton = new Button("Set Wall Temp")); wallTempButton.addActionListener(this); add(new Label("Volume", Label.CENTER)); add(volumeBar = new Scrollbar(Scrollbar.HORIZONTAL, 100, 1, 10, 100)); volumeBar.addAdjustmentListener(this); add(new Label("Heater Temperature", Label.CENTER)); add(tempBar = new Scrollbar(Scrollbar.HORIZONTAL, 15, 1, 0, 100)); tempBar.addAdjustmentListener(this); add(new Label("Gravity", Label.CENTER)); add(gravityBar = new Scrollbar(Scrollbar.HORIZONTAL, 20, 1, 0, 100)); gravityBar.addAdjustmentListener(this); add(new Label("Velocity Histogram", Label.CENTER)); hist_cv = new HistogramCanvas(this); hist_cv.addComponentListener(this); add(hist_cv); cv.setBackground(Color.black); cv.setForeground(tempcolor = Color.lightGray); hist_cv.setBackground(Color.black); hist_cv.setForeground(tempcolor = Color.lightGray); random = new Random(); pause = defaultPause; adjustTemp(); try { String param = getParameter("PAUSE"); if (param != null) pause = Integer.parseInt(param); } catch (Exception e) { } reinit(0); repaint(); } void reinit(int type) { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) return; molcount = (winSize.width*winSize.height)/400; upperBound = winSize.height * (100-volumeBar.getValue()) / 100; areaHeight = winSize.height-upperBound; mx = new double[molcount]; my = new double[molcount]; mdx = new double[molcount]; mdy = new double[molcount]; walltemps_left = new double[wallSegments]; walltemps_right = new double[wallSegments]; walltemps_top = new double[wallSegments]; walltemps_bottom = new double[wallSegments]; dbimage = createImage(d.width, d.height); grid = new short[winSize.width][winSize.height]; int i, j; for (i = 0; i != winSize.width; i++) for (j = 0; j != winSize.height; j++) grid[i][j] = -1; for (i = 0; i != wallSegments; i++) walltemps_left[i] = walltemps_right[i] = walltemps_top[i] = walltemps_bottom[i] = .5; for (i = 0; i != molcount; i++) { mx[i] = getrand(winSize.width); my[i] = getrand(areaHeight)+upperBound; mdx[i] = (getrand(5)/4.0-.5); if (type != 0) { mdy[i] = java.lang.Math.sqrt(1-mdx[i]*mdx[i]); if (getrand(10) > 4) mdy[i] = -mdy[i]; if (type == 2) { double q = ((i & 1) > 0) ? 3 : .2; mdx[i] *= q; mdy[i] *= q; } } else { mdy[i] = (getrand(5)/4.0-.5); } grid[(int)mx[i]][(int)my[i]] = (short)i; } heaterTop = winSize.height-5; heaterSize = winSize.width/9; heaterLeft = (winSize.width-heaterSize*3)/2; heaterRight = (winSize.width+heaterSize*3)/2; } 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; for (short i = 0; i != molcount; i++) { double x = mx[i]; double y = my[i]; double dx = mdx[i]; double dy = mdy[i]; boolean bounce = false; int ix = (int) x; int iy = (int) y; j = (stopped) ? 5 : 0; for (; j < 5; j++) { dy += gravity; x += dx; y += dy; if (x < 0 || x >= winSize.width) { dx = -dx; if (x < 0) x = 0; if (x >= winSize.width) x = winSize.width-1; double v = java.lang.Math.sqrt(dx*dx+dy*dy); int wallidx = iy*wallSegments/winSize.height; double walltemp = (x == 0) ? walltemps_left[wallidx] : walltemps_right[wallidx]; double wto = walltemp; walltemp = walltemp * .8 + v*.2; //System.out.print("v " + v + " wto " + wto + // " walltemp " + walltemp + "\n"); double mix = getrand(10)/9.0; double newv = mix + walltemp/v*(1-mix); dx *= newv; dy *= newv; if (x == 0) walltemps_left[wallidx] = walltemp; else walltemps_right[wallidx] = walltemp; bounce = true; } if (y < upperBound || y >= winSize.height) { dy = -dy; if (y < upperBound) y = upperBound; if (y >= winSize.height) y = winSize.height-1; // if (y > 10 && x > winSize.width/3 && //x < winSize.width*2/3 && dy > -4) //dy *= 2; double v = java.lang.Math.sqrt(dx*dx+dy*dy); int wallidx = ix*wallSegments/winSize.width; double walltemp = (y == upperBound) ? walltemps_top[wallidx] : walltemps_bottom[wallidx]; double wto = walltemp; walltemp = walltemp * .8 + v*.2; //System.out.print("v " + v + " wto " + wto + // " walltemp " + walltemp + "\n"); double mix = getrand(10)/9.0; double newv = mix + walltemp/v*(1-mix); dx *= newv; dy *= newv; if (y == upperBound) walltemps_top[wallidx] = walltemp; else walltemps_bottom[wallidx] = walltemp; bounce = true; } int nix = (int) x; int niy = (int) y; if (!bounce && nix >= heaterLeft && nix <= heaterRight && niy >= heaterTop-1) { double v = java.lang.Math.sqrt(dx*dx+dy*dy); double mxv = temp; double mix = getrand(10)/9.0; double newv = v*mix + mxv*(1-mix); dx = getrand(101)/50.0-1; dy = -(1-dx*dx)*newv; dx *= v; bounce = true; y = heaterTop-2; niy = (int) y; } if (nix != ix || niy != iy) { try { grid[ix][iy] = -1; } catch (Exception e) { System.out.print(ix + " " + iy + "\n"); }; int q = grid[nix][niy]; if (q != -1 && !bounce) { x -= dx; y -= dy; double dx2 = mdx[q]; double dy2 = mdy[q]; double adx = (dx+dx2)/2; double ady = (dy+dy2)/2; //System.out.print("collide " + dx + " " + dy + " " // + dx2 + " " + dy2 + "\n"); double v = java.lang.Math. sqrt((dx-adx)*(dx-adx)+(dy-ady)*(dy-ady)); dx = getrand(101)/50.0-1; dy = (1-dx*dx)*v; dx *= v; mdx[q] = -dx + adx; mdy[q] = -dy + ady; dx += adx; dy += ady; } else { ix = nix; iy = niy; } grid[ix][iy] = i; } } mx[i] = x; my[i] = y; mdx[i] = dx; mdy[i] = dy; int col = (int)((((dx<0) ? -dx : dx) + ((dy < 0) ? -dy : dy))*7); if (col > 15) col = 15; g.setColor(colors[col]); g.fillRect((int)x, (int)y, 2, 2); } int heatstateint = ((int) heatstate); 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(tempcolor); g.fillRect(x, heaterTop+y, 2, 2); } for (j = 0; j != wallSegments; j++) { int col = (int) (walltemps_left[j]*7.0); if (col > 15) col = 15; g.setColor(colors[col]); g.drawLine(0, j*winSize.height/wallSegments, 0, (j+1)*winSize.height/wallSegments-1); col = (int) (walltemps_right[j]*7.0); if (col > 15) col = 15; g.setColor(colors[col]); g.drawLine(winSize.width-1, j*winSize.height/wallSegments, winSize.width-1, (j+1)*winSize.height/wallSegments-1); col = (int) (walltemps_top[j]*7.0); if (col > 15) col = 15; g.setColor(colors[col]); g.drawLine(j*winSize.width/wallSegments, upperBound, (j+1)*winSize.width/wallSegments-1, upperBound); col = (int) (walltemps_bottom[j]*7.0); if (col > 15) col = 15; g.setColor(colors[col]); g.drawLine(j*winSize.width/wallSegments, winSize.height-1, (j+1)*winSize.width/wallSegments-1, winSize.height-1); } for (j = 0; j != wallSegments-1; j++) { double wt = (walltemps_left[j] + walltemps_left[j+1])/2; walltemps_left[j] = (walltemps_left[j]+wt)/2; walltemps_left[j+1] = (walltemps_left[j+1]+wt)/2; wt = (walltemps_right[j] + walltemps_right[j+1])/2; walltemps_right[j] = (walltemps_right[j]+wt)/2; walltemps_right[j+1] = (walltemps_right[j+1]+wt)/2; wt = (walltemps_top[j] + walltemps_top[j+1])/2; walltemps_top[j] = (walltemps_top[j]+wt)/2; walltemps_top[j+1] = (walltemps_top[j+1]+wt)/2; wt = (walltemps_bottom[j] + walltemps_bottom[j+1])/2; walltemps_bottom[j] = (walltemps_bottom[j]+wt)/2; walltemps_bottom[j+1] = (walltemps_bottom[j+1]+wt)/2; } for (j = heaterLeft*wallSegments/winSize.width; j <= heaterRight*wallSegments/winSize.width; j++) walltemps_bottom[j] = tempMove; g.setColor(Color.lightGray); g.fillRect(winSize.width/2 - 20, 0, 40, upperBound); realg.drawImage(dbimage, 0, 0, this); if (!stopped) { heatstate += tempMove; cv.repaint(pause); } } 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]; for (i = 0; i != slots; i++) graph[i] = 0; int graphmax = 0; double maxv = 0; for (i = 0; i != molcount; i++) { double q = java.lang.Math.sqrt(mdx[i]*mdx[i]+mdy[i]*mdy[i]); if (q > maxv) maxv = q; } maxv += .5; for (i = 0; i != molcount; i++) { double q = java.lang.Math. sqrt(mdx[i]*mdx[i]+mdy[i]*mdy[i]) * slots/maxv; int r = (int) q; if (r >= slots) continue; graph[r]++; if (graphmax < graph[r]) graphmax = graph[r]; } for (i = 0; i != slots; i++) { if (graph[i] == 0) continue; int y = d.height-(graph[i] * d.height / graphmax); if (y == d.height) y--; int col = (int) (i*7*maxv/slots); if (col > 15) col = 15; g.setColor(colors[col]); g.fillRect(i*2, y, 2, d.height); } realg.drawImage(dbimage, 0, 0, this); hist_cv.repaint(1000); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e){} public void componentResized(ComponentEvent e) { reinit(0); cv.repaint(100); hist_cv.repaint(100); } public void actionPerformed(ActionEvent e) { if (e.getSource() == startButton) { stopped = false; cv.repaint(); } if (e.getSource() == stopButton) { stopped = true; } if (e.getSource() == resetButton) { stopped = false; reinit(0); cv.repaint(); } if (e.getSource() == equalButton) { stopped = false; reinit(1); cv.repaint(); } if (e.getSource() == extremeButton) { stopped = false; reinit(2); cv.repaint(); } if (e.getSource() == wallTempButton) { for (int j = 0; j != wallSegments; j++) { walltemps_left[j] = tempMove; walltemps_right[j] = tempMove; walltemps_top[j] = tempMove; walltemps_bottom[j] = tempMove; } } } public void adjustmentValueChanged(AdjustmentEvent e) { if (e.getSource() == volumeBar) { upperBound = winSize.height * (100-volumeBar.getValue()) / 100; areaHeight = winSize.height - upperBound; } if (e.getSource() == gravityBar) gravity = gravityBar.getValue() * (.001/20); if (e.getSource() == tempBar) adjustTemp(); } void adjustTemp() { temp = (tempBar.getValue() * .029111971)*3 + .01; tempMove = (tempBar.getValue() * .029111971) + .3; tempcolor = colors[(tempBar.getValue()*15)/100]; } }