// Diffusion applet // ---------------- // Educational demo program showing the effects of diffusion (as of perfume // molecules escaping from an open bottle into a room) // // Author: // Paul O. Lewis // Department of Ecology and // Evolutionary Biology // The University of Connecticut // 75 N. Eagleville Road, Unit 3043 // Storrs, CT 06269-3043 // // Tel: (860) 486-2069 // Fax: (860) 486-6364 // E-mail: paul.lewis@uconn.edu // http://hydrodictyon.eeb.uconn.edu/people/plewis/ // // Created: July 11, 1996 // Copyright (C) 1996 by Paul O. Lewis // // While now modified almost beyond recognition, this applet grew out of // playing with the Graph.java applet created by Jim Hagen and available // through the web site java.sun.com // // Permission to use, copy, modify, and distribute this software // and its documentation for NON-COMMERCIAL purposes and // without fee is hereby granted. // // Easy modifications: // To change number of particles, search for 'numParticles' // To change radius of particles, search for 'particleRadius' // To change thickness of barrier walls, search for 'wallthickness' // To change speed of particles, search for 'stepsize' // To change particles from being filled to being open, search for // 'fillOval' and change to 'drawOval' // To change barriers from being filled to being open, search for // 'fillRect' and change to 'drawRect' import java.util.*; import java.awt.*; import java.applet.Applet; class Barrier { double x; double y; double w; double h; boolean inside( double xx, double yy ) { boolean isInside = false; boolean xInside = ( xx >= x && xx <= x+w ); boolean yInside = ( y >= y && yy <= y+h ); if( xInside && yInside ) isInside = true; return isInside; } } class Particle { double x; double y; double angle; double steplen; } class GraphPanel extends Panel implements Runnable { Diffusion diff; final double wallthickness = 20.0; final int particleRadius = 4; final double stepsize = 5.0; Particle next; double insideleft; double insideright; double insidetop; double insidebottom; int nparticles; Particle particles[] = new Particle[100]; int nbarriers; Barrier barriers[] = new Barrier[3]; Thread diffuser; GraphPanel(Diffusion diff) { this.diff = diff; } void removeLid() { nbarriers=2; } void createBarriers( int w, int h ) { nbarriers = 3; insideleft = w/2 - 0.1*w; insideright = w/2 + 0.1*w; insidetop = h - 0.2*h; insidebottom = h; // create left side of the container Barrier b = new Barrier(); b.x = w/2 - 0.1*w - wallthickness; b.w = wallthickness; b.y = h - 0.2*h; b.h = 0.2*h; barriers[0] = b; // create right side of the container b = new Barrier(); b.x = w/2 + 0.1*w; b.w = wallthickness; b.y = h - 0.2*h; b.h = 0.2*h; barriers[1] = b; // create lid of the container b = new Barrier(); b.x = w/2 - 0.1*w; b.w = 0.2*w; b.y = h - 0.2*h - wallthickness; b.h = wallthickness; barriers[2] = b; } int addParticle() { Particle p = new Particle(); // set initial x coordinate for new particle double leftmost = insideleft + 2.0*particleRadius; double extent = insideright - insideleft - 4.0*particleRadius; p.x = leftmost + extent*Math.random(); // set initial y coordinate for new particle double topmost = insidetop + 2.0*particleRadius; extent = insidebottom - insidetop - 4.0*particleRadius; p.y = topmost + extent*Math.random(); // set initial direction of travel for new particle p.angle = 2.0 * Math.PI * Math.random(); particles[nparticles] = p; return nparticles++; } public void run() { while(true) { diffuse(); try { Thread.sleep(100); } catch (InterruptedException e) { break; } } } double checkRange( double theta ) { double twoPI = 2.0 * Math.PI; while( theta < 0.0 ) { theta += twoPI; } while( theta > twoPI ) { theta -= twoPI; } return theta; } void checkPlotBoundaries( int i ) { boolean hit = false; Particle p = particles[i]; double dx = p.steplen * Math.cos( p.angle ); double dy = p.steplen * Math.sin( p.angle ); double xx = p.x + dx; double yy = p.y + dy; Dimension d = size(); double deltaX; double deltaY; double r; double surfacex; double surfacey; double theta = particles[i].angle; // is particle moving off the left side of the plot area? surfacex = xx - particleRadius; if( surfacex < 0.0 ) { hit = true; deltaX = particles[i].x - particleRadius - 0.0; r = deltaX / Math.cos(theta); if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.x = 0.0 + particleRadius; next.y = particles[i].y + ( deltaX * Math.tan(theta) ); next.angle = checkRange(Math.PI - theta); } } // is particle moving off the right side of the plot area? surfacex = xx + particleRadius; if( !hit && surfacex > d.width-1 ) { hit = true; deltaX = (d.width - 1.0) - (particles[i].x + particleRadius); r = deltaX / Math.cos(theta); if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.x = d.width - 1.0 - particleRadius; next.y = particles[i].y + ( deltaX * Math.tan(theta) ); next.angle = checkRange(Math.PI - theta); } } // is particle moving off the top side of the plot area? surfacey = yy - particleRadius; if( !hit && surfacey < 0.0 ) { hit = true; deltaY = (particles[i].y - particleRadius) - 0.0; r = deltaY / Math.sin(theta); if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.x = particles[i].x + ( deltaY / Math.tan(theta) ); next.y = 0.0 + particleRadius; next.angle = checkRange(-theta); } } // is particle moving off the bottom side of the plot area? surfacey = yy + particleRadius; if( !hit && surfacey > d.height-1 ) { hit = true; deltaY = (d.height - 1.0) - (particles[i].y + particleRadius); r = deltaY / Math.sin(theta); if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.x = particles[i].x + deltaY / Math.tan(theta); next.y = ( d.height - 1.0 ) - particleRadius; next.angle = checkRange(-theta); } } } /************************************************************************* | checkTop, checkBottom, checkLeft, and checkRight all check whether the | path of particle i is going to intersect the surface of barrier j. If | so, they then check whether the distance to the intersection is less than | that found thus far for any other barrier or the sides of the plot area. | If this is the case, the dummy particle next takes on the parameters that | particle i would have if it turns out that this intersection is indeed the | closest (after all other barriers are checked). Then, and only then, is | this information actually copied to particle i. If you don't take the | precaution of checking for the shortest path to an intersection, then you | end up with particles entering barriers. */ /** check whether particle i is just about to hit barrier j from the top */ void checkTop( int i, int j ) { Barrier b = barriers[j]; double theta = particles[i].angle; double len = particles[i].steplen; // let (x,y) be at the bottom of the particle at its current position double x = particles[i].x; double y = particles[i].y + particleRadius; // deltaY is vertical distance from (x,y) to top of barrier double deltaY = b.y - y; // determine whether particle is indeed moving down boolean thetaCorrect = ( theta > 0.0 && theta < Math.PI ); if( deltaY > 0.0 && thetaCorrect ) { double deltaX = deltaY / Math.tan(theta); double xx = x + deltaX; double r = deltaY / Math.sin(theta); // handle particle coming in at the corners double leftmost = b.x; double rightmost = b.x + b.w; if( theta > 0.0 && theta < Math.PI/2.0 ) leftmost -= particleRadius; else rightmost += particleRadius; if( xx >= leftmost && xx <= rightmost && r <= len ) { if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.angle = checkRange(-theta); next.x = xx; next.y = b.y - particleRadius; } } } } /** check whether particle i is just about to hit barrier j from the left */ void checkLeft( int i, int j ) { Barrier b = barriers[j]; double theta = particles[i].angle; double len = particles[i].steplen; // let (x,y) be at the right edge of the particle // at its current position double x = particles[i].x + particleRadius; double y = particles[i].y; // deltaX is horizontal distance from (x,y) to left side of barrier double deltaX = b.x - x; // determine whether particle is indeed moving to the right boolean thetaCorrect = ( theta > 3.0*Math.PI/2.0 && theta < 2.0*Math.PI ); if( !thetaCorrect ) thetaCorrect = ( theta > 0.0 && theta < Math.PI/2.0 ); if( deltaX > 0.0 && thetaCorrect ) { double deltaY = deltaX * Math.tan(theta); double yy = y + deltaY; double r = deltaY / Math.sin(theta); // handle particle coming in at the corners double topmost = b.y; double bottommost = b.y + b.h; if( theta > 0.0 && theta < Math.PI/2.0 ) topmost -= particleRadius; else bottommost += particleRadius; if( yy >= topmost && yy <= bottommost && r <= len ) { if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.angle = checkRange(Math.PI - theta); next.x = b.x - particleRadius; next.y = yy; } } } } /** check whether particle i is just about to hit barrier j from the right */ void checkRight( int i, int j ) { Barrier b = barriers[j]; double theta = particles[i].angle; double len = particles[i].steplen; // let (x,y) be at the left edge of the particle // at its current position double x = particles[i].x - particleRadius; double y = particles[i].y; // deltaX is horizontal distance from (x,y) to right side of barrier double deltaX = b.x + b.w - x; // determine whether particle is indeed moving to the left boolean thetaCorrect = ( theta > Math.PI/2.0 && theta < 3.0*Math.PI/2.0 ); if( deltaX < 0.0 && thetaCorrect ) { double deltaY = deltaX * Math.tan(theta); double yy = y + deltaY; double r = deltaY / Math.sin(theta); // handle particle coming in at the corners double topmost = b.y; double bottommost = b.y + b.h; if( theta > Math.PI/2.0 && theta < Math.PI ) topmost -= particleRadius; else bottommost += particleRadius; if( yy >= topmost && yy <= bottommost && r <= len ) { if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.angle = checkRange(Math.PI - theta); next.x = b.x + b.w + particleRadius; next.y = yy; } } } } /** check whether particle i is just about to hit barrier j from the bottom */ void checkBottom( int i, int j ) { Barrier b = barriers[j]; double theta = particles[i].angle; double len = particles[i].steplen; // let (x,y) be at the top of the particle // at its current position double x = particles[i].x; double y = particles[i].y - particleRadius; // deltaY is vertical distance from (x,y) to bottom of barrier double deltaY = b.y + b.h - y; // determine whether particle is indeed moving up boolean thetaCorrect = ( theta > Math.PI && theta < 2.0*Math.PI ); if( deltaY < 0.0 && thetaCorrect ) { double deltaX = deltaY / Math.tan(theta); double xx = x + deltaX; double r = deltaY / Math.sin(theta); // handle particle coming in at the corners double leftmost = b.x; double rightmost = b.x + b.w; if( theta > 3.0*Math.PI/2.0 && theta < 2.0*Math.PI ) leftmost -= particleRadius; else rightmost += particleRadius; if( xx >= leftmost && xx <= rightmost && r <= len ) { if( particles[i].steplen - r > next.steplen ) { next.steplen = particles[i].steplen - r; next.angle = checkRange(-theta); next.x = xx; next.y = b.y + b.h + particleRadius; } } } } void moveParticle( int i ) { // if next.steplen never increased, means we never hit // any barriers or the plot boundary next.steplen = -1.0; double dx; double dy; checkPlotBoundaries(i); for( int j = 0; j < nbarriers; j++ ) { checkTop(i, j); checkLeft(i, j); checkRight(i, j); checkBottom(i, j); } if( next.steplen < 0.0 ) { // nothing hit, take full step double theta = particles[i].angle; double len = particles[i].steplen; particles[i].steplen = 0.0; dx = len * Math.cos(theta); dy = len * Math.sin(theta); particles[i].x += dx; particles[i].y += dy; } else { // something hit, take smallest of the possible // steps (corresponds to largest residual steplen) double theta = particles[i].angle; double len = particles[i].steplen - next.steplen; particles[i].steplen = next.steplen; particles[i].x = next.x; particles[i].y = next.y; particles[i].angle = next.angle; } } synchronized void diffuse() { Dimension d = size(); for(int i = 0; i < nparticles; i++ ) { // move an amount (stepsize) in direction specified by particle's angle particles[i].steplen = stepsize; while( particles[i].steplen > 0.0 ) { moveParticle(i); } } repaint(); } Image offscreen; Dimension offscreensize; Graphics offgraphics; public void paintParticle(Graphics g, Particle p) { int x = (int)p.x; int y = (int)p.y; g.setColor(Color.black); int particleDiameter = 2*particleRadius; g.fillOval(x - particleRadius, y - particleRadius, particleDiameter, particleDiameter); } public void paintBarrier(Graphics g, Barrier b) { int x = (int)b.x; int y = (int)b.y; int w = (int)b.w; int h = (int)b.h; g.setColor(Color.black); g.fillRect(x, y, w, h); } public synchronized void update(Graphics g) { Dimension d = size(); if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) { offscreen = createImage(d.width, d.height); offscreensize = d; offgraphics = offscreen.getGraphics(); offgraphics.setFont(getFont()); } offgraphics.setColor(getBackground()); offgraphics.fillRect(0, 0, d.width, d.height); offgraphics.setColor(Color.red); offgraphics.drawRect(0, 0, d.width-1, d.height-1); for (int j = 0 ; j < nbarriers ; j++) { paintBarrier(offgraphics, barriers[j]); } for (int i = 0 ; i < nparticles ; i++) { paintParticle(offgraphics, particles[i]); } g.drawImage(offscreen, 0, 0, null); } public void start() { Dimension d = size(); next = new Particle(); int numParticles = 100; createBarriers( d.width, d.height ); for( int i = 0; i < numParticles; i++ ) addParticle(); diffuser = new Thread(this); diffuser.start(); } public void stop() { diffuser.stop(); } } public class Diffusion extends Applet { GraphPanel panel; public void init() { setLayout(new BorderLayout()); panel = new GraphPanel(this); add("Center", panel); Panel p = new Panel(); add("South", p); p.add(new Button("Remove lid")); } public void start() { panel.start(); } public void stop() { panel.stop(); } public boolean action(Event evt, Object arg) { if ("Remove lid".equals(arg)) { panel.removeLid(); return true; } return false; } }