M/C Part 12
This month, um, I think we'll have a look at another graphics
routine, since my scrolly cylinder stuff a few months back was
kind of OK. If you've bought Impatience (and if you haven't, why
not?) you will have sat in awe (or something) of the spinning
Fred logo at the start of Triltex. I know this is not something
everyone wants to do (I might be wrong), but sometimes learning
how problems are solved can be useful.
Right, now basically the routine takes a block of graphics data
(in this case, the Fred logo) and dumps it onto the screen. As
it does so, it occasionally "skips" lines of data, giving a
"shrinking" effect. If we change the number of lines to miss out
every frame, we can control the effect.
The method I used was to take a table which held one value for
each frame. This value is equlivilant to the number of data
lines to skip every time we dump a line to the screen. Confused?
Well, if we want to skip one line, the value is 1.00; if we want
to skip two lines, the value is 2.00; if we want to skip 1 and a
half lines the value is 1.50.
Of course, we can't actually jump 1.5 lines on, and continue
dumping the graphics, because the image would become garbage.
Instead we ignore the fractional part when it comes to dumping.
Let me show you how easy this is:
Oh, and before I go on, I'd better explain how I use fractions.
Well, the good old Z80 can do 8-bit calculations very easily
using the accumulator. It can do 16-bit calculations almost as
well using HL, and this is what we use. Normally, HL contains a
whole number between 0 and 65535: H holds the most significant
byte (MSB), and L the least significant byte (LSB). Since the
line numbers in this routine will never exceed 255, surely the
line number can be held just as well in H. This means that L can
be taken as the fractional part of the number. Bit 7 of L will
have the value 1/2; bit 6 will be worth 1/4, bit 5 -> 1/8 and so
on. If we use this method of storing the values in the above
table, we can have HL (or DE, BC etc) accumulating a fractional
line number value. The integer part of this is simply H. The
only complication to this is that on the SAM, lines are 128
bytes long. To make provision for this, bits 7 -> 0 of H, and
bit 7 of L are actually the line number's integer part. Bits 6
-> 0 of L are the fraction.
Eg. We have a "skip" value of 1.5. Using HL as the pointer to
the data, and DE as a contiuously increasing pointer to the
screen:
1) Store this value somewhere.
2) Mask out the fractional part of L (a logical AND with 128).
3) Dump the line from HL to DE, using LDIR.
4) Recover to stored line value, which will contain a fraction.
5) Add on 1.5, and loop until finished.
HL starts at, say, 0, and this is the line number the data is
dumped from.
Next time round HL holds %0000000011000000 (ie. 1.5), but since
bits 6 -> 0 are ignored when the line is dumped, it is dumped
from line 1.
Next time round HL holds %0000000110000000 (ie. 3.0), and so on.
Now for the code. One little point - since the logo is supposed
to be rotating in mid-air, the point on the screen at which to
start dumping will move up and down, so the routine calculates
this. It also means that, if the image is getting smaller, the
old image has to be erased. Since a dummy screen is used, this
is not a problem. Oh, and when the image goes upside down, the
data needs to be read backwards (using a negative skip value).
NB. The full routine sits over the ROM with its own interrupt
handler, so that the screen can go in at #8000, and two screens
are used - at pages 10/11 and 14/15. The Fred logo is 50 lines
tall. The table of skip values also contains the height, in
lines, of the image with each skip value.
spin IN A,(vmpr)
AND 31
XOR 4 ; Find the dummy screen
OUT (hmpr),A ; Put it in hmpr.
LD HL,fred_screen ; Fred_screen is the address
; of where we are dumping to
LD DE,fred_screen+1
LD BC,6399
LD (HL),0 ; Erase the image.
LDIR
LD HL,(data_start); Get the address of where
; the data starts - it can
; be read from either end.
LD DE,(next_logo_line)
; Get the starting screen
; address.
LD IX,no_lines ; Contents of (no_lines)
; equals actual height of
; image on screen.
sp1 LD BC,128
PUSH HL ; Store line number,
; including fraction.
LD A,L
AND 128 ; Mask out the fraction.
LD L,A
LDIR ; Dump a line.
POP HL ; Recover real line number.
LD BC,(logo_step) ; Get the skip value,
ADD HL,BC ; and add it on.
DEC (IX+0) ; Decrement the counter,
JR NZ,sp1 ; and loop until all lines
; have been dumped.
LD HL,(logo_line_pl)
; Get the location in the
; table.
sp2 LD A,(HL) ; Get the height of the image
; There are special token
; values:
; 253 - start from data end.
; 254 - start from data top.
; 255 - go back and start
; from the beginning
; of this table.
CP 253 ; Any other value is a height
JR C,sp5
CP 254
JR Z,sp3
CP 255
JR Z,sp4
LD BC,logo_data+6272 ; End of data
LD (data_start),BC
INC HL
INC HL
JR sp2
sp3 LD BC,logo_data ; Start of data
LD (data_start),BC
INC HL
INC HL
JR sp2
sp4 LD HL,logo_lines ; Start of value table
LD (logo_line_pl),HL
JR sp2
sp5 LD (no_lines),A ; Store the height.
INC HL
INC HL
LD C,(HL) ; Get the skip value
INC HL
LD B,(HL)
INC HL
LD (logo_step),BC ; Store it.
LD (logo_line_pl),HL
; Also store the current
; position in the table.
SRL ; SRL shifts acts like
; RRA, except that the MSB
; is always filled with 0,
; not with the carry.
; This bit calculates the new
; start of the image on the
; screen: take the height,
; half it, take this away
; from half the maximum
; height, and add on the
; screen position all the
; frames start at:-
NEG
ADD A,25+ystart
LD C,0
SCF
RRA
RR C
LD B,A
LD (next_logo_line),BC
IN A,(vmpr) ; Switch screens.
XOR 4
OUT (vmpr),A
LD A,(exit_flag) ; This could be a key press
; checker.
AND A
RET NZ
JR spin
logo_data EQU ???? ; Anywhere you like.
no_lines DB 50
next_logo_line DW fred_screen
logo_step DW 128 ; ie. 1.00
logo_line_pl DW logo_lines
data_start DW logo_data
fred_screen EQU #8000 ; Top of the screen
ystart EQU 0 ; "" "" "" ""