FAST 3-D LANDSCAPES A Bizarre Freeware Program For the 64K ZX/TS with 8K SRAM By Fred Nachbaur INTRODUCTION F-SCAPES is a menu-driven "toy" program for the ZX81. By "toy" I mean that it has no immediate application other than sheer enjoyment. Well, OK; it may have educational potential, and might even form the basis for an actual application. But my sole purpose in writing the program was to provide a piece of "entertainment software" unlike the usual arcade or adventure games. F-SCAPES lives in that ill-defined twilight zone between art and mathematics. It is heavily mathematical (but don't panic, you don't have to be a math wiz to understand it), yet its products are "landscapes" of striking beauty (don't panic, you don't need a degree in Art Appreciation to enjoy it). Surely you've seen those computer-generated 3-D line drawings of imaginary alien landscapes; they are showing up everywhere, it seems. Well here they are on your faithful old doorstopper. Can you think of a more useless program? If so, don't tell me; I'd probably write it. Being far beyond the point of having to try to "compete" with anyone, and leaving the boring old mundane things to Mr. I.B.M. Clone, we can afford to engage in flights of fancy on a toy computer that the gurus of yore disparaged, and the experts of today have long forgotten. HARDWARE REQUIREMENTS The hardware requirements for running this program are only a little more than my previous "commercial" attempts, in which I tried to cater to the unfortunates with only 16K of user RAM. You will need a ZX81 (or TS1500) with "64K" RAM. By this I mean that you have to have RAM from 16K to 64K. You also have to have 8K of static RAM, mapped 8-16K, for the high-res display file and associated data. This RAM must be accessible during "refresh time." In other words, the RD* line to the CMOS RAM chips has to be ANDed with the CPU RFSH* line. If you've successfully run any of the SRAM HI*RES software written by Gregory C. Harder, myself, and others, and have 48K of usable "User RAM," you're all set to go. "M1* decoding" is not required, but won't get in the way if you have it. If the preceeding has been pure gibberish, all I can suggest is that you contact your nearest user group, in the hopes of locating a Z-Xpert to help you out. There have been so many commercial products and "roll your own" magazine projects that I can't begin to enumerate all the options. If you write, I'll do what I can to help, but please understand that I, like you, am just a hobbyist. I simply can't afford to try to make a sideline, let alone a living, on your (and my) favourite computer. But I CAN afford to spend my spare evenings at the ol' ZX, doing bizarre things to be shared with my friends. How, you ask, did this one come about? Well, ... TELL YOU A STORY ... The "main engine" behind F-SCAPES is actually a simple idea. Perhaps it has been published elsewhere. Perhaps I'm advertising my lack of knowledge about the general computing scene by proposing it as a "new" idea. If so, perhaps some future historian will get a chuckle out of this manuscript. If so, great. As I said before, my main goal here is to entertain. It all started a long time ago, in a city far, far away. How would one write a program to make lovely sinusoidal 3-D "landscapes" of pure mathematics? How to add cracks and crevasses (as Tommy Smothers would put it) to make things interesting? Let's make the game a little more challenging; how would one do it on a ZX81? While we're fantasizing impossibilities, what could be done to make the whole thing take place in less than a week? The idea sprouted, and began to take root. Eventually it became a fungus on the brain, resurfacing periodically like a stubborn fairy ring on the lawn of consciousness. One very fine day in the exquisite city of Ottawa, far far away from the loony-bin I call home, after a challenging session on the lovely NEC IBM clone at the offices of a friendly Canadian Artists' lobby group, during a stuffy but interesting Ontario heat wave, I bought a Taiwanese lighter. Now, this lighter was not your ordinary old boring BIC. It was a king among lighters, a lighter any true computer nut would be proud to own. It had emblazoned upon its elegant black exterior, in canary yellow, one hell of a fancy computer graphic. Swoops and curls and hidden lines and all. It was even cheaper than its brand-name compadres. That did it. It those guys can do it, I'll be a monkey's second cousin if it can't be done on a ZX81. As it turns out, I'm still a monkey's second cousin, but true to form, the ZX81 continues to ape the "other guys." (No names mentioned, the names seem to change faster than I change my socks.) The result of this inanity is a program that makes pretty 3-D math landscapes, is menu-driven with overlapping multi- font menus (please don't sue me, Mr. Tramiel), and has a 3- D Mandelbrot-set routine to boot. Not to mention view options and do-dads and all the other junk that makes people woo and wow their favourite plate of DIPs and chips. Now my only chore is to try to explain it all. Wish me luck. THE SCOPE-SCAPE The core of this program was based on the properties of electric fields, and gives a view, or "scope" into the nature of such fields. The results are a bit suggestive of a 3-D oscilloscope, or "'scope." The routine is a near- perfect model for the study of electrostatics. After experimenting with it, however, I realized that it has a much broader "scope" than simple electrostatics. Hence the name, "Scope-scape." Imagine a two-dimensional plane, which we will call the "Generator Plane." At the centre of this plane is a point- source of positive electric potential, which we'll call a "Generator." Now imagine that we have a sensitive electric field- strength probe. By moving the probe around near our point- source in the generator plane, we can get readings of its field at any point. Let's now visualize a second plane, above and parallel to the Generator Plane. We'll call this the "Effect Plane." Let's scan our field-strength probe over a portion of this plane (plane-segment), taking readings at every point within the plane-segment. The measured readings are recorded; the table of readings are stored in a table called the "Scope map." These values are subsequently used to draw either 2-D topographical maps, or 3-D view maps, showing the effect of the generator on the "Effect Plane." Figure 1 shows the concept behind the Scope-scape. How are the values in the Scope map calculated? Good question. If you will recall your college Physics, you might remember that the field strength generated by a point electric source is "inversely proportional to the square of the distance." Put in everyday terms, imagine that we're taking a field-strength reading one unit of distance from a point source. If we double the distance, the new field strength will be 1/4 of the first reading. At three times the distance, field strength will be 1/9 of the first reading, and so on. To put it yet another way, FS=K/(D**2) where K is a constant of proportionality. More about "K" later. How would we calculate the distance, D? Easy. Remember Pythagoras and his ubiquitous theorem? Let X and Y be the co-ordinate in the Effect plane that we are calculating. Let Z represent the distance between the generator plane and the effect plane. Let Xg and Yg be the co-ordinates of the generator. The distance from the generator to the point under study would be, D = SQRT( (X-Xg)^2 + (Y-Yg)^2 + Z^2 ) Note that, since field strength is inversely proportional to the SQUARE of the distance, or D^2, there is no need to evaluate the square root; we save a lot of time by using D^2= (X-Xg)^2 + (Y-Yg)^2 + Z^2 We then divide the value of D^2 into constant K. In this program, K is assigned the value of Z^2. This is to "normalize" the results for varying values of inter-plane distance Z. Regardless of the value of Z, the maximum value in the effect plane will always be directly above the generator, i.e. when the distance D is minimum. By using Z^2 as the proportionality constant, this maximum value will equal the value (size) of the generator. What if the generator has a negative value, representing (for example) a negative charge? No problem. The resulting values in the Effect Plane will simply be inverted. Instead of the "bump" created by a positive generator, we will obtain a "dip" from a negative generator. MULTIPLE GENERATORS What if we have more than one generator in the Generator plane? In such a case, we borrow the principle of "superposition" from electronics. At every point in the effect plane, we compute the effect of EACH generator, and algebraically add them together. The scope-scape idea is also an exact analog for magnetic fields. If only positive values are used for the generators, it also provides an analog for gravitational fields, light intensity, and a wide variety of other phenomena which follow the "inverse square law." THE SCOPE-SCAPE ALGORITHM Here is a "pseudo-code" summary of how the scope-scape routine works: INPUT N :# of GENERATORS FOR I=1 TO N INPUT X(N),Y(N),V(N) :Input generator X, Y & value NEXT I INPUT Z distance K=Z**2 :calculate K, =Z^2 DIM P(XM,YM) :Dimension map array FOR Y=0 TO YM FOR X=0 TO XM TOTAL=0 FOR I=1 TO N VAL=K+(X-X(I))^2+(Y-Y(I))^2 TOTAL=TOTAL+VAL NEXT I P(X,Y)=TOTAL NEXT X NEXT Y In simple English: For every point in the effect plane, sum the effects of each of the generators in the generator plane. Store the superposed value in map array P. ARITHMETIC IN F-SCAPES The Scope-scape routine, and indeed all the major map and display routines, are written in machine-code. However, no less than THREE different kinds of arithmetic are used, depending on accuracy requirements. The first is ordinary Z80 8-bit math. This is used for counters and such, where we only need values from 0 to 255 (or -128 to +127). Where more range is needed, 16-bit math is used. Such numbers can have a range from 0 to 65535 (positive 16-bit) or -32768 to +32767 (signed 16-bit). A collection of math routines (multiply, divide, etc.) are used to handle such operations. These routines were originally found in Bob Berch's runtime package for his "Compiler." The third type of math is Sinclair floating-point. It has the widest range, and therefore the best precision, but it is also the slowest. Some speed improvement is afforded even in this case, by coding directly in the Sinclair's "calculator language" instead of BASIC. These three types of math are used as the situation dictates. For instance, 8-bit math is used for the loop counters, simple comparisons, etc. 16-bit math is used to compute the square of the distance from the given point to the given generator. Floating-point math is used to calculate and sum the net effect of each generator. This approach pervades F-SCAPES. This is how I could optimize speed without compromising precision. Only as much precision as is really needed, is used in these routines. THE MAP ARRAY To allow the program to run in 48K, and give the maximum possible resolution, a string array (instead of a numeric array) is used to store the map values. This is P$(99,256). The first 96 dimensions represent 96 vertical lines. The second dimension represents 128 horizontal positions on each line, two bytes each. (I.e. the map values are stored as signed 16-bit numbers.) The last three dimensions (97, 98 and 99) are used for the generator X position, Y position, and value (magnitude), respectively. These are also 16-bit signed numbers, so it follows that 128 is the maximum number of generators allowed. By including the generators in the same map as the scope map they create, I considered owners of Z-XLR8 and similar "array-save" routines. Dimensioning this array is done as a separate operation in the MAP menu. The reason is so that you can superpose scope-scapes from two or more different generator sets onto a single map. You could even superpose a scope-scape onto a 3-D "Mu-map" (Mandelbrot set). Other possibilies exist, and are entirely up to your imagination. SETUP Hook up your computer with at least 48K of "User RAM." This can be any of the following: ZX81 + 64K RAM pack ZX81 + 32K Memotech + 16K TS1500 + Oliger 64K (or other compatible, if any exist) "Home-brew" dynamic or static RAM from 4000h-FFFFh Also have 8K of refresh-accessible RAM in the 8-16K region. This can be a modified Hunter board, SCRAM board, "Built-in NVM", or other home-brew. If you are using a 64K RAM, disable the 8-16K region to prevent conflicts. Also disable any other memory-mapped peripherals in this region (EPROMs, etc.) Perhaps the ideal hardware for running this program is a ZX81/TS1000 with Tim Stoddard's built-in 64K static RAM. It is really something to see what looks like a stock TS1000 running this program (or others, like ZX-PAINT, ZX-TERM*80, and so on). Geez, what we couldn't have done if... if... if.... LOADING THE PROGRAM Before proceeding, be sure your 8K SRAM is enabled. POKE 16389,255 then NEW, to "open up" your RAMTOP all the way. (There is just enough headroom for Compusa disk or A&J stringy-floppy users to store directory; minimum recommended value for 16389 is 245.) The program loads in two parts. The first only installs the lower-case character set, and the sprite characters used for the generator map. After the first part has been loaded, and the character data is installed, you're prompted for "LOAD NAME?". Enter "F-SCAPES" (the quotes will already be there), or simply press ENTER. The program itself will now load. Go do something else while loading proceeds; it takes quite a while (about 8 minutes). When loaded, the Home Menu will appear. MAKING BACKUPS Back up the character set loader by pressing EDIT (shift 1), then STOP (shift A) then GOTO 2. Start the recorder and enter "CHARS" in response to "SAVE NAME?" Let the tape run for a few seconds after the program has saved. Backup part 2 (the program itself) by loading as described above. From the Home Menu, press [F]ile, then [S]ave, then [A]ll. Start the recorder and enter "F-SCAPES" for "SAVE NAME?" THE MENUS The program is entirely menu-driven. I think you'll like the overall feel that this approach lends to the program. Sub-menus partially over-print the "parent" menu, so you can see where you came from as well as where you're going. Whenever a menu is displayed, the program is waiting for input (a keypress). The selections are made by pressing the first letter of the desired option. This is indicated in this document by brackets; e.g. "[H]ome" means "press H to get to the Home Menu." Each menu has a distinctive "look." After awhile, you'll know which menu you're in right away, before you even read the menu name. Now why didn't the big boys ever think of that? The menus are stored in pre-defined arrays, and cannot be changed. For this reason, I added a menu, Tools, that does absolutely nothing as supplied. This is dedicated for whatever routines or options you may wish to add. A custom keyboard routine is used to economize memory and improve speed, and to help prevent problems due to pressing the BREAK key. However, there are other parts of the program during which the BREAK key will stop execution. If this happens, the best thing you can do is enter CONT. Otherwise your menus might be messed up. If you really want to stop, use the [Q]uit option from the Home Menu. Some menu options require user input (numbers, filenames, etc.) In this case, a prompt will appear in a box in the upper-left corner, and the usual "L" cursor will appear in the lower left. The prompt box will usually indicate the "present value" (PV) of the variable being inputted. If you wish to retain this present value, press ENTER (only). If you wish to change it, type the new value before pressing ENTER. The upper-left corner also displays info messages, such telling you to "please wait" or informing you that a specified action has taken place. After two seconds, the message will erase itself and the program will proceed; however, if you don't want to wait the full two seconds, press a key to terminate the message. If you're doing a potentially dangerous (or time-consuming) operation, you are asked, "Are you sure?" If you want to proceed, press [Y]. Otherwise press [N] or any other key except [Y]. (This feature can be toggled on or off using the [V]erify option in the Set Menu.) You can always get back to the "parent menu" by pressing [Enter], or any "invalid" key except [SPACE/BREAK]. THE HOME MENU The "Home Menu" is, as the name implies, the most basic menu level. From the Home Menu you have the following options, each (except [Q]uit) representing a sub-menu: Map (Create/manipulate/destroy map array) File (Tape operations) View (Display options) Draw (Create displays from map) Set (Change parameters) Tools (User-installable options) Quit (Stop & return to BASIC) QUIT This is the safe way to exit the program, to make changes, look around, or quit for good. If desired, restart the program after [Q]uit by entering GOTO H ("go to home") or CONT (continue). THE VIEW MENU The [V]iew menu has to do with the image you see on the screen. Pressing [V]iew from the Home Menu will erase the Home Menu, so that you can see the full display. At this point, you have three options, which are (for obvious reasons) not "menu'ed:" [H] - return directly to Home Menu [C] - clear the screen [Any other key] (e.g. [Enter]) - display View Menu. From the View Menu you can do a number of interesting things: VIEW [V]iew simply erases the View Menu, and waits for [H], [C] or [other key] as described above. [H]ome [H]ome returns to Home Menu. [C]lear This clears the screen. If the [V]erify option is on (as it is on initial loading), you'll be asked, "Are you sure?" Clearing the screen is almost always an "intentional" operation. Only when actually creating a new map is the screen automatically erased. [N]egative [N]egative does a "video invert." Everything (including menus, prompts, etc.) will be reversed. This is a toggle; if you don't like white-on-black, hit [N]egative again to return to black-on-white. [N]egative will NOT change how your display is sent to the printer using the [P]rint option. [I]nvert This inverts the current display (only). Overall "system" mode stays the same (i.e. menus, etc.). [I]nvert, like [N]egative, is a toggle. [I]nvert physically reverses the contents of the high-res display file; it therefore will send the same image to the printer as you see on the screen. The best way to get the difference between [N]egative and [I]nvert is to experiment with the options. [P]rint This sends the current screen to the TS2040 printer. Be sure printer is turned on. Also see comments above. [B]order - draws a cute border around your picture. There are actually two kinds of borders you can draw using this command. To draw the "alternate" kind, first [I]nvert your picture, [B]order, and [I]nvert again. [M]irror - makes a mirror image of your screen. After pressing this option, you may either make a [V]ertical or [H]orizontal mirror-image. Remember, pressing any key other than the indicated options (such as [Enter]) aborts the current operation and returns to the parent menu. [S]ave Save the current screen (only) to tape. As supplied, this will save the screen in the same format as is used by ZX- PAINT, to make it easy to import/export screens between these two programs. This is similar to a Sinclair save, except that the file is "nameless." If you selected [Q]uick from the Set Menu, the screen save will be done in the F- SAVE format instead, and you'll be prompted for a name. In this case, the entire 8-16K region is saved (including character definitions, etc.), instead of just the 8-14K picture area. See the discussion in the "File Menu" portion of this document. [L]oad This loads a screen from tape. As with the [S]ave option, this will be in the "normal" speed mode compatible with ZX- PAINT. Also as with the [S]ave option, the loading protocol can be changed using the [Q]uick option in the Set Menu. [T]race This enters SHR-EB V3's "Trace" command. This lets you fix up your picture manually, and also lets you get the X & Y coordinates at the cursor location after any of the 2-D Draw Menu options ([T]iny, [B]ig, [S]trange, and [G]enerator). Here's how it works. A flashing pixel (cursor) will appear somewhere on the screen. After a 3-D Draw Menu option, the cursor will be at or near the upper right corner of the drawing. The 2-D Draw Menu options do not affect cursor location. Pressing the "arrow" keys (5-8) moves the cursor left, right, up and down. The 1-4 keys move the cursor diagonally, in the direction shown by the graphic symbol on the respective keys. The "command" keys are [D]raw, [E]rase, [C]ursor, and [R]eturn. [D]raw and [E]rase can be used to "fix up" your picture, add custom borders or other touch-ups, etc. Pressing [C]ursor after [D]raw or [E]rase revert the cursor back to its "transparent" mode, in which it does not affect what is on the screen. [R]eturn exits the Trace routine, and returns you to the View Menu. If you have just drawn one of the 2-dimensional Draw Menu figures, a report of the cursor's X/Y location is given. This is based on actual map co-ordinates, rather than screen co-ordinates. For instance, when drawing a [B]ig, [S]trange or [Generator] display, there is a two-to- one pixel relationship; i.e. the X axis goes from 0 to 127, and the Y axis from 0 to 95. You are also asked "Again?"; if you press [Y] you are returned to the Trace routine; any other key exits to the View Menu. THE MAP MENU Finally, after all this preambling, here's how you go about actually creating a "Map" from which you can "Draw" various neat "F-Scapes." The options are [C]lear, [S]cope, [M]umap, [S]cale, [Flip], [T]urn, and [Z]ap. CLEAR [C]lear dimensions the map array, setting the value of each element to zero. This is one of those places where you are asked "Are you sure?", for obvious reasons. You have to use the [C]lear option before attempting to generate a F-Scape map. ZAP [Z]ap is, in a way, the opposite of [C]lear. It collapses the map array to a size of (1,1). This is so that you can save the program alone, without that humungous (almost 25K) map array. This is how you receive the program. HINT: If you [Z]ap by mistake, you might still be able to [D]raw from the "deleted" array. Only the bottom few lines will be corrupted if you do your Draw Menu option right after Zapping. DO NOT use [C]lear, as this will genuinely erase the present array. However, the [S]ave options in the File Menu will NOT save the zapped array to tape. SCOPE This creates a scope-scape. Use the [C]lear option first, to create the array if it doesn't already exist. You are asked for the number of generators to use, from 1 to 128. How long the scope-scape will take to create, depends on how many generators you select, at the rate of about 1.5 minutes per generator. You are then asked if you want the generators to be [R]andomly determined, or [A]ssigned by you. If you select [R]andom, you're asked for a "Randomizing Seed" value, so that you can duplicate (or extend) the same scope-scape later, if desired. Entering zero (or using the default of PV=0 by pressing [Enter] alone) gives a truly unpredictable set of generators. The co-ordinates (location) and value (magnitude) of the generators are then pseudo-randomly created. If you select [A]ssign, you are asked to input the X and Y co-ordinates, and the value of each generator. Though the generators are, of course, in the Generator plane, their X and Y values correspond directly to the co-ordinates of the Effect plane (0<=X<=127, and 0<=Y<=95). Note, however, that X and/or Y MAY be outside of the range of the Effect plane. In other words, it's perfectly valid to specify X or Y values less than zero, or greater than 127 or 95 respectively. Don't go TOO far out of range, however. Not only will such remote generators have a negligible effect, but you might also run into overflow of the 16-bit arithmetic, with bizarre results. There is no set range limit on "Value," except that it must of course be within the 16-bit signed integer range (-32768 to +32767). However, once again there will be a problem with overflow if the absolute value of these are too high. Keeping "value" in the range of -300 to +300 will usually be fine, but to be unconditionally safe, limit them to the 8-bit range (-128 to 127). (This will also prevent the need for major re-scaling later.) After the generators have been assigned one way or another, you are asked to input the "Z" distance. Lower values of Z yields sharper peaks and dips, and higher values yields broader ones. Electronics people can relate this to the "Q" of a tuned circuit; higher Z equals lower Q. "Good" values of Z range from about 5 through 30, though higher (or lower) values are, of course, permissible. Z=zero would only produce a "carbon-copy" of the Generator plane ("infinite Q"). However, using 0 will give a zero result in this program because attempted division by zero gives an answer of zero, rather than stopping with an error code. Finally, you are asked to verify that you want to continue. Answering [Y] will commence calculating the map. This is done in FAST mode, and the BREAK key is inactive during calculation, so don't assume that the program has crashed. Be patient, you are eventually returned to the Home Menu. MUMAP This is something of an afterthough. It lets you do 3- dimensional Mandelbrot set plots, as well as the usual 2-D ones we have all grown to know and love. ("Mu-map" was Benoit Mandelbrots original term for what was later named the "Mandelbrot Set" in his honour.) You can even do composites of Mu-maps and Scope maps, for even more realistic-looking 'scapes! There is good reason why [M]umap appears before [S]cope in the Map menu. This is that creating a Mu-map over-writes the map array. Scope maps, on the other hand, are additive. In other words, you can do a Scope-scape using one set of parameters, then do another without first [C]learing the old map. The new map will simply be algebraically added to the old map. You can use this, for example, to make some rolling hills and valleys (high Z), then add sharper peaks and dips (low Z). The process can be continued ad infinitum, or until you mess up the map by overflowing 16- bit arithmetic. If you want to superimpose a Mumap and a Scope-Scape (as the illustration of Figure 2 below), you have to do the Mumap FIRST, then add the scope-scape(s). You can't superimpose one mumap on top of another, or on top of a scope-scape. For information (and practice) in playing with the Mandelbrot Set (Mumap), see my "MANDEL-FFP." MAP MANIPULATION The next options physically modify the map array. In general, the modifications can be reversed, but beware of using Re-[S]cale, which can get you into trouble because of the rounding that accompanies integer math. FLIP [F]lip inverts the map (multiplies each element by -1). Peaks become dips, and dips become peaks. Using [F]lip again reverts to original. TURN [T]urn rotates the map by 180 degrees. If you use one of the 2-D Draw Menu options ([T]iny, [B]ig or [S]trange), it's as if you turned the map upside down. If you use a 3-D option ([F]ront or [A]ngle), it's as if you're looking at the map from the other side. In other words, "North" becomes South, and vice versa. An exact analog to [T]urn is if you used the View Menu [M]irror options, mirroring both horizontally and vertically. Note the important distinction, however; [M]irror, being in the View Menu, affect only the screen display; it does not modify the map. [T]urn, however, modifies the map itself, without visible results on the screen until you use one of the [D]raw options to re-create the new image. As with [F]lip, the effect of [T]urn can be reversed with a second application of [T]urn. [S]cale You will find quite often, that your resulting map spills off the screen when using a 3-D Draw Menu option. Slight overspills are alright, the part the goes of the screen is simply ignored. Larger overspills, however, can cause bizarre vertical lines to be generated. [S]cale lets you view the present scale of the map (maximum and minimum values), and optionally rescale the entire map. It works like this. The program first searches through the map to find the highest and lowest values, and reports them. If you wish to change the scale, answer [Y] when asked "Rescale?". You are now prompted for a new maximum and new minimum. For instance, your map might range from - 300 to +250. Looking at an [A]ngle 3-D plot, you figure that it would all fit on the screen if the range were -100 to +100. This approach was a little more involved to program than a simple "scale factor" and "offset" approach, but is (I feel) a lot easier for the user. The trouble with rescaling is that we are dealing with integers. If you increase the spread between maximum and minimum, you will get "jaggies" as a result. So rescale judiciously. You can always go smaller, but beware of going too small; if you try to enlarge after getting carried away with this, you will lose detail. Incidentally, the rescaling is done in floating point math even though the starting numbers as well as the final results are integers. This minimizes "integer strobing" and allows fractional scale factors. In other words, it is perfectly valid to rescale from "-100 to +100" to "-47 to +45", or for that matter anything else. A WORD ABOUT ERRORS I have already pointed out that generating a Scope map does not automatically re-dimension the map array. There are several other parts of the program where you have to make a conscious effort to do things in the correct order. For instance, the Draw Menu options do not automatically clear the screen. As in the case of map dimensioning, this can be annoying if you forget to clear the display before drawing a new map. On the other hand, it allows for added flexibility. For instance, you can super-impose a [B]ig display over a [S]trange, or a [G]enerator over a [B]ig, or whatever. Since this is a program for entertainment and experimentation, I felt it better to let you make mistakes and suffer the consequences, than to make it all too cut and dried. Some of your "errors" will actually produce fascinating (if sometimes bizarre) results. To my knowledge, it's not possible to crash the program by making mistakes. A few mistakes, such as entering the null string for the SAVE name, will cause an error stop, so even though the program is crash-proof, it's not quite bomb- proof. THE SET MENU Before we get onto the Draw Menu, which finally creates those nifty displays I've been promising, let's talk about the Set Menu. This modifies various parameters, and at least one is vital in controlling your 2-D displays. After you are done with the Set menu, return to the Home menu using [Enter] or other undefined key. [F]ast Setting Fast mode causes all Draw Menu options (except [G]enerator) to be done in FAST mode. This is about six times as fast as SLOW mode, and is probably what you will usually be using. [S]low Pressing [S]low lets you see subsequent Draw Menu options "develop". It's especially neat to watch the 3-D pictures being drawn, but it is rather ... well... SLOW. [B]andwidth This sets the "bandwidth" or "map scale" of the 2-D maps [T]iny, [B]ig and [S]trange. If you've used MANDEL-FFP or JULIA-FFP, you are already familiar with this concept. If not, read on. Let's consider a topographical map. A very accurate map would have a line for every meter of elevation. This would correspond to a bandwidth of one. However, such a map might be so busy that you can't see what's going on. So you might prefer a map with a topographic line for every ten meters of elevation. In other words, a bandwidth of 10. Two of the 2-D maps (Tiny and Big) are exactly analogous to topographic maps. The Strange map is similar, except that it is a "color" topo map, with the "colors" repeating cyclically. If your map is just a busy batch of bustling pixels, use the Set Menu to increase Bandwidth. If it just shows broad bands with no detail, use a smaller bandwidth. VERIFY This is a toggle, and is used to turn the "Are you sure?" message on or off. As you get proficient in finding your way around the program, you'll probably want to turn this off. (MS-DOS users: does this option seem at all familiar?) [Q]uick [Q]uick changes all tape operations to the F-SAVE format. [N]ormal This reverts tape operations to the slower but more generally reliable "Sinclair-style" routines. This is how the program is supplied. [L]evel This is a neat option, usable in all the Draw Menu Effect- plane options. It sets a "sea-level," or minimum "elevation" below which no plotting occurs. For instance, if the Level variable is set to zero, all map array entries that are negative will be plotted at a level of zero. This lets you experiment with the equivalent of a "sea-level," and watch how your continents appear, disappear, and change shape as the level is varied. As supplied, this is set to -32768, which in effect disables this feature. [T]race speed If the [T]race option in the View Menu is too fast (or too slow) for you, you can use this option to change it. Zero is the fastest (great for moving around but hard to manage for [D]rawing or [E]rasing), whereas 2 to 3 is good for touch-ups but a little sluggish for moving about. A good compromise is 1 (which is how you receive the program). THE DRAW MENU Very well, you've generated a map and have used the SET menu to choose your desired operating parameters. Now let's discuss the Draw Menu options. They are: [T]iny Draws a "half-size" 2-D map [B]ig Draws a full-size 2-D map. [S]trange Draws a full-size "color" map (2-D) [F]ront 3-D "front view", with perspective [A]ngle 3-D "angle" view. My favourite. [G]enerator Graphic image of the generator plane. TINY The resolution of the program, as discussed before, is 128x96. The resolution of the screen is 256x128, so plotting the map on a 1-pixel-per-point basis gives a "tiny" (half-size) map. "Tiny" is the fastest of the drawing routines. It is therefore ideal for experimenting with different values of "Bandwidth" (use the Set Menu to change this). Note that "Tiny" (along with its cousins "Big" and "Strange") is essentially a "topographic" drawing of your map. You're looking "down" on the map, each "band" represents a range of "altitudes" (as determined by the "Bandwidth" parameter). [B]ig This is just like "Tiny" except that it produces a full- size display. Each point in the map is 2 pixels wide and two pixels high. Like "Tiny," it relies on "Bandwidth" to specify the degree of "busy-ness" of your display. When using [B]ig, you also have the option of selecting a [D]ark or [L]ight pattern for the "black" bands. [D] will usually be preferred for screen displays, but [L]ight gives, I think, a better looking TS2040 printout using the View Menu [P]rint option. [S]trange This is the neatest of the 2-D routines. What it does is produce a "big" map, but instead of coloring each four pixels of a given point the same (black or white), it uses a value that represents the average of that point and its immediate neighbor. This has several effects: 1: Apparent resolution improvement. Much of the "jagginess" of the "Big" display is eliminated. 2: "Quasi-color." By this I mean that the process of averaging, coupled with the phenomenon of "integer strobing," produces different colors (shades of grey) for the various areas. 3: Heavy reliance on "Bandwidth" for the nature of the display. For instance, values of 4, 5, 6, and 7 will give essentially the same pattern, but each with a different "look." Experimentation with the "Bandwidth" parameter is highly recommended. If behavior seems strange, keep playing. And read on. If you're wondering why I called this option "Strange," here's an anecdote to explain it. The idea of averaging is very simple; I at first intended only to give a resolution improvement. When I ran the routine, however, I got all these bands of different shades of grey. With some values of Bandwidth on some maps, I got "borders" around each band. Though the effect was neat, I KNEW that I had a bug. I spent hours combing back over my code. No bugs to be found. Strange. I recoded it in BASIC, and it worked as I thought it should have. Very strange. Then I realized that integers are a different breed of cat than floating point. As it turned out, integer rounding phenomena were responsible for this interesting display; adding INT before every number in my BASIC equivalent produced the same display as that mysteriously "buggy" (yet bug-free) machine-code version. Extremely strange. What I thought of as a "bug" was, indeed, not a bug at all. It was only my own expectations differing from reality. In this case, the actual result was far better than what I was trying to do. I was only going for "advantage #1," but got advantages #2 and #3 without even trying. Now that's REALLY STRANGE. Usually, such errors in prediction result in inferior performance, compared to the programmer's expectations; some would say that this is always the case. This time, it was the opposite. Pretty strange, wouldn't you agree? Besides, I couldn't figure out what else to call it. "2-D Topographic Plot With Improved Resolution Grey-Scale And Variable Visual Aspect" simply wouldn't fit on the menu line. [F]ront This is the first of two "3-D" routines. It shows a direct view, facing "North". Its claim to fame is that it offers true, variable perspective. Perspective is a simple concept, but it can be a real bugger to code into a computer program. The purpose of these notes is not to provide a record of the travails of a travelling programmer for trivial machines, but let me assure you that I "lost" quite a bit of sleep pondering the sublimities of visual perspective. Oh well, who needs sleep anyway. This option first asks for a "Perspective factor." If this is zero, there is no perpsective. It's just as if you were viewing the map from a great distance through a telescope. The length of the nearest horizontal (south) edge is the same as the furthest (north) edge. The maximum suggested perspective factor is 8. This results in the north edge (furthest away) being 1/2 as wide as the south (nearest) edge. Higher values will be accepted, but produce bizarre effects due, one again, to the wraparound and rounding problems associated with integer math. An interesting side-light; not only does perspective shorten the X and Y axes, but it also makes things in the distance appear shorter on the Z axis. Of course, you will say. That's obvious. But it wasn't to me. I neglected this in my first attempts. My first 3-D perspective maps looked good, but were somehow unrealistic with some maps. Why? I had forgotten to make things "shorter" as distance increased. Silly me. In the final result, this oversight has been corrected. This option also introduces the concept of a "scale factor." If you input a scale factor of 16, this means that you are specifying a 1:1 relationship between the value of a point and the number of pixels it elevates the "Z" axis. A point with a value of 100 at the front of the map, will raise that point's display 100 pixels above the X-Y grid. Lower values will prescale the entire map proportionally. Note that this only affects the current DRAW operation. It does not change the values in the map. If you want to permanently rescale the map, you have to do so using the [S]cale option in the Map Menu. Speaking of scale, let me restate that if your map contains extremely high values, you might get some very bizarre Drawings. Either specify a lower scale factor during "Draw time," or else rescale your map to more reasonable values. The final note to this option is that only the horizontal lines are drawn. I found that drawing the vertical lines also only made this type of map display busier, without any real visual advantages. It thereby makes this 3-D option superior for extremely complicated maps. [A]ngle This is my coup-de-grace. I've been trying to figure out how to do this for years. It gives a 3-D view of the map, facing approximately north-by-northeast. This time, we do plot both the vertical and the horizontal lines. In this case, there is no perspective. In other words, you're looking at the map through a telescope, from a great distance. Why didn't I include perspective? See the "Creative Compromises" section below. However, you are asked for a "Density" factor. This represents the distance by which adjacent lines are separated. "2" gives the best results in most cases. "1" gives a very "dense" display, which is often too busy but can be good for simple maps. "3" and "4" are good values for a first look, since they run proportionally faster. Higher values show up shortcomings in my method of hiding lines, and are not recommended for that reason. The front and left side of the drawing show vertical lines, representing positive Z-axis deviance from zero. This helps to enhance the 3-D effect. This one is really neat to watch in SLOW mode. It takes quite a while, but shows you how the map is created. Creative Comprises This is another "excuses" section. We all dream of a computer with a DWIM button. You press this button to tell the computer to "Do What I Mean." Another handy button would be labelled "DWIWRN" (Do What I Want Right Now." In reality, we constantly have to balance speed, features, speed, accuracy, speed, ease of use, speed, and flexibility. This is where different programmers have differing priorities. In my case, I wanted to do some neat mathematical graphics on an antique toy computer, without having to wait days for something I might not like after all. So that's why there are three different kinds of math in the final program. That's also why the hidden-line method is not perfect in all cases. (To make it perfect would have taken at least twice as long, without a perceptible improvement in most cases.) It's also why I threw out the idea of including perspective in the [A]ngle map. This is also why the program isn't smart enough to clear the screen or the map by direct telepathy. It also explains certain other oddities that might be construed as bugs. They're not bugs. They're creative compromises. I did what I could to make the program fast, user-friendly, accurate, fun to use, impressive, and fast. All while fitting into "only" 56K of RAM. I had to compromise on "all of the above" in order to give you all of the above. Congratulate me. I've just invented the ultimate computer cop-out. [G]enerator This plots a 2-D map of the generator plane. Upward- pointing characters represent positive generators, downward pointing shows negative generators. The size of the UDG characters (actually sprites) represents the relative size of each generator. This assumes that you're using generators in the "reasonable" range of 0-128. APPENDIX A THE BASIC VARIABLES Quite a few variables were defined externally, to save memory. This means that you won't find any LET or DIM statements for many variables used by the program, even though they are read (and in some cases written to) within the program. They represent constants (often-used integers, GOSUB and GOTO routine line numbers, and USR/POKE/PEEK addresses), menu arrays, and pre-defined parameters which have a "PV" (present value) but may be changed by the program. The following list details the name, function, and constant value (if applicable) of all externally pre- defined variables in the program, in order of definition. NAME VALUE DESCRIPTION HR 20000 The SHR-EB entry point KEY 22551 The "key" routine O 0 (zero) Constant H 1000 Home Menu entry WA 260 "Please Wait" subroutine SR 250 "Are You Sure?" subroutine PR 230 "Print Prompt, wait for key" subroutine ER 220 "Erase Prompt" subroutine VU 10 View Menu entry point IN 240 "Input value" subroutine AS 600 "Assign 16-bit Value to P$" subroutine MS 290 "Print Message" subroutine RD 500 "Read FP value in T, convert to 16-bit" K 5 Constant F 0 Input flag. Controls CLEAR/RUN mode (lower 2 lines) for single or multiple input. VE 1 "Verify" flag D 2 Constant MR 1030 Home Menu "return" line number W$(12,18) Message/prompt swap string V$(105,10) View Menu swap string H$(91,8) Home Menu swap string S$(81,8) Set Menu swap string D$(73,10) Draw Menu swap string M$(81,7) Map Menu swap string F$(43,6) File Menu swap string C$(43,6) Save/Load Menu swap string U$(73,10) User Tools Menu swap string X$(32) Info message accumulator MD 21377 2-D mode (Tiny, Big or Strange) BW 3 Bandwidth parameter Z 10 Gen.-to-Effect plane distance R 3 Number of Generators V 20 Constant E Save/Load flag Q 1 Normal/F-SAVE flag Y$(2,4) "Save"/"Load" Save/Load menu title A -2.4 A-corner (mumap) B -1.25 B-corner (mumap) S 2.5 Side length (mumap) L 99 Iteration limit (mumap) G 2.5/96 "Gap" = S/96 (mumap) W "Window-down input" flag VM 700 View Menu Swap subroutine HM 710 Home Menu Swap subroutine MM 720 Map Menu Swap subroutine DM 730 Draw Menu Swap subroutine FM 740 File Menu Swap subroutine SM 750 Set Menu Swap subroutine UM 770 User Tools Swap subroutine TS 1 Trace speed THE OTHER VARIABLES Here are the rest of the BASIC variables, which are defined within the program, but whose meanings are undoubtedly obscure without at least a brief description. T General temporary numeric variable, used quite extensively. T$ General temporary string C Mumap accumulator M Mumap "Imaginary" counter N Mumap "Real" counter HI High byte of 16-bit number LO Low byte of 16-bit number Q "Which dimension" (temp.) P$(99,256) The main map array. P$(97) through P$(99) represent generator X, Y, and value. Q$(256) Used by the hidden-line subroutine. I General loop counter SOME ROUTINE ADDRESSES Here are the meanings of the addresses referred to within the BASIC portion of the program, in order of appearance: 23344 FFP-Mandelbrot set (mumap) routine 23701 LOAD screen, normal mode 23571 SAVE screen, normal mode 22605 Mirror vertically 22702 Mirror horizontally 22639 Draw dots for border 22743 Figure X/Y co-ords of cursor, return value 22812 X position of cursor 22813 Y position of cursor 23583 SAVE variables, normal mode 23622 LOAD variables, normal mode 22288 2-D plots. Which type is selected by MD 22385 Pattern (L or D) sprite number for "Big" 21878 Perspective value 21940 "Front" 3-D plot routine 21879 Scale value for 3-D plots 22961 Density value for "Angle" plot 22962 "Angle" 3-D plot routine 23250 "Generator" plot routine 21376 Fast/Slow display mode flag 16505 "Sea-level" variable (2 bytes) 21580 Scope-scape routine 23173 Scan map for maximum/minimum values 23190 Get minimum value 23196 Rescale routine 21850 Flip map routine 21813 Turn map routine APPENDIX B BASIC ROUTINE LINE NUMBERS Here are the BASIC routine addresses. This list is essentially the REM lines that would have made the program more readable, had there been room for them. First, the menus: HOME MENU, line 1000 25 View 1080 Quit 1500 File 2000 Draw 2500 Set 2200 Strange 2250 Front 3-D 2300 Angle 3-D 2350 Generator SET MENU, line 2500 2600 Fast 2650 Slow 2700 Level 2750 Bandwidth 2800 Verify 2850 Quick 2900 Normal 2950 Return to Home menu 2975 Trace speed TOOLS MENU, line 3000 9100 User routine 1 9200 User routine 2 9300 User routine 3 9400 User routine 4 9500 User routine 5 9600 User routine 6 MAP MENU, line 3500 4000 Clear 4100 Mumap 4500 Scope 5000 (Re-)Scale 5100 Flip 5150 Turn 5180 Zap OTHER ROUTINES 4 Dummy RETURN (if GOSUB goes to 0) 5 General "KEY" subroutine (alpha chars only) 40 Main Mumap routine 190 64-column message subroutine 195 32-column message routine 220 Erase message subroutine 230 Print prompt, wait for keypress, erase 232 Wait for keypress, then erase 240 Input value subroutine 250 "Are you sure?" subroutine 260 "Please Wait..." subroutine 290 Print message, pause, erase 500 Convert T to 16-bit integer 600 Assign HI/LO to P$(97)-P$(99) 700-775 Menu swap subroutines 1030 Return to home menu 1400 Input filename subroutine 1500 File menu 1510 File menu return 1530 Save/Load menu 1550 All/Data? 1595 Do selected File menu option 1600 Save/Load All 1700 Save/Load Data 1900 Exit file menu 2080 Erase Draw menu, return to Home 2290 Input scale factor 2520 Set Menu return 2625 Print "... mode set" message 3510 Map Menu return 4050 "Map ..." report 4600 Assign/Calculate generators loop 4900 Clear Map & Home menus Appendix C Sample User Tools Here are a few sample "Tools" routines you might like. The first allows you to input any function of X and Y, and evaluates the function over the "effect plane" for plotting with any of the Draw Menu routines. The other two options allow you turn the hidden-line routine off and on; there may be times when you want a wire-frame drawing (Front 3-D or Angle 3-D) without hiding the lines that go "behind" something. Start by modifying line 3060 so that it looks as follows (note that lower-case means inverse): 3060 IF USR HR THEN PRINT AT V+S GN PI,D;;;"1 fUNCTN";TAB D;"2 uN hIDE|;TAB D;"3 hIDE";TAB D;"4 uN USED";TAB D;"5 uNUSED";TAB D;"6 uNUSED" If you want to add other user routines, now is a good time to add the options to the menu (replacing the "Unused" entries for options 4, 5 and 6). Note that you are limited to six characters per menu option (7 is borderline). For instance, "4" might be "DOS" to go into your disk system, "5" might be "Paint" to automatically load ZX-PAINT, and "6" might be "Dump" (to big printer). Or whatever. It's up to you. When done modifying the menu, GOTO H, select [T]ools, then [Y]es. When the menu has been generated, press [Enter] to return to Home Menu, and [Q]uit. Delete lines 3010-3095 and 9399, then enter the following lines: 9100 REM functions 9105 IF USR HR THEN RETURN :return to normal BASIC 9110 CLS 9115 PRINT "ENTER A FUNCTION F(X ,Y)","0<=X<=127, 0<=Y<=95" 9120 INPUT A$ :function in A$ 9125 FAST 9130 FOR Y=0 TO 95 : Y-loop counter 9135 FOR X=0 TO 127 :X-loop counter 9140 LET Q=Y+1 :Setup for ASsign 9145 LET I=X+1 subroutine 9150 LET T=INT (VAL A$+.5) :evaluate function 9155 GOSUB AS :assign to P$ 9160 NEXT X 9165 NEXT Y 9199 GOTO H :return to Home Menu 9200 REM hidden lines off 9205 POKE VAL "21917",VAL "33" 9210 LET T$="FF" 9220 LET X$="hIDDEN LINES o"+T$ 9225 GOSUB MS :print message 9299 GOTO H 9300 REM hidden lines on 9305 POKE VAL "21917",VAL "34" 9310 LET T$="N" 9315 GOTO VAL "9220" 9399 [delete] The screen-dump below shows the function: 100* SIN(X/10)* COS(Y/20)* EXP(-X/60)* EXP(-Y/80) After generating the map for this function, the map was [F]lipped. This effectively multiplied the function by -1. It was also [T]urned, which effectively replaced all "X" with "128-X" and all "Y" with "96-Y". Get it? If yes, great. If no, don't worry about it. Just have fun. When drawing, I selected [A]ngle from the Draw Menu, then Density=3 and Scale=12. The border and legends were added from the immediate mode using the SHR-EB "IF USR HR THEN LPRINT R;" and "IF USR HR THEN PRINT;," commands, respectively.