A.N.A.L.O.G. ISSUE 16 / FEBRUARY 1984 / PAGE 16

Stars 3-D

16K Cassette or Disk

by Craig Patchett

EDITOR’S NOTE: The article accompanying this demonstration is intended primarily for advanced readers. The demo program itself, however, can be typed in and enjoyed by anybody.

Stars 3D illustrates a simple 3-dimensional graphics effect, produced by moving similar objects horizontally at different speeds. It uses a couple of assembly-language “tricks” to achieve the illusion of depth as efficiently as possible.

Listing 1 is an ATARI BASIC program that POKEs the Stars 3D routine into memory and activates it with a USR call. Listing 2 is the assembly-language source code, provided for programmers who want to see how the routine works.

After entering Listing 1 and D:CHECKing it, SAVE the demo program to disk or tape and type RUN <return>. A few seconds later your screen will be filled with what appears to be 192 stars in several planes of motion. Believe it or not, the entire starfield is actually composed of only eight individual stars!

How it’s done.

The dramatic display in Stars 3D is accomplished by using multiple LMS (Load Memory Scan) instructions in the display list. Every DL instruction has its LMS option bit set. This allows me to have the same section of memory appear in more than one place on the screen.

When I set up the display list, I randomly pick one of the eight star memory segments and store its address in an LMS instruction. Normally, this would mean that all the stars from a given memory segment would appear at the same x-position on the screen. I avoid this by adding a random offset (0-48) to each memory address, thereby spreading the stars all over the x-range.

If you think about the offset I’m adding, you’ll realize that an offset of, say, twenty will result in a mode line with twenty “orphan bytes” on the end (see Figure 1). This difficulty is corrected by assigning 96 bytes to each star memory segment instead of 48. But this solution creates yet another problem: scrolling a star across 96 bytes when only 48 are shown will cause the star to be invisible half the time (Figure 2). My solution is to give each memory segment two stars, separated by 48 bytes. Whenever one star moves off the edge of the screen, the other star will appear at the opposite side (Figure 3).

OFFSET = 20     48 BYTES, ON SCREEN   
                                     
  MODE LINE (48 BYTES)   20 EXTRA    

Figure 1.
       48 BYTES ON SCREEN   
                             

The star is off screen and will not appear on again until it reaches the right edge of the screen area. It must scroll 48 bytes to do this.

Figure 2.
        48 BYTES ON SCREEN
                                
a)    •                   •     
The star on the left scrolls off the screen.
        48 BYTES ON SCREEN
                                
b)  •                  •        
The star on the right scrolls on.

Figure 3.

Eight luminances.

Each DL instruction in Stars 3D has its display list interrupt (DLI) option bit set. A small DLI service routine allows me to put a different star color on every mode line. Instead of going for a broad spectrum of colors, I decided to make each star a different shade of the same color, with slow-moving stars set to darker luminances than the fast movers. This further enhances the illusion of depth.

No fine scrolling.

Fine scrolling would have required handling each of the 192 mode lines separately. This takes a lot of processing time. Instead, I chose to manipulate each of the 16 stars (two in each memory segment) directly, using the 6502 ASL instruction. When a star is shifted out of one byte it falls into the next, and when it shifts out of its memory segment it is stuck onto the other end. Each memory segment has its own timer which determines how quickly the stars in that segment will appear to move.

Those are the basics behind Stars 3D. You should be able to find any details I skipped over by studying my source code. I hope this demo will show you how an unconventional approach can lead to terrific savings in both processing time and memory (the entire program requires less than 2K, including the screen RAM), both of which are critical in today’s high-performance graphics programs.

Listing 1.

100 REM ************************
110 REM *    STARS 3D DEMO     *
120 REM *  BY CRAIG PATCHETT   *
130 REM * ANALOG COMPUTING #16 *
140 REM ************************
150 REM 
160 START=14336:REM * $3800 HEX
170 FOR I=START TO START+377
180 READ BYTE:POKE I,BYTE:NEXT I
190 X=USR(START)
200 DATA 32,49,56,32,145,56,169,0,141,200,2,169,7,162,56,160,38,32,92,228,169,253,141,0,2
210 DATA 169,56,141,1,2,169,192,141,14,212,76,35,56,32,13,57,169,0,141,173,63,76,98,228,169
220 DATA 189,141,189,62,169,59,141,205,62,162,0,142,221,62,232,189,188,62,24,105,48,157,189,62,189
230 DATA 204,62,105,0,157,205,62,169,0,157,221,62,232,224,16,208,229,169,189,133,204,169,59,133,205
240 DATA 160,255,162,3,169,0,145,204,136,192,255,208,249,202,240,5,230,205,76,106,56,162,0,189,189
250 DATA 62,133,204,189,205,62,133,205,169,64,160,0,145,204,232,224,16,208,235,96,169,119,133,204,169
260 DATA 57,133,205,169,0,141,173,63,160,3,169,206,145,204,200,208,2,230,205,173,10,210,41,7,72
270 DATA 170,189,111,57,174,173,63,157,237,62,104,238,173,63,10,170,173,10,210,41,63,201,48,176,247
280 DATA 24,125,189,62,145,204,200,208,2,230,205,189,205,62,105,0,145,204,200,208,2,230,205,192,67
290 DATA 208,189,169,65,145,204,200,169,119,145,204,141,48,2,200,169,57,145,204,141,49,2,169,35,141
300 DATA 47,2,96,174,173,63,189,237,62,141,10,212,141,22,208,238,173,63,64,162,13,32,50,57,32
310 DATA 50,57,162,12,32,50,57,32,50,57,202,48,17,222,87,57,208,248,32,50,57,189,99,57,157
320 DATA 87,57,76,29,57,96,189,189,62,133,204,189,205,62,133,205,188,221,62,177,204,10,10,145,204
330 DATA 144,15,169,1,136,192,255,208,2,160,47,145,204,152,157,221,62,96,8,8,6,6,4,4,3
340 DATA 3,2,2,1,1,8,8,6,6,4,4,3,3,2,2,1,1,34,36,38,40,42,44,46,34,112,112,240

Listing 2.

0100 ; *********************
0110 ; *   STARS 3D DEMO   *
0120 ; * BY CRAIG PATCHETT *
0130 ; *********************
0140 ;
0150 ; System equates
0160 ; 
0170 SDMCTL = $022F
0180 NMIEN = $D40E
0190 RANDOM = $D20A
0200 WSYNC = $D40A
0210 SDLSTL = $0230
0220 VDSLST = $0200
0230 COLPF0 = $D016
0240 COLOR4 = $02C8
0250 SETVBV = $E45C
0260 XITVBL = $E462
0270 ; 
0280 ; Zero-page equate
0290 ; 
0300 INDRCT = $CC    ; for indirect addressing
0310 ; 
0320     *=  $3800
0330 ; 
0340 ; Get things going
0350 ; 
0360 INITIL
0370     JSR STRINI  ; set up stars
0380     JSR DLSINI  ; set up display list
0390     LDA #0      ; set background color
0400     STA COLOR4
0410     LDA #7      ; set up VBLANK
0420     LDX #VBLANK/256
0430     LDY #VBLANK&255
0440     JSR SETVBV
0450     LDA #DLI&255 ; get DLI's going
0460     STA VDSLST
0470     LDA #DLI/256
0480     STA VDSLST+1
0490     LDA #192
0500     STA NMIEN
0510 ALLDON
0520     JMP ALLDON  ; let things run
0530 ; 
0540 ; VBLANK routine
0550 ; 
0560 VBLANK
0570     JSR CNTDWN  ; take care of star movement
0580     LDA #0      ; reset index
0590     STA INDEX
0600     JMP XITVBL  ; back to system
0610 ; 
0620 ; Initialize stars
0630 ; 
0640 STRINI
0650     LDA #STRLIN&255 ; set up STRTPL/H arrays
0660     STA STRTPL
0670     LDA #STRLIN/256
0680     STA STRTPH
0690     LDX #0
0700     STX STRPOS
0710     INX 
0720 STRBR1
0730     LDA STRTPL-1,X ; make each address a screen
0740     CLC         ;    width more than the one
0750     ADC #48     ;    before
0760     STA STRTPL,X
0770     LDA STRTPH-1,X
0780     ADC #0
0790     STA STRTPH,X
0800     LDA #0
0810     STA STRPOS,X
0820     INX 
0830     CPX #16
0840     BNE STRBR1
0850     LDA #STRLIN&255 ; clear star memory
0860     STA INDRCT
0870     LDA #STRLIN/256
0880     STA INDRCT+1
0890     LDY #255
0900     LDX #3
0910     LDA #0
0920 STRBR2
0930     STA (INDRCT),Y
0940     DEY 
0950     CPY #255
0960     BNE STRBR2
0970     DEX 
0980     BEQ STRBR3
0990     INC INDRCT+1
1000     JMP STRBR2
1010 STRBR3
1020     LDX #0      ; give each line a star
1030 STRBR4
1040     LDA STRTPL,X
1050     STA INDRCT
1060     LDA STRTPH,X
1070     STA INDRCT+1
1080     LDA #64
1090     LDY #0
1100     STA (INDRCT),Y
1110     INX 
1120     CPX #16
1130     BNE STRBR4
1140     RTS 
1150 ; 
1160 ; Initialize display list
1170 ; 
1180 DLSINI
1190     LDA #DLIST&255 ; set up for indirect
1200     STA INDRCT  ;   addressing
1210     LDA #DLIST/256
1220     STA INDRCT+1
1230     LDA #0      ; get index ready
1240     STA INDEX
1250     LDY #3
1260 DLSBR4
1270     LDA #$CE    ; ANTIC 14, DLI, LMS line
1280     STA (INDRCT),Y
1290     INY 
1300     BNE DLSBR1
1310     INC INDRCT+1
1320 DLSBR1
1330     LDA RANDOM  ; pick star type
1340     AND #7
1350     PHA 
1360     TAX 
1370     LDA MANCOL,X ; tell STRCOL what color
1380     LDX INDEX   ;  this line is
1390     STA STRCOL,X
1400     PLA 
1410     INC INDEX
1420     ASL A       ; times two so we skip over
1430     TAX         ; two screen widths...
1440 DLSBR2
1450     LDA RANDOM  ; pick random offset into
1460     AND #63     ; line
1470     CMP #48     ; make sure it's less than
1480     BCS DLSBR2  ; a screen width
1490     CLC 
1500     ADC STRTPL,X ; ...here instead of one
1510     STA (INDRCT),Y ; put it into display list
1520     INY 
1530     BNE DLSBR5
1540     INC INDRCT+1
1550 DLSBR5
1560     LDA STRTPH,X
1570     ADC #0
1580     STA (INDRCT),Y
1590     INY 
1600     BNE DLSBR6
1610     INC INDRCT+1
1620 DLSBR6
1630     CPY #67     ; 192 lines done, finish up
1640     BNE DLSBR4
1650     LDA #$41
1660     STA (INDRCT),Y
1670     INY 
1680     LDA #DLIST&255
1690     STA (INDRCT),Y
1700     STA SDLSTL
1710     INY 
1720     LDA #DLIST/256
1730     STA (INDRCT),Y
1740     STA SDLSTL+1
1750     LDA #$23    ; give us a wide screen
1760     STA SDMCTL
1770     RTS 
1780 ; 
1790 ; Display list interrupt routine
1800 ; 
1810 DLI
1820     LDX INDEX   ; what line are we on?
1830     LDA STRCOL,X ; load this line's color
1840     STA WSYNC   ; wait for end of last line
1850     STA COLPF0  ; store color
1860     INC INDEX   ; get ready for next line
1870     RTI 
1880 ; 
1890 ; Timer routine
1900 ; 
1910 CNTDWN
1920     LDX #13     ; move fastest star
1930     JSR SCROLL
1940     JSR SCROLL
1950     LDX #12     ; and its twin
1960     JSR SCROLL
1970     JSR SCROLL
1980 CNTBR1
1990     DEX         ; for the rest...
2000     BMI CNTRET
2010     DEC TIMER,X ; ...countdown timer
2020     BNE CNTBR1
2030     JSR SCROLL  ; scroll if ready
2040     LDA TIMARY,X ; and reset timer
2050     STA TIMER,X
2060     JMP CNTBR1
2070 CNTRET
2080     RTS 
2090 ; 
2100 ; Star scroll routine
2110 ; 
2120 SCROLL
2130     LDA STRTPL,X ; set up for indirect
2140     STA INDRCT  ;  addressing
2150     LDA STRTPH,X
2160     STA INDRCT+1
2170     LDY STRPOS,X ; get star position
2180     LDA (INDRCT),Y ; get byte with star in it
2190     ASL A       ; shift star left one
2200     ASL A
2210     STA (INDRCT),Y ; put it back
2220     BCC SCRBR1  ; did it fall off byte?
2230     LDA #1      ; if so, put it in next one
2240     DEY 
2250     CPY #255    ; did it fall off screen?
2260     BNE SCRBR2
2270     LDY #47     ; if so, put it on other end
2280 SCRBR2
2290     STA (INDRCT),Y
2300     TYA 
2310     STA STRPOS,X ; remember where it is now
2320 SCRBR1
2330     RTS 
2340 ; 
2350 TIMER
2360     .BYTE 8,8,6,6,4,4 ; timers for scrolling
2370     .BYTE 3,3,2,2,1,1
2380 TIMARY
2390     .BYTE 8,8,6,6,4,4 ; values to reset timers
2400     .BYTE 3,3,2,2,1,1
2410 MANCOL
2420     .BYTE $22,$24,$26 ; star colors
2430     .BYTE $28,$2A,$2C
2440     .BYTE $2E,$22
2450 ; 
2460 DLIST
2470     .BYTE $70,$70,$F0 ; display list
2480     *=  *+579
2490 STRLIN
2500     *=  *+768   ; star (screen) memory
2510 STRTPL
2520     *=  *+16    ; addresses of beginning of
2530 STRTPH
2540     *=  *+16    ;   each star line
2550 STRPOS
2560     *=  *+16    ; position of star on line
2570 STRCOL
2580     *=  *+192   ; color of each line
2590 INDEX
2600     *=  *+1     ; used to index into STRCOL