import java.applet.*; import java.util.*; import java.io.*; import java.net.*; import java.awt.*; import java.awt.image.ColorModel; import java.awt.image.MemoryImageSource; /* portions of this code were adapted from the DitherTest applet */ /* midpoint subdivision codes adapted from Usenet and published references * - Basic plasma from implementation by Andrea Griffini (Usenet) * - Diamond-square subdivision adapted from implementation by * Paul Martz (Usenet) * - Offset-squares method adapted from 'moonbase' demo by James McNeill */ public class SubdivApplet extends Applet implements Runnable { /************************************************************ * Boilerplate methods to handle painting and initialization ************************************************************/ private int width = 256; private int height = 256; private double scalefactor = 1.0; /* Frame only used for standalone apps private static Frame my_frame = null; */ private int[][] heightfield; private Random rgen; private Image img; private boolean started; /* only things done in here are done for standalone program, not applet public SubdivApplet() { setTitle("Midpoint Subdivision Fractal"); } */ public boolean handleEvent(Event evt) { if (evt.id == Event.WINDOW_DESTROY) System.exit(0); return super.handleEvent(evt); } public boolean action( Event evt, Object arg) { return true; } /*static*/ String calcString = "Calculating..."; /*static*/ String calcMethod; public void paint(Graphics g) { int w = size().width; int h = size().height; if (img == null) { super.paint(g); g.setColor(Color.black); FontMetrics fm = g.getFontMetrics(); int x = (w - fm.stringWidth(calcString))/2; int y = h/2; g.drawString(calcString, x, y); } else { g.drawImage(img, 0, 0, w, h, this); } } public void init() { int w = Integer.parseInt(getParameter("width")); if (w < =0 || w > 256) w=width; width=w; height=w; scalefactor = 256/width; calcMethod=getParameter("method"); rgen = new Random(); started = false; start(); } /* main is used for a standalone program, not applet public static void main(String args[]) { SubdivApplet f = new SubdivApplet(); f.resize(width, height); f.show(); my_frame = f; rgen = new Random(); f.start(); } */ synchronized void BuildImage() { /* build the image for display -- greyscale */ int pixels[]; int i, j, a, index = 0, min, max; // calculate range of values in heightfield min = heightfield[0][0]; max = heightfield[0][0]; for (i=0;i < width;i++) { for (j=0;j < width;j++) { if (heightfield[i][j] < min) min = heightfield[i][j]; if (heightfield[i][j] > max) max = heightfield[i][j]; } } scalefactor = 255 / (max-min); pixels = new int[width * height]; for (i=0;i < width;i++) { for (j=0;j < width;j++) { a = (int)((heightfield[i][j] - min) * scalefactor); if (a < 0) a=0; if (a > 255) a=255; /*if (a > 255) a=255;*/ pixels[index++] = (255 << 24) | (a << 16) | (a << 8) | a; } } img = createImage(new MemoryImageSource(width, height, ColorModel.getRGBdefault(), pixels, 0, width)); repaint(); } /************************************************************ * Thread methods to handle processing in the background ************************************************************/ Thread kicker; public /*synchronized*/ void start() { if (!started) { started = true; kicker = new Thread(this); kicker.start(); } else if ((kicker != null) && (kicker.isAlive())) kicker.resume(); } public /*synchronized*/ void stop() { try { if ((kicker != null) && (kicker.isAlive())) { kicker.suspend(); } } catch (Exception e) { } } public void restart() { try { if (kicker != null) { kicker.stop(); } } catch (Exception e) { } kicker = null; img = null; started = false; start(); } public void run() { Thread me = Thread.currentThread(); me.setPriority(4); if (calcMethod.equals("standard")) DoPlasma(0.5, 0); else if (calcMethod.equals("diamond-square")) DoDiamondSquare(width); else if (calcMethod.equals("offset-square")) DoOffsetSquare(width); } /************************************************************ * Plasma implementations ************************************************************/ /* Plasma example - basic midpoint subdivision on a grid grid size is a power of 2 (width for this example) integer math is used where possible random distribution is uniform random K is scaling factor (ratio of altitude variation to length), H is constant noise factor, independent of area size; typical values passed in are (0.5, 0.0) so that, with a width cell grid, starting values range from -128..128. This still runs the risk of overflow, but can still be acceptable. altitude at each level is uniform random, + or - (h*K+H)/2 where h is the stepsize (length of area side) */ /* this used to return 'byte' instead of 'int' */ private int Rand1(int base,int delta) { int i; i=base+(rgen.nextInt()%delta)-(delta/2); /*if (i < -128) i=-128; if (i > 127) i=127;*/ return (int)i; } synchronized void DoPlasma(double K,double H) { int i,j,i2,j2,i3,j3,h,h2,delta,a,b,c,d; /* Get the memory (widthxwidth bytes) */ heightfield = new int[width][width]; /* base value = 0, byte values are -128..127 */ for (i=0;i < width;i++) for (j=0;j < width;j++) heightfield[i][j]=0; /* Plasma clouds computing */ for (h=width; h > 1; h=h2) { h2=h/2; delta= (int)(h*K+H); for (i=0; i < width; i+=h) { i2=((i+h)&(width-1)); for (j=0; j < width; j+=h) { /* Get corner heights (a=TL, b=TR, c=BL, d=BR) */ j2=((j+h)&(width-1)); i3=i+h2; j3=j+h2; a=heightfield[i][j]; b=heightfield[i2][j]; c=heightfield[i][j2]; d=heightfield[i2][j2]; /* Central point */ heightfield[i3][j3]=Rand1((a+b+c+d)/4,delta); /* Top point (only needed for top row) */ if (i==0) heightfield[i3][j]=Rand1((a+b)/2,delta); /* Left point (only needed for left column) */ if (j==0) heightfield[i][j3]=Rand1((a+c)/2,delta); /* Right point */ heightfield[i2][j3]=Rand1((b+d)/2,delta); /* Bottom point */ heightfield[i3][j2]=Rand1((c+d)/2,delta); } } } BuildImage(); } /**************************************************************** Plasma example - diamond-square midpoint subdivision on a grid. The surface is iteratively computed by calculating the center point of the initial square (where the four corners of the surface are the corners of the square), then of the resulting diamonds, then of squares again, etc. Code adapted from 'fillSurf' example posted to Usenet by Paul Martz (pmartz@dsd.es.com). Original source calculated full 3D points; I have elided that for this version because the underlying regular grid gives you that implicitly. Original copyright notice: Use this code anyway you want as long as you don't sell it for money. Paul Martz, 1995 ****************************************************************/ private int avgyvals (int i, int j, int strut, int dim) { if (i == 0) return ((heightfield[i][(j-strut)&(width-1)] + heightfield[i][(j+strut)&(width-1)] + heightfield[(i+strut)&(width-1)][j]) / 3); else if (i == dim-1) return ((heightfield[i][(j-strut)&(width-1)] + heightfield[i][(j+strut)&(width-1)] + heightfield[(i-strut)&(width-1)][j]) / 3); else if (j == 0) return ((heightfield[(i-strut)&(width-1)][j] + heightfield[(i+strut)&(width-1)][j] + heightfield[i][(j+strut)&(width-1)]) / 3); else if (j == dim-1) return ((heightfield[(i-strut)&(width-1)][j] + heightfield[(i+strut)&(width-1)][j] + heightfield[i][(j-strut)&(width-1)]) / 3); else return ((heightfield[(i-strut)&(width-1)][j] + heightfield[(i+strut)&(width-1)][j] + heightfield[i][(j-strut)&(width-1)] + heightfield[i][(j+strut)&(width-1)]) / 4); } private int avgyvals2 (int i, int j, int strut, int dim) { int tstrut = strut/2; return ((heightfield[(i-tstrut)&(width-1)][(j-tstrut)&(width-1)] + heightfield[(i-tstrut)&(width-1)][(j+tstrut)&(width-1)] + heightfield[(i+tstrut)&(width-1)][(j-tstrut)&(width-1)] + heightfield[(i+tstrut)&(width-1)][(j+tstrut)&(width-1)]) / 4); } synchronized void DoDiamondSquare(int dim) { int i, j; int strut, tstrut; boolean oddline; /* Get the memory (widthxwidth bytes) */ heightfield = new int[width][width]; /* base value = 0, byte values are -128..127 */ for (i=0;i < width;i++) for (j=0;j < width;j++) heightfield[i][j]=0; /* initialize things */ strut = dim / 2; /* seed the first y values */ /* we disregard this for direct comparison with other method. oddline = false; for (i=0; i < dim; i+=strut) { oddline = (!oddline); for (j=0; j < dim; j+=strut) { if (!oddline) j+= strut; heightfield[i][j] = Rand1(0, strut); j+=strut; } } */ /* create fractal surface from seeded values */ tstrut = strut; while (tstrut > 0) { oddline = false; for (i=0; i < dim; i+=tstrut) { oddline = (!oddline); for (j=0; j < dim; j+=tstrut) { if ((oddline) && (j==0)) j+=tstrut; heightfield[i][j] = Rand1(avgyvals (i, j, tstrut, dim), tstrut); j+=tstrut; } } if (tstrut/2 != 0) { for (i=tstrut/2; i < dim; i+=tstrut) { for (j=tstrut/2; j < dim; j+=tstrut) { heightfield[i][j] = Rand1(avgyvals2(i, j, tstrut, dim), tstrut); } } } tstrut /= 2; } BuildImage(); } /**************************************************************** Plasma example - offset square midpoint subdivision on a grid. This method is similar to standard subdivision, but it is 'offset' in that the new points at each level are offset from the previous level by half the square size. The original corner points at level x are only used once to form a weighted average, and then become the center points of the new squares: O O O O becomes x x O O x x x x x x x x O O x x (Okay, so I don't do good ASCII squares; I hope you get the idea.) It should be clear that this method includes some non-local context when calculating new heights. With standard subdivision, the only heights that can ever influence the area inside the original square are the original corner points. With offset squares, most of the new corner points lie outside the original square and therefore are influenced by more distant points. This feature can be a problem, because if you don't want the map to be toroidal (wrapped in both x and y), you need to generate a large number of points outside the final terrain area. For this example, I just wrap x and y. Original copyright notice: This code was inspired by looking at Tim Clark's (?) Mars demo; hence the name "Moonbase". (Moonbase is also the name of our student-run Linux server.) I believe the Mars demo is still available on x2ftp.oulu.fi. You are free to use my code, my ideas, whatever. I have benefited enormously from the free information on the Internet, and I would like to keep that process going. James McNeill mcneja@wwc.edu ****************************************************************/ synchronized void DoOffsetSquare(int width) { int row_offset = 0; // start at zero for first row /* Get the memory (widthxwidth bytes) */ heightfield = new int[width][width]; /* base value = 0, byte values are -128..127 */ for (int i=0;i < width;i++) for (int j=0;j < width;j++) heightfield[i][j]=0; for ( int square_size = width; square_size > 1; square_size /= 2 ) { int random_range = square_size; for ( int x1 = row_offset; x1 < width; x1 += square_size ) { for ( int y1 = row_offset; y1 < width; y1 += square_size ) { // Get the four corner points. int x2 = (x1 + square_size) % width; int y2 = (y1 + square_size) % width; int i1 = heightfield[x1][y1]; int i2 = heightfield[x2][y1]; int i3 = heightfield[x1][y2]; int i4 = heightfield[x2][y2]; // Obtain new points by averaging the corner points. int p1 = ((i1 * 9) + (i2 * 3) + (i3 * 3) + (i4)) / 16; int p2 = ((i1 * 3) + (i2 * 9) + (i3) + (i4 * 3)) / 16; int p3 = ((i1 * 3) + (i2) + (i3 * 9) + (i4 * 3)) / 16; int p4 = ((i1) + (i2 * 3) + (i3 * 3) + (i4 * 9)) / 16; // Add a random offset to each new point. p1 = Rand1(p1, random_range); p2 = Rand1(p2, random_range); p3 = Rand1(p3, random_range); p4 = Rand1(p4, random_range); // Write out the generated points. int x3 = (x1 + square_size/4) % width; int y3 = (y1 + square_size/4) % width; x2 = (x3 + square_size/2) % width; y2 = (y3 + square_size/2) % width; heightfield [x3][y3] = p1; heightfield [x2][y3] = p2; heightfield [x3][y2] = p3; heightfield [x2][y2] = p4; } } row_offset = square_size/4; // set offset for next row } BuildImage(); } }