Landscape Applet

Originally, this Applet was intended to have been an illustration of cellular automata, however, it quickly came to adhere to that description only very loosely, with some elements straying quite a distance. The basic premise is that the growth and spread of plants can be simulated by setting some very basic rules and turning the computer loose.

Choosing the Terrain:

Before the fun could begin, an appropriate environment had to be created. I chose to let the user determine the shape of the terrain, by manipulating twenty-five scrollbars on the start-up screen. A 6-by-4 array corresponds to control points on the landscape, while a single bar to the left determines water level. Initially, all of the scrollbars are set to zero, with possible ranges of -40 to 20. Since scrollbars have return higher values when the indicator appears in a lower position, which is counter-intuitive when you are manipulating heights, I invert the values before using them, yielding an effective range of -20 to 40.

After the control point heights are chosen, there is no need to clutter up the screen with these scrollbars. Therefore, a CardLayout was used (many thanks to David Anson for the help he gave me with this.) CardLayouts allow panels to occupy the same space with one being shown at a time and the rest being hidden. This applet uses three cards, the first being the scrollbar array. To switch cards, all that needs to be done is to override handleEvent so that pushing the appropriate button "shows" the next card.

Another task for handleEvent is to construct the array of heights from the scrollbar positions. All scrollbars begin set to 0, which is also the value that the heights array elements are initialized to. HandleEvent changes the corresponding element of the heights array whenever a scrollbar is manipulated. Because I originally did not know what size array would be practical and aesthetically pleasing, the array elements corresponding to the scrollbars occur at (2*D)*(the location of the scrollbar), where D (for delta) is a constant I can quickly redefine for best effect. The size of the array then becomes (6-1)*(2*D)+1 by (4-1)*(2*D)+1 for the 6 by 4 array of scrollbars. After some experimentation, I decided upon D = 20.

Next, the number of control points is artificially increased by generating new points midway between the user-defined ones. These come from randomly perturbing the average of the control points on either side, first in the x-direction, then in the y-direction. Since the user is given the opportunity to re-use this landscape, I didn't want this perturbation to change, so I made my own "random" numbers by using the modulus of strange functions of the location. At this stage, the array of heights contains data at every Dth element.

Finally, to achieve the effect of rolling hills, the heights between control points needed to be filled in. The algorithm I wrote to fill in these heights minimizes the change in the derivative at the control points, while propagating most of the change in height and some irregularity. Essentially, the midpoints between all the control points are used, and a progressive interpolation fills in the intermediate elements. In the accompanying figure, the red dots represent the control points before any interpolation occurs. The blue then, shows the results of the algorithm. This illustration uses D = 4 for simplicity. First, the missing elements are filled in in one direction, then the other. One unfortunate side effect of this is that the isometric lines run parallel to the x- and y-axes too frequently, though this is only apparent at the water line.

Once the full array of heights (180,000 elements) is completed, the slopes in each direction are computed, and from these the normals are obtained, which are then dotted with an arbitrary vector to determine shading. Using a short distance in the slope calculations resulted in a very digital-looking landscape, so a larger distance was used (10 height points) since the interpolation method insures that there won't be any high frequencies anyway. The drawback is that the mostly flat areas tend to "band" pronouncedly, but I feel the smoothness of the hilly areas easily justifies this. Also, the banding is sometimes reminiscent of the checkered patterns of farmland, which might be interpreted as a good feature by a very sympathetic eye.

Seeding the Map:

All of this array construction occurs when handleEvent must deal with the DONE WITH HEIGHTS button being pushed. When all the landscape arrays have been filled in, the next card is shown, on which a map is drawn for the user to place "seeds" on. The map itself is just an overhead view of the landscape with lighter areas signifying greater height, drawn to fill a canvas. Made in the "map" instance of the DrawCanvas class, the map is passed the necessary variables when the panels are first constructed.

Also on the second card, four buttons represent the four plant types, and dots of four colors are drawn on the map when it is clicked on. At the same time, these seeds are entered into an array (also 180,000 elements) according to location. There is no test here to see if that location has already been seeded, and only one plant may occupy a position in the array. In this seeding algorithm, if more than one seed is planted in the exact same spot, the one with the higher plant number (the one with the lowest button) gets to stay.

Here, the main class' handleEvent is responsible for setting the seed type when the buttons are pushed, this information becomes available immediately to map (seed type is passed by reference), and map's handleEvent adds the seed's location to a list when the canvas is clicked on. Map's paint method is responsible for drawing the seeds in the lists and also for making the entries into the plant array.

The Growing Landscape:

After the user finishes seeding, the results are displayed: a third card shows a landscape from the side and slightly above, with plants growing and spreading across its surface. From almost the beginning, I had decided that the plants would not exist in 3-space, but rather would just be a collection of flat pictures. This dictated that a set view be used, which in turn made drawing the landscape very simple: a quick 3D to 2D transformation set the screen positions which were colored using a function of the shading array entries. Each colored block extends downward for six pixels, so that hills with high slopes aren't see-through. Water does not have this thickness since its slope is zero. The landscape is drawn from back to front so that the objects obstruct the view properly.

Initially, the plants on the landscape are only the ones put there by the user, and these are very small. Then the landscape is redrawn repeatedly, with the behavior of the plants determined by these initial conditions and their governing rules:

Reedy plants: These are partial to marshy conditions and tend to grow and spread quickly (note how quickly they have spread on this young landscape - though they are difficult to see here: look for the yellowish glow). A reed planted by the user will survive only if it's planted in the height range of 3 units below to 10 units above water level. If a location is unoccupied and in the range 2 units below to 5 units above water level, there is a 40% chance that a reed will sprout there if any of its eight neighbors is a reed. A reed gets taller (five possible sizes) if its neighbors combined heights do not exceed set thresholds. An alteration I would like to make regarding reedy plants is to pseudo-randomly perturb their locations slightly so that they don't align themselves on the landscape as they currently do.

Bushy plants: These like conditions with better drainage and tend to grow and spread more slowly. A bush planted by the user will survive only if it's planted at least 6 units above water level. It then grows steadily (nine possible sizes). Mature, but not old, bushes reproduce: In the two sizes just below the maximum, a bush attempts to seed a spot randomly chosen within 5 units of itself. If that spot is suitable, and unoccupied, and if all its neighbors are unoccupied, there is a 50% chance that the seed will take. In this picture, you can see the first descendents of the original bushes

Creeper plants: These ivy-like plants are the most like the cellular automata programmed in CS418. They survive anywhere above water level. Unlike the other varieties, creepers do not grow, they merely spread. If a location is unoccupied and above water level, there is a 40% chance that the creeper will spread there if any of its eight neighbors is a creeper, unless a reed tries to spread to that spot on the same turn. If these conditions are met, but the creeper does not spread to the spot, a flag is set and it doesn't get a second opportunity. Were it not for this flag, the creepers would have nearly taken over by the time this picture was taken. The creepers look a bit boxy in appearance now, which also should be fixed.

Flowering plants: These have the most interesting life cycle, in that they are the only plant in this simulation that dies even under the best conditions. These flowers can grow anywhere out of the mud (at least 2 units above water level), and start off as little green shoots. They next grow slightly larger, and remain in this state for a random amount of time. When they next change, it is to grow buds. The flowers bloom in the next iteration, and in the following step, the petals have fallen to the ground. The plant is dead, and disappears the next time through the loop. In the same step, however, five seeds were scattered randomly within 5 units of the dying plant. Like with bushes, that spot must be suitable, and unoccupied, for the seed to take. The spatial demands are less severe, however: the spot can have neighbors as long as their cumulative size does not exceed a threshold. As long as these conditions are met, there is a 50% chance that the seed will take. Because of the lifecycle, it is possible for flowers to be pushed out of the lower part of their range by reeds. The other plants restrain them too, but less so: creepers don't occupy every location in their range, and don't have the size necessary to inhibit the seeding of neighboring spots, and bushes require more altitude, reproduce slower, and require more space. Late in this landscape's development, the flowers have spread so much at higher elevations that the bushes are no longer finding empty spaces to spread to. I am currently unsatisfied with the appearance of the flowering plants as well - they are badly scaled - and I hope to improve their looks.

This picture was taken late enough in the landscape's development, that it is at an equilibrium. The reeds and creepers have spread as far as they are going to - the reeds due to having filled their habitat, and the creepers because of the limitations of the "visited" constraint. The bushes no longer enjoy the space they require to reproduce, and the flowers have spread to the extent that they now have about enough room on average for about one seed per plant to take.

All of the work in this third card takes place in the paint method of the class PicCanvas, with instance "pic" central to this applet. Like map, pic is passed all relevant variables by reference from the main program. From these, the paint method draws the landscape, back to front, based upon the heights array. Simultaneously, it checks the plants array and adds any plants that should be present. As each point is visited, the contents of the plants array is updated according to the rules outlined above. The bulk of the code then for pic's paint is conditionals for updating the plants array, and bitmaps for drawing the plants. To "match" the plants to the landscape effectively. The colors used in the bitmaps are functions of the shading associated with the landscape at the point the plant is growing on.

What Still Needs to be Done:

Currently, this applet's biggest problem is one of platform independence. This applet does everything from run perfectly on Internet Explorer and Netscape Navigator 3.01 on the CSUGLAB's NT's, to cause a core dump on the HP's which run Navigator 2.01 ten feet away. On the Power Macs running Navigator 3.0 in the basement of Upson, it works correctly, but the interface looks completely different and sometimes it is necessary to go back and forth between pages to get the applet to initialize. Some of the platform problems involving the interface were easy to correct, like the sizing difficulties involving the scrollbars, but others, like the erratic card-hopping on some machines, were trickier. Why 2.01 on the HP's collapses halfway through the applet is still a mystery to me. Also, the reinitialization of the applet when it is left and returned to needs to be improved.

A second problem this applet has is that it does not allow the final stage to be interrupted so that the process can be re-initiated at any time. My solution at present is to loop the growth algorithm eight times, at the end of which the user can restart the applet from scratch or continue growing the plants for another eight cycles. This is not too inconvenient, but it is a hack which should be replaced with real code.

As for the applet's appearance when it is running properly: two issues need attention. First, the applet looks terrible when it has to run without a whole slew of colors. Perhaps I should design a second version that looks better with a more limited palette. Secondly, the redrawing of the landscape for each iteration detracts from the visual effect of plants growing and spreading. Buffering the graphics should be able to cure this problem.

Despite these problems, I hope you enjoy using this applet. I had a lot of fun writing it after I got through the usual frustrations of trying to deal with a new language. Hopefully, I'll be able to cure some of what ails it soon.

The code is available if you would like to view it.