// A class that represents Java Canvas's that draw maps of fractal landmasses // on themselves. Note that I called this class "IslandCanvas" because the // landmasses often, but not always, look like islands. // History: // // January 2007 -- Created by Doug Baldwin as a prototype for a CSci 240 // exercise. import java.awt.Canvas; import java.awt.Graphics; import java.awt.Color; import java.util.Random; class IslandCanvas extends Canvas { // Internally, island canvases store an array of elevations, and the size // of each side of this array. They also store a random number generator for // computing elevations: private double[][] elevations; private int size; private Random generator; // The constructor. This takes the size (i.e., height and width) of the // map, in pixels, as its parameter. This creates a size-by-size array // to hold the map's elevations, and stores the size for future reference. // It also creates the random number generator that will be used to // randomize elevations. Finally, it seeds the elevation array with // below-sea-level elevations at the corners, and calls an auxiliary // method to fill in the remaining elevations. public IslandCanvas( int size ) { this.size = size; this.elevations = new double[size][size]; generator = new Random(); elevations[0][0] = elevations[0][size-1] = elevations[size-1][0] = elevations[size-1][size-1] = -25.0; this.calculateElevations( 0, 0, size-1, size-1 ); } // The "paint" method that all Canvas subclasses must provide. This simply // steps through the array of elevations, deducing the color for pixel (i,j) // in the window from the elevation of cell (i,j). Pixels are colored according // to their elevations. public void paint( Graphics g ) { // Elevations at which map colors change, and the colors associated // with them: final double seaLevel = 0.0; final Color seaColor = new Color( (float)0.35, (float)0.35, (float)1.0 ); final double forestLevel = 25.0; final Color forestColor = new Color( (float)0.0, (float)0.6, (float)0.0 ); final double plainsLevel = 60.0; final Color plainsColor = new Color( (float)0.75, (float)0.55, (float)0.25 ); final Color peakColor = new Color( (float)1.0, (float)1.0, (float)1.0 ); // Fill in colors for each pixel according to its elevation: for ( int i = 0; i < size; i++ ) { for ( int j = 0; j < size; j++ ) { if ( elevations[i][j] < seaLevel ) { g.setColor( seaColor ); } else if ( elevations[i][j] < forestLevel ) { g.setColor( forestColor ); } else if ( elevations[i][j] < plainsLevel ) { g.setColor( plainsColor ); } else { g.setColor( peakColor ); } g.fillRect( j, i, 1, 1 ); } } } // The auxiliary method that calculates elevations for the "map." The parameters // to this method are the indices in the elevations array of the west (left), // north (top), east (right) and south (bottom) sides of the region to calculate // elevations for. This algorithm uses a recursive subdivision approach to assign // heights to the center of the region and the centers of its sides, and then // use those heights to recursively calculate heights within each quadrant. // // Precondition: The region is square, i.e., east-west = south-north. private void calculateElevations( int west, int north, int east, int south ) { final double scaleFactor = 0.25; // Scale factor for converting region sizes to // random offsets // There is nothing to do unless the region is more than 1 pixel high and wide. double regionSize = south - north; if ( regionSize > 1.0 ) { double diagonal = 1.414 * regionSize; int nsCenter = ( north + south ) / 2; int ewCenter = ( east + west ) / 2; elevations[nsCenter][west] = ( elevations[north][west] + elevations[south][west] ) / 2.0 + generator.nextGaussian() * regionSize * scaleFactor; elevations[nsCenter][east] = ( elevations[north][east] + elevations[south][east] ) / 2.0 + generator.nextGaussian() * regionSize * scaleFactor; elevations[north][ewCenter] = ( elevations[north][east] + elevations[north][west] ) / 2.0 + generator.nextGaussian() * regionSize * scaleFactor; elevations[south][ewCenter] = ( elevations[south][east] + elevations[south][west] ) / 2.0 + generator.nextGaussian() * regionSize * scaleFactor; elevations[nsCenter][ewCenter] = ( elevations[north][west] + elevations[north][east] + elevations[south][west] + elevations[south][east] ) / 4.0 + generator.nextGaussian() * diagonal * scaleFactor; this.calculateElevations( west, north, ewCenter, nsCenter ); this.calculateElevations( ewCenter, north, east, nsCenter ); this.calculateElevations( west, nsCenter, ewCenter, south ); this.calculateElevations( ewCenter, nsCenter, east, south ); } } }