// "Appear" - a simple lattice-gas like simulation for MAS967. // lots of code here. class Particle has the logic of what it's like // to be a particle. HexGrid and HexCoord implement the world geometry. // Starts out with 5 particles - every 100 steps add a new particle import java.applet.*; import java.awt.*; import java.util.*; import HexGrid; import Particle; public class Appear extends DoubleBufferApplet implements Runnable { Vector particleList; HexGrid hexGrid; int clock = 0; Dimension cellSize; // size for drawing double cellSpacing = 0.20; // fraction of cellsize double cellLineWidth = 0.1; // fraction of cellsize protected Thread simulationThread; int frameDelay = 300; // milliseconds public void init() { this.setBackground(Color.white); this.setForeground(Color.black); update(getGraphics()); // force window create String gridSizeString = getParameter("gridSize"); int gridSize = 4; try { if (gridSizeString != null) gridSize = Integer.parseInt(gridSizeString); } catch (Exception e) { gridSize = 4; } String frameDelayString = getParameter("frameDelay"); try { if (frameDelayString != null) frameDelay = Integer.parseInt(frameDelayString); } catch (Exception e) {} hexGrid = new HexGrid(gridSize, gridSize); particleList = new Vector(10); particleList.addElement(new Particle(hexGrid, (int)(Math.random()*gridSize), (int)(Math.random()*gridSize), (int)(Math.random()*6))); particleList.addElement(new Particle(hexGrid, (int)(Math.random()*gridSize), (int)(Math.random()*gridSize), (int)(Math.random()*6))); particleList.addElement(new Particle(hexGrid, (int)(Math.random()*gridSize), (int)(Math.random()*gridSize), (int)(Math.random()*6))); particleList.addElement(new Particle(hexGrid, (int)(Math.random()*gridSize), (int)(Math.random()*gridSize), (int)(Math.random()*6))); particleList.addElement(new Particle(hexGrid, (int)(Math.random()*gridSize), (int)(Math.random()*gridSize), (int)(Math.random()*6))); // calculate the cell size (really only has to be done on resize) cellSize = new Dimension((int)(imageBufferSize.width / (hexGrid.width + hexGrid.width * cellSpacing)), (int)(imageBufferSize.height / (hexGrid.height + hexGrid.height * cellSpacing))); } public void run() { while (Thread.currentThread() == simulationThread) { Graphics g = imageBuffer.getGraphics(); // pick the first particle on the list, activate it, move it to the back Particle p = (Particle)(particleList.firstElement()); particleList.removeElement(p); particleList.addElement(p); drawEmptyCell(g, p.location.x, p.location.y); p.step(); drawParticle(g, p); drawParticleTail(g, p); repaint(); clock++; if (clock % 100 == 0) particleList.addElement(new Particle(hexGrid, (int)(Math.random()*hexGrid.width), (int)(Math.random()*hexGrid.height), (int)(Math.random()*6))); // sleep for next update.. try { Thread.sleep(frameDelay); } catch (InterruptedException ex) { System.out.println("Sleep Interrupted??"); } } } public void start() { super.start(); simulationThread = new Thread(this); simulationThread.start(); } public void stop() { simulationThread = null; super.stop(); } public boolean keyDown(Event e, int key) { if ((char)key == ' ') { if (simulationThread == null) { simulationThread = new Thread(this); simulationThread.start(); } else { simulationThread.stop(); simulationThread = null; } } return true; } public boolean mouseDown(Event e, int mouseX, int mouseY) { int x, y = 0; boolean clickInCell = false; // ick ick ick - to figure out where mouse click happened, just // scan all cells. This is so evil! guessMouseClick: for (x = 0; x < hexGrid.width; x++) for (y = ((x % 2 == 0) ? 0 : -1); y < hexGrid.height; y++) if (cellRectangle(x, y).inside(mouseX, mouseY)) { clickInCell = true; if (y == -1) // correct for odd cell y = hexGrid.height - 1; break guessMouseClick; } Particle clickedParticle = (Particle)(hexGrid.elementAt(x, y)); if (clickInCell && clickedParticle != null) { if (mouseX < cellCenter(x, y).x) clickedParticle.rotate(1); else clickedParticle.rotate(-1); Graphics g = imageBuffer.getGraphics(); drawEmptyCell(g, x, y); drawParticle(g, clickedParticle); drawParticleTail(g, clickedParticle); repaint(); } return true; } public void paint(Graphics g) { // calculate the cell size (really only has to be done on resize) cellSize = new Dimension((int)(imageBufferSize.width / (hexGrid.width + hexGrid.width * cellSpacing)), (int)(imageBufferSize.height / (hexGrid.height + hexGrid.height * cellSpacing))); // render the empty grid int x, y; for (x = 0; x < hexGrid.width; x++) for (y = 0; y < hexGrid.height; y++) drawEmptyCell(imageBuffer.getGraphics(), x, y); // and the particles drawParticles(); super.paint(g); } public void drawParticles() { Enumeration e = particleList.elements(); while (e.hasMoreElements()) drawParticle(imageBuffer.getGraphics(), (Particle)e.nextElement()); e = particleList.elements(); while (e.hasMoreElements()) drawParticleTail(imageBuffer.getGraphics(), (Particle)e.nextElement()); } // draw an empty cell at x, y. public void drawEmptyCell(Graphics g, int x, int y) { Rectangle r = cellRectangle(x, y); g.setColor(Color.black); g.fillOval(r.x, r.y, r.width, r.height); // draw a smaller oval inside r.x += r.width * cellLineWidth; r.y += r.height * cellLineWidth; r.width -= (int)(r.width * cellLineWidth) * 2; // int conversion to r.height -= (int)(r.height * cellLineWidth) * 2; // duplicate roundoff err g.setColor(Color.white); g.fillOval(r.x, r.y, r.width, r.height); // draw two halves for odd column, bottom cell if (x % 2 == 1 && y == hexGrid.height - 1) { r = cellRectangle(x, -1); g.setColor(Color.black); g.fillOval(r.x, r.y, r.width, r.height); // draw a smaller oval inside r.x += r.width * cellLineWidth; r.y += r.height * cellLineWidth; r.width -= (int)(r.width * cellLineWidth) * 2; r.height -= (int)(r.height * cellLineWidth) * 2; g.setColor(Color.white); g.fillOval(r.x, r.y, r.width, r.height); } } public void drawParticle(Graphics g, Particle p) { Rectangle r = cellRectangle(p.location.x, p.location.y); r.x += r.width * cellLineWidth*2; r.y += r.height * cellLineWidth*2; r.width -= (int)(r.width * cellLineWidth) * 4; // int conversion to r.height -= (int)(r.height * cellLineWidth) * 4; // duplicate roundoff err g.setColor(Color.black); g.fillOval(r.x, r.y, r.width, r.height); // draw two halves for odd column, bottom cell if (p.location.x % 2 == 1 && p.location.y == hexGrid.height - 1) { r = cellRectangle(p.location.x, -1); r.x += r.width * cellLineWidth*2; r.y += r.height * cellLineWidth*2; r.width -= (int)(r.width * cellLineWidth) * 4; r.height -= (int)(r.height * cellLineWidth) * 4; g.setColor(Color.black); g.fillOval(r.x, r.y, r.width, r.height); } } public void drawParticleTail(Graphics g, Particle p) { g.setColor(Color.white); Point center = cellCenter(p.location.x, p.location.y); g.fillOval(center.x-8, center.y-8, 16, 16); // draw tails HexCoord lastLocation = new HexCoord(p.location); lastLocation.moveAwayFromDirection(p.direction); // not normalized Point oldCenter = cellCenter(lastLocation.x, lastLocation.y); Point newCenter = cellCenter(p.location.x, p.location.y); g.setColor(Color.white); // magic tail length... drawThickLine(g, newCenter.x, newCenter.y, (int)(newCenter.x + (oldCenter.x - newCenter.x) / 3.6), (int)(newCenter.y + (oldCenter.y - newCenter.y) / 3.6), 6); // and draw another tail for top if in the funny row if (p.location.x % 2 == 1 && p.location.y == hexGrid.height - 1) { HexCoord top = new HexCoord(p.location); top.y = -1; center = cellCenter(top.x, top.y); g.fillOval(center.x-8, center.y-8, 16, 16); lastLocation = new HexCoord(top); lastLocation.moveAwayFromDirection(p.direction); // not normalized oldCenter = cellCenter(lastLocation.x, lastLocation.y); newCenter = cellCenter(top.x, top.y); g.setColor(Color.white); drawThickLine(g, newCenter.x, newCenter.y, (int)(newCenter.x + (oldCenter.x - newCenter.x) / 3.6), (int)(newCenter.y + (oldCenter.y - newCenter.y) / 3.6), 6); } } // here's the dirty work - for a given coordinate, figure out what // rectangle that cell should be drawn in. Handles staggering of // odd columns, also uses cellSpacing - what fraction of cellSize // to leave around each unit. // note that in the case of the bottom cell on odd columns, only half // the cell will be on-screen. You can find a rect to draw the other // half at the top by looking at (x, -1). public Rectangle cellRectangle(int x, int y) { Rectangle r = new Rectangle(cellSize.width, cellSize.height); r.x = (int)(cellSize.width*cellSpacing/2 + x*cellSize.width + x*cellSize.width*cellSpacing); if (x % 2 == 0) r.y = (int)(cellSize.height*cellSpacing/2 + y*cellSize.height + y*cellSize.height*cellSpacing); else r.y = (int)(cellSize.height*cellSpacing/2 + y*cellSize.height + y*cellSize.height*cellSpacing + (cellSize.height + cellSize.height*cellSpacing)/2); return r; } public Point cellCenter(int x, int y) { int screenX = (int)(cellSize.width*cellSpacing/2 + x*cellSize.width + x*cellSize.width*cellSpacing) + cellSize.width/2; int screenY; if (x % 2 == 0) screenY = (int)(cellSize.height*cellSpacing/2 + y*cellSize.height + y*cellSize.height*cellSpacing) + cellSize.height/2; else screenY = (int)(cellSize.height*cellSpacing/2 + y*cellSize.height + y*cellSize.height*cellSpacing + (cellSize.height + cellSize.height*cellSpacing)/2) + cellSize.height/2; return new Point(screenX, screenY); } public void drawThickLine(Graphics g, int x1, int y1, int x2, int y2, int thick) { // Cruft below is for drawing a thick line between two points. // First, figure out which distance is bigger. int xdistance, ydistance; xdistance = (x1 - x2); if (xdistance < 0) xdistance = -xdistance; ydistance = (y1 - y2); if (ydistance < 0) ydistance = -ydistance; // move one point at a time along the longer axis, drawing // lines along the way. if (xdistance > ydistance) { int x, dx; float y, dy; dx = (x1 < x2) ? 1 : -1; dy = (float)ydistance / (float)xdistance; if (y1 > y2) dy = -dy; for (x = x1, y = y1; x != x2; x += dx, y += dy) g.drawLine(x, Math.round(y) - thick/2, x, Math.round(y) + thick/2); } else { float x, dx; int y, dy; dy = (y1 < y2) ? 1 : -1; dx = (float)xdistance / (float)ydistance; if (x1 > x2) dx = -dx; for (x = x1, y = y1; y != y2; x += dx, y += dy) g.drawLine(Math.round(x) - thick/2, y, Math.round(x) + thick/2, y); } } }