In this article we'll be discussing the programming techniques used in writing the anniversary "PYRO" demo in EUG #58. If you missed it then order a copy today because it marked ten years of EUG and was packed with lots of other exciting programs.
There are three programs: 3D (which generates the paths that the fireworks follow), MC (the main code dealing with the animation), and BANNER (which produces the large letters in the finale), plus a small sprite file called FLARES. These collectively make up the machine code file PYROMC, the *RUNnable program that appeared on EUG #58 along with a short Basic loader program called PYRO.
It might seem complicated to assemble the machine code in three separate parts, but it means we can assemble more code. Furthermore we can test out each part in turn. Once one part has assembled itself, the source code isn't needed any more, so we can use the memory for more source code. This does mean that 3D, MC, and BANNER must be assembled in strict sequence for everything to work properly, but with due care you shouldn't experience any difficulties.
1. 3D - Path Generation
Each firework follows a path that is either calculated (which we'll discuss in part 2) or predefined. The 3D program calculates and stores co-ordinates that make up the predefined fireworks. Some three-dimensional geometry is involved, and that's quite complicated so we won't go into too much detail here.
The screen is divided up into a grid of 80 points (-40 to 39 from left to right) by 64 points (-32 to 31 from top to bottom). The procedure PROCmove3(x%,y%,z%) is used to map three-dimenstional co-ordinates (x%,y%,z%) onto the two-dimensional plane (the screen of your TV or monitor). The variable th% (theta) relates to the position from which we view the object, defined in line 260.
Well, we can't physically get inside the TV screen to look at an object from any other position except the front, so really th% just affects how the object is drawn thus giving the impression that we might be looking at it from a different angle.
It's initially set to 60 degrees, the usual angle for 3D geometry, and m1 and m2 are the the SINe and COSine of this angle and multiplied by k%, the scaling factor. This is varied throughout the shape-plotting procedures (see below).
If you're interested in 3D geometry then there are plenty of books on the subject - although a lot of maths texts make things more complicated than they need to be. Andre Moerenhout presented an excellent article in Electron User ("Enter the Third Dimension" in the October 1987 issue). I just experimented with my own programs.
The co-ordinates are stored in memory from &1100 onwards as sequential pairs, i.e. (x1,y1); (x2,y2); (x3,y3) and so on. &1100 is the first free location after the Disc Filing System workspace provided files aren't being opened. We aren't reading from or writing to files, so &1100 is safe to use and that's where we start building our machine code program. Electron owners must change &1100 to &1E00 however; ADFS needs all of the memory from &E00 to &1DFF regardless of whether you're reading/writing files or not. (This explains why some of you experienced problems with the original disc!)
The variable c% is the offset from &1100. A pair of co-ordinates is stored, a white square plotted on the screen, then c% is incremented by two. The white squares give a rough idea of what the shape of moving fireworks will look like.
The next free address at which points will be stored is printed at the top of the screen. The program waits between plotting each part of a shape so that I could write down the memory locations at which different paths started. They were added to the DATA lines in MC which we'll come to shortly.
I have included four shapes which are dealt with by four procedures:
PROCcathwhl | Catherine Wheel, a spirals of points curling in to the centre |
rad% | Number added to the radius which decreases as points move toward the centre. i.e. tightness of the spirals |
st% | Number of arcs used to make up the Wheel expressed as a division of 360 degrees e.g. 4 arcs and 360/4 = 90 degrees between starting point of one arc and the next |
PROCfountin | calculates points for a fountain of fireworks, using: |
si% | Number of points on each jet |
st% | Density - how close together points are |
sa% | Start rotation angle (increasing or decreasing gives impression of fountain turning about its centre) |
se% | Number of jets, in this case eight. More will take up more memory. Total number of bytes used to store all points for the fountain is si%*se%*2 |
ds% | Vertical displacement. -ve numbers will shift points downward, use to counteract effects of jets going off top of screen |
PROChelix | Fireworks spiralling down the screen (like coils in a spring) |
rad% | Radius of helix |
k | Number added to y co-ordinates, i.e. how fast the helix spirals down the screen. This only needs to be a tiny amount e.g. 4 |
sa% | Number of coils |
ds% | Y co-ordinate of top of (first point on) helix |
PROCshower | A shower of fireworks. Similar to PROCfountin but the sparks fall to earth immediately instead of climbing up first like in a fountain |
rad% | Radius of shower at base |
k | Steepness of arcs. Larger values spread the sparks out more widely (i.e. fall to earth more slowly) but if too big they'll go off the screen! |
sa% | Start angle (changing this gives the impression of the shower rotating about its centre) |
se% | Number of arcs. Again, the more arcs there are, the more memory is used to store the co-ordinates |
The above are only my notes on experiments, so be tempted to do the same and change a few numbers to see what effect you get. You could invent some more paths such as ellipses or bell shapes.
The next program needs to know how many points there are on each arc that makes up the fountain, catherine wheel, or whatever. I wrote down the difference between the current address minus two and previous address, then divided by two. For example the address is &1148 after the first arm of the catherine wheel is plotted, so (&1148-2-&1100)/2 = 35 points.
Each arc that makes up the shape must have the same number of points. This is taken care of for symmetrical shapes like the ones in 3D, but be careful if you are designing a firework whose component paths are slightly different from one another.
When each shape is complete, I noted the address at the top of the screen minus two - &1100, &1220, &1350, and &14A0. These will be different of course if you're assembling the code elsewhere in memory, e.g. &1E00, &1F20, &2050, and &21A0 if you changed the starting address to &1E00. The final address printed at the top of the screen is the address at which program MC starts assembling the animation code.
2. MC - Animation Code
This program has to be run at least twice. That's because the code needsto know where in memory it finishes assembling (the address at which the BANNER code will reside) and also where the next program finishes assembling (the start of the workspace used in the animation).
MC begins assembly at &155E, the address we noted above. It will be different if you stored the points at another address e.g. &225E.
PYRO is the main control loop. Location &52 counts the number of fireworks displayed for use as an index into the FNOS table. An entry in the table is either an index into the fireworks type at FADR (e.g firework zero is a vee shape and firework one is a rocket), or 128 which flags the computer to pause between one section of the show and the next.
The delay is achieved by using OSBYTE 129, the machine code equivalent of INKEY. Before calling OSBYTE, the X and Y registers contain a time limit up to which the computer will wait for a key to be pressed. Try holding down a key throughout the demo and you'll see that this speeds things up because the computer detects a keypress and skips to the next part of the program.
Delaying via INKEY also lets us examine the Escape key. The Y register will return with the ASCII code of the pressed key - 27 for ESCAPE. The program uses OSBYTE 124 to clear the Escape condition and prints 'Fireworks' before returning to Basic.
You can put your own error messages into machine code programs by writing:
BRK EQUBEQUS "Error Message" BRK
If ESCAPE wasn't pressed then the program branches to NOKEY, where a VDU19 is performed to change logical colour two. The bottom two bits of the fireworks counter become an index into table COLS (line 1400). Logical colour two is changed to one of these four actual colours - yellow, green, magenta, or flashing black/white.
The fireworks type (in the Y register) indexes into the two-byte addresses at FADR, the addresses of the path details. The first three bytes always describe the (s)ound effect (0-4), (n)number of paths, and number of (p)points on a path. The next bytes depend on whether the paths are predefined or calculated:
Calculated: n groups of four bytes: start x co-ordinate, start y co-ordinate, dx (x direction), and dy (y direction). FNfwc used to read in data.
Predefined: one group of three bytes: start x co-ordinate, start y co-ordinate, and address of first path. FNfwp used to read in data.
The macros FNwc and FNwp RESTORE to line L% and advance L% by 10 ready for the next time they are called.
Points on calculated paths are worked out by the code at lines 500 to 660. The next position is worked out by adding dx and dy to the current x and y co-ordinates beginning with the starting co-ordinates (usually the screen centre, left-, middle-, or right-bottom).
The points are stored in the workspace starting where BANNER (the next program) finishes so it's necessary to come back to MC later when we know what that address is. In my case it was &19A7, assigned to 'paths' in line 140. One page is used per path, so it is possible to have up to 255 points on a path, and fireworks with eight paths like the fountain need eight pages of workspace.
Now, you might think that it would save a lot of trouble if we reserved several pages of zeroes within the assembly and assemble BANNER after that, but it would make the resultant machine code longer. Provided your machine code programs clear or overwrite their workspace there isn't any point in saving out loads of blank workspace in the middle.
PREDEF just transfers the points from the address given in the predefined data to the workspace, adding the start co-ordinates to each point in the process.
FWORK is the animation proper. Essentially it plots successive frames of the flare sprites in successive positions along each path of the firework. This gives the impression of moving flames. Normally we can only address 40 by 32 positions on the Mode 5 screen. There are 40 columns (groups of 1x8 bytes) across and 32 character rows down the screen.
This I found too coarse and it made the fireworks move too jerkily. So I've used a special technique to divide up the screen more finely into 80 by 64, i.e. twice the resolution.
The flares were designed as 2 by 4 pixel sprites. Basically if the x co-ordinate is even then we multiply the sprite byte by two and OR it into the screen byte ANDed with 51. For even co-ordinates the sprite byte is ORed into the screen byte ANDed with 204.
If the y co-ordinate is odd then four is added to the screen address, calculated in CALC. This uses a lookup table to do the Basic calculation of a Mode 5 screen address: &5800 + x*16 + y*320.
MKSND uses OSWORD 7 to produce the sound effects. On entry A contains the sound number required. This a compact and handy routine - it works out the address of the eight bytes of sound data (held at SNDTAB onwards) and sets the X and Y registers to that address before calling OSWORD.
3. BANNER - Finale Messages
Before running BANNER you need to know the addresses of FLARE, ERR, and also P% (the next free location) which are used by this program. After the MC has assembled type:
PRINT flare,err,P%
FLARE and ERR must substituted into line 40, and P% into line 150, of BANNER.
All this program does is to enlarge the letters of messages by using the flare sprites to represent those bits of the character definitions. The messages are held M1 (line 640) onwards and pointed to by table MADR (line 570). A message comprises the length of the message followed by the message itself - spelt backwards!
BANNER centralises the message on the screen by doing what in Basic would be (79 - len) DIV 2. For each character, EXPL is called to 'explode' the shape. The character definition is obtained by OSWORD 10, and each line of the definition is examined by rotating the bits into the carry.
TF contains the sprite used to represent set bits. This changes from zero to 15 then back again (see line 270). Lower numbered sprites contain more red and black, while those nearer to 15 are yellow and white, giving the impression of the message fading in and out.
Make a note of where the next free location by typing PRINT~P%. Make a note of it because we now have to assemble the MC program again. Load it in and change line 140 to the value of P% you noted down just now (it should be either be &19A7 or &26A7). Also change the address after the JMP instruction in line 200 to the other value of P% you noted (either &18CE or &25CE). Then RUN the program.
When assembly is complete, save the whole code by typing:
*SAVE PYROMC 1100 19A7 155E
and use PAGE=&2200:CHAIN"PYRO", or if you changed the address to &1E00 back in section 1:
*SAVE PYROMC 1E00 26A7 22CE
and PAGE=&2F00:CHAIN"PYRO".