Cloud "Cloud" is a program that generates and displays fractal surfaces. I had briefly looked at 'sc', but found that it was too slow to be interesting. I read about generating fractal surfaces and displaying them as clouds in the (technical) book "Fractals", by Jens Feder (ISBN 0-306-42851-2), section 13.4 "Random Addition Surfaces". So I thought I'd try it myself. What follows are some explanations of the program's user interface, and some of its workings. The clouds that look like these images are called "fracto-cumulus" by the way. Requirements: It requires about 200K to run - I've tried it on an A1000 with expansion memory turned off, and it works OK. Cloud brings up a custom lo-res screen (320x200). The screen is in two sections - the right 3/4 is where the image is displayed. On the left of the screen is a control panel with a bunch of gadgets. Most of these gadgets are grouped into three little panels. From the top of the window downwards, these buttons are: Standard Window Close Gadget: Exit Cloud by clicking on the window's close gadget. Auto - toggle to select the automatic generate and display loop. When "auto" is set, the program will loop between generating new images and displaying them. To stop, toggle "auto" back off. To pause, toggle "pause". Pause - toggle to pause while in the automatic loop. This pauses the program while in the automatic loop. You can then select other palettes. Save - this will let you save the current image as an IFF file, when it is implemented. ' ' - (there is an empty, unused gadget beneath Save) Rand - toggle to turn on the randomizer. This enables a section of code that adjusts the random number generator. If you don't turn this on, you will get the same sequence of images each time you run Cloud. Scroll - toggle to have the display scroll up from the bottom. When this is off, the new image fills in from the top of the screen downwards. When it is on, the screen is scrolled upwards, with the old image disappearing off the top, and the new image appearing on the bottom. It adds "lightning" to the clouds. Gen - to generate and display a new image. This generates a new image, and displays it. Re-Do - to re-display the present image. This displays the current image again, which is useful to see the effects the different scalings can have on the same image. "d = XX %" - place to enter the delta percent value. This string gadget accepts a two digit number whose value is used in generating new images. The gadget hit box surrounds the digits. The value determines the smoothness of the image. For clouds, a good value is 80%, for terrain and water, 70% is better. Lower and higher values are also interesting, if less natural. Log - selects a logarithmic scaling, Lin - selects a linear scaling, Exp - selects an exponential scaling, Histo - selects an equal area scaling between data and colors. You choose one of these four buttons to select the type of scaling performed to map the image data to the screen. There's more on this later. Color Bar - shows the current image palette. This isn't a gadget, it just shows you what the palette is. Reverse - flips the color palette end for end. By flipping the palette over, the image seems to reverse; dark things become light, and light sections become dark. Atmos - selects the "cloud in the sky" color palette, Earth - selects the Rand McNally map palette, Water - selects an "islands in oceans" palette, Therm - selects a palette that is reminiscient of thermal images. Choose one of these four buttons to select the color palette for displaying the image. These are active at all times, so that while generating the next image, you can fiddle with the one currently on the screen. ------------------------------------------------- About the color palettes (or do you say palette?): The following table lists the color trends in each of the palettes. Color range Atmos Earth Water Therm ----- ----- ----- ----- HI white near white light green red ^ ... dark brown sand orange | lighter blue light brown blue-green yellow | ... green light blue green v lighter blue sand blue blue LO blue lake blue deep blue dark blue Data means: density height depth temperature If you're running Workbench 1.3, you can use the Palette command to change Cloud's colors. Put the Cloud screen in the front, pull it down to expose a CLI window or the drawer containing the "Palette" command, and invoke "Palette". You can then change Cloud's colors temporarily - the color changes are not remembered by Cloud. Also, there will be a white rectangle left over from where the Palette window was. Just click on the Re-Do gadget to redraw the image. ----------------------------- About the scaling selections: The data can be mapped to the colors in many ways - I've chosen 4: linear, log, exponential, and equal area. Each has its own good and bad points. The linear scaling makes a hazy cloud, but it leaves the Earth landscape flat and boring. This is because the majority of the data values get mapped to the middle range of the palette - so we don't see much of the peaks and pits. The log scaling tends to expand the lower data values over a larger color range and compress the higher values into the higher colors. It emphasizes the upper data values at the expense of the lower ones. It makes the sky seem even more hazy. Exponential scaling is the reverse of log scaling. The higher data values will be spread across a greater color range, as compared with linear scaling, and the lower data values will be compressed into the lowest color(s). It emphasizes the lower data values at the expense of the upper ones. The sky becomes more blue with this setting. The equal-area scaling uses a histogram to find out the distribution of the data values. It then maps the data values to colors such that each color occurs as often as every other color. It seems to reveal lots of detail, and so it makes Therm and Water busy looking, but it makes Atmos look better. It emphasizes the extremes of the data, both upper and lower, at the expense of the mid-range values. The sky looks most natural with this setting - patches of blue sky and patches of white clouds with quick transitions between. ----------------------------- About the scaling algorithms: LINEAR scaling maps the data values to palette color positions (pen numbers) with the equation: Delta-C C = (V - VMin) * ------- + LoColor Delta-V This divides the range VMin to VMax into NColor sections (where NColor = HiColor - LoColor + 1). Actually, instead of doing that equation over 66000 times, I build a color lookup table, from the minimum data value to the maximum data value, and use that equation NColor (13) times to fill the table. LOGARITHMIC scaling finds the multiplicative factor such that: NColor VMax = VMin * X This will divide the range VMin to VMax into NColor sections, each of which is X times larger than the previous. X can be found by: (1 / NColor) X = (VMax/VMin) The EXPONENTIAL scaling uses the logarithmic routine to first build the color table for the logarithmic scaling. Then it flips the table around end-for-end, and top-to-bottom. This changes the up-and-over logarithmic curve to the over-and-up exponential curve. HISTOGRAM scaling produces an equal-area scaling, where the range VMin to VMax is divided into NColor sections, each of which has the same number of image points within it. To do this, a histogram is made from the image data. The histogram is a table that represents how many data points occur for each data value, over all the data values (from VMin to VMax), i.e. how many data elements are at a certain height for all heights. (Typically, the histogram looks like the infamous "bell-shaped" curve used by school teachers to place exam grades "on the curve". This is that curve.) Next, the histogram is summed along its values - changing the bell-curve into an stretched out S-curve: - ---- - - - -- - - - --> -- - --- --- ---- - Given this final table, we can divide the vertical span of the table into NColor sections. Working towards the graph line and then down to the baseline, we find the NColor data values that divide the original image into NColor equal-area pieces. From this information, we build the color lookup table. Note that in all of these scaling algorithms, it is much quicker at run time to build a 1024 element table once, than it is to repeatedly perform the same calculation over 66000 times. The cost is in memory use: an extra 1KB for the lookup table and 4KB for the histogram seem worth it. The only routine that uses floating point arithmetic is the Logarithmic scaling routine. It uses the "pow()" function and floating point multiplication and floating-point to integer conversions. Roughly two dozen explicit floating-point operations are needed. All other routines throughout the program use integer arithmetic. -------------------------------- The central algorithm for Cloud: (This is mostly a historical account of how the generator evolved, as well as a summary for myself...:-) Assumptions: - the generated image will be square, size of 2^N + 1 (257 x 257) - x axis runs left-right, y axis goes up-down Data generation: - there are N iterations - each iteration has two parts - adding random values to elements of the array, - generating new elements by averaging between the old ones - during each iteration the "step" between elements is halved - between iterations the range of the random value is decreased - we're finished when the step size becomes 1 - the initial step size is 2^N (first iteration does the 4 corners) Data display: - the max and min are found - one of four routines are used to fill a Color LookUp table - each point in the array is translated by the color lookup table into a pen color which is written to the respective screen pixel ------------------- Generation example: ------------- NUMBER OF ELEMENTS ----------- Iteration Step Add random Average new Total points 1 N (256) 4 5 9 (3x3) 2 N/2 (128) 9 16 25 (5x5) 3 N/4 ( 64) (9x9) 4 N/8 ( 32) (17x17) 5 N/16 ( 16) (33x33) 6 N/32 ( 8) (65x65) 7 N/64 ( 4) (129x129) 8 N/128 ( 2) (257x257) --------------------- Generation algorithm: The first 4 elements: Generate 5 more, to make 9: (256 pts apart) (now 128 pts apart) o o o - o | X | o o o - o Next iteration: Take 9 elements: Generate 16 more, to make 25: (128 pts apart) (now 64 pts apart) o o o o - o - o | X | X | o o o o - o - o | X | X | o o o o - o - o At each step, the character shows what to do at that element: "o" is an old element, on the first pass we add a random value to it, then on the second pass we use it to generate the new elements "-" is a new element, it is generated by averaging the left and right neighbors "|" is a new element, generated by averaging the top and bottom neighbors "X" is a new element, generated by averaging the neighbors in the surrounding corners The initial algorithm went like this: randrange = rrmax stepsize = MAX while ( stepsize > 1 ) do /* first elevate the random points */ for y in the set {0, stepsize, 2*stepsize,... MAX} for x in the set {0, stepsize, 2*stepsize,... MAX} data[x,y] += random_value end x end y /* now, adjust the stepsize, and generate the new points */ stepsize /= 2 for y in the set {0, stepsize, 2*stepsize,... MAX} if ( y is "odd" ) /* generate the "| X | X |..." averages */ for x in the set {0, 2*stepsize, 4*stepsize,... MAX"} data[x,y] = ( data[x,y-1 ] + data[x, y+1] ) / 2 /* do |'s */ for x in the set {stepsize, 3*stepsize,... (MAX)} data[x,y] = ( data[x-1,y-1] + data[x-1,y+1] + /* do X's */ data[x+1,y-1] + data[x+1,y+1] ) / 4 else /* y is "even", generate "o - o - o ..." */ for x in the set {stepsize, 3*stepsize,... (MAX)} data[x,y] = ( data[x-1,y] + data[x+1,y] ) / 2 end y randrange = randrange * fraction done In the present program, all array indexing is avoided, and the core algorithm is implemented differently. One problem with the above algorithm is that each old element is accessed 8 times when generating new elements. You can see this by looking at the general pattern around an old element: X | X - o - <-- this `o' gets bothered 8 times! X | X We can take another look and use a different pattern for our core routine. Instead of having different actions for each row, we can treat the array as a set of "cells": ... o - o ... Or, giving them names: NW N NE ... | X | W C E ... o - o ... SW S SE where C is the center of the cell, and the other corners and sides are named after the compass points. We can save a lot of unnecessary fetches by realizing that the East side of the current cell is the West side of the next cell. In this way, the old elements are fetched only twice apiece - once for the cells below them, and once for the cells above them. Our computational cell is: Generate these: From these: (NW) NE => (NW) NE (NW) NE W C E W C E C E (SW) S SE => (SW) S SE S (SW) SE current ----> next From the last cell (to the left), we have NW and SW. For this current cell we fetch NE and SE. We compute S, C, and E, and store them. To prepare for the next cell (to the right), we update NW from NE, and SW from SE. We then move our pointers to go to the next cell. Now the core algorithm becomes: Generate the first row of "o - o - ... o" separately. Get pointers to W and C. Pick up NW, SW; average them and store at W. Shift the W pointer to now point to E. For all the rows: For the cells in this row: Pick up NE, SE. Generate the various averages, and store at S, C, E. S = (SE + SW)/2 E = (NE + SE)/2 C = (NE + SE + SW + NW)/4 Shift SE to SW, shift NE to NW. Shift C pointer to next C (to the right). Shift E pointer to next E (to the right). There is one final trick - we can see that C can be generated in many ways, and one way is: C = (E + W)/2. We can adjust the algorithm to remember W and SW, instead of NW and SW. The advantage is there's slightly less bother when indexing off the pointers. Also, I use two times W, i.e. before it is halved. This way there is less problem of truncation in computing element C. The inner core equations finally become: Pick up NE, SE Form E2 = NE + SE Store E = E2/2 Store C = (W2 + E2)/4 Store S = (SE + SW)/2 Update SW from SE Update W2 from E2 Update C and E pointers And that's why you don't have to wait very long for a new image. --------------------------- About that "delta percent": The integer gadget lets you change the amount that the random numbers influence the evolving surface. In the initial algorithm given above, the range of the random numbers was changed at each iteration by the statement: randrange = randrange * fraction The value of "delta percent" gets turned into `fraction'. What it means is that at each iteration, the range of random numbers is decreased by "delta percent". If it is 80% (default), then the range of random numbers decreases like this: 100, 80, 64, 51, 41, 33, 26, 21 (for 8 iterations). With higher values, the resulting image is "bumpier"; with lower values it is smoother. Lower values are useful to help you get a feel for how the scaling types affect the image. I find that the images are too noisy with values above 80, and have a nice smoothness with values around 50. Below 40 the images become too simple to be interesting in themselves. ---------------- Some last words: I hope you like 'Cloud' as much as I do. It was fun to watch it grow and evolve. Bugs, problem reports, and suggestions for improvements can be sent to: Mail: Mike Hall 2 S 461 Cherice Dr. Warrenville, IL 60555 UUCP: att!cuuxb!migh Thanks!