1 \ ******************************************************************
\r
5 \ * Spinning star globe
\r
7 \ * Change the speed of rotation with Z and X keys
\r
8 \ * Press Esc to quit
\r
10 \ * Try assembling the code with debugrasters = TRUE (line 20)
\r
11 \ * It shows how the frame is split up into various stages of
\r
14 \ * The red part is where we start to plot the dots - starting as
\r
15 \ * soon before the first screenline of the next frame is rastered
\r
18 \ * The magenta part is a small loop where we wait for 'vsync'.
\r
19 \ * It's not actually vsync, but in fact a fixed time from actual
\r
20 \ * vsync (timed by timer 1) from which point we can start to erase
\r
21 \ * points from the top of the screen down.
\r
23 \ * The blue part is the time when we are erasing dots. Because
\r
24 \ * the dots are sorted from top to bottom of screen, we can
\r
25 \ * overlap these updates with the actual screen rasterisation.
\r
27 \ ******************************************************************
\r
33 timerlength = 64*8*26
\r
34 debugrasters = FALSE
\r
37 \\ Define some zp locations
\r
53 \\ Seed the random number generator so the build is repeatable
\r
58 \\ Set start address
\r
62 \ ******************************************************************
\r
63 \ * The entry point of the demo
\r
64 \ ******************************************************************
\r
68 \\ Set up hardware state and interrupts
\r
71 LDX #&FF:TXS ; reset stack
\r
73 LDA #&7F:STA &FE4E ; disable all interrupts
\r
74 STA &FE43 ; set keyboard data direction
\r
75 LDA #&C2:STA &FE4E ; enable VSync and timer interrupt
\r
76 LDA #&0F:STA &FE42 ; set addressable latch for writing
\r
77 LDA #3:STA &FE40 ; keyboard write enable
\r
78 LDA #0:STA &FE4B ; timer 1 one shot mode
\r
79 LDA #LO(irq):STA &204
\r
80 LDA #HI(irq):STA &205 ; set interrupt handler
\r
95 \\ Set up CRTC for MODE 2
\r
105 \\ Set up video ULA for MODE 2
\r
110 \\ Set up palette for MODE 2
\r
123 LDA #0:STA angle:STA angle+1
\r
128 \\ Enable interrupts, ready to start the main loop
\r
132 \\ First we wait for 'vsync' so we are synchronised
\r
135 LDA vsync:BEQ initialwait:LDA #0:STA vsync
\r
137 \\ This is the main loop!
\r
141 \\ Plot every dot on the screen
\r
147 ; setup y pos ready for plot routine
\r
149 LDA doty,X:STA ypos
\r
153 CLC:LDA dotx,X:ADC angle+1:TAY
\r
154 CLC:ADC #64:STA behindflag
\r
156 ; get colour from sin index
\r
158 LDA coltable,Y:STA colour
\r
160 ; perform sin(x) * radius
\r
161 ; discussion of the multiplication method below in the table setup
\r
163 SEC:LDA sintable,Y:STA temp:SBC dotr,X
\r
164 BCS noneg:EOR #&FF:ADC #1:.noneg
\r
165 CPY #128:TAY:BCS negativesine
\r
167 CLC:LDA dotr,X:ADC temp:TAX
\r
168 BCS morethan256:SEC
\r
169 LDA multtab1,X:SBC multtab1,Y:JMP donemult
\r
171 LDA multtab2,X:SBC multtab1,Y:JMP donemult
\r
174 CLC:LDA dotr,X:ADC temp:TAX
\r
175 BCS morethan256b:SEC
\r
176 LDA multtab1,Y:SBC multtab1,X:JMP donemult
\r
178 LDA multtab1,Y:SBC multtab2,X
\r
181 CLC:ADC #64:STA xpos
\r
183 ; routine to plot a dot
\r
184 ; also we remember the calculated screen address in the dot tables
\r
186 LDA ypos:LSR A:LSR A:AND #&FE
\r
188 LDA xpos:AND #&FE:ASL A:ASL A
\r
190 LDY counter:STA olddotaddrlo,Y
\r
191 TXA:ADC #&40:STA write+1:STA olddotaddrhi,Y
\r
192 LDA ypos:AND #7:STA olddotaddry,Y:TAY
\r
193 LDA xpos:LSR A:LDA colour:ROL A:TAX
\r
197 BIT behindflag:BMI behind
\r
199 ; if the dot is in front, we double its size
\r
201 DEY:BPL samescreenrow
\r
202 DEC write+1:DEC write+1:LDY #7:.samescreenrow
\r
208 ; loop to the next dot
\r
215 \\ Wait for VSync here
\r
219 LDA #&00 + PAL_magenta:STA &FE21
\r
222 LDA vsync:BEQ waitingforvsync
\r
223 CMP #2:BCS exit ; insist that it runs in a frame!
\r
226 \\ Now delete all the old dots.
\r
227 \\ We actually do this when the screen is still rasterising down..!
\r
231 LDY olddotaddrlo,X:STY write
\r
232 LDY olddotaddrhi,X:STY write+1
\r
235 DEY:BPL erasesamerow
\r
236 DEC write+1:DEC write+1:LDY #7:.erasesamerow
\r
242 LDA #&00 + PAL_red:STA &FE21
\r
247 CLC:LDA angle:ADC speed:STA angle
\r
248 LDA angle+1:ADC speed+1:STA angle+1
\r
250 \\ Check keypresses
\r
252 LDA #66:STA &FE4F:LDA &FE4F:BPL notx
\r
253 CLC:LDA speed:ADC #16:STA speed:BCC notx:INC speed+1:.notx
\r
254 LDA #97:STA &FE4F:LDA &FE4F:BPL notz
\r
255 SEC:LDA speed:SBC #16:STA speed:BCS notz:DEC speed+1:.notz
\r
256 LDA #112:STA &FE4F:LDA &FE4F:BMI exit
\r
260 \\ Exit - in the least graceful way possible :)
\r
267 \ ******************************************************************
\r
269 \ ******************************************************************
\r
272 LDA &FE4D:AND #2:BNE irqvsync
\r
274 LDA #&40:STA &FE4D:INC vsync
\r
276 LDA #&00 + PAL_blue:STA &FE21
\r
282 LDA #LO(timerlength):STA &FE44
\r
283 LDA #HI(timerlength):STA &FE45
\r
285 LDA #&00 + PAL_black:STA &FE21
\r
292 \ ******************************************************************
\r
293 \ * Colour table used by the plot code
\r
294 \ ******************************************************************
\r
297 EQUB &00, &00 ; black pixels
\r
298 EQUB &02, &01 ; blue pixels
\r
299 EQUB &08, &04 ; red pixels
\r
300 EQUB &0A, &05 ; magenta pixels
\r
301 EQUB &20, &10 ; green pixels
\r
302 EQUB &22, &11 ; cyan pixels
\r
303 EQUB &28, &14 ; yellow pixels
\r
304 EQUB &2A, &15 ; white pixels
\r
308 \ ******************************************************************
\r
309 \ * Values of CRTC regs for MODE 2
\r
310 \ ******************************************************************
\r
313 EQUB 127 ; R0 horizontal total
\r
314 EQUB 64 ; R1 horizontal displayed - shrunk a little
\r
315 EQUB 91 ; R2 horizontal position
\r
316 EQUB 40 ; R3 sync width
\r
317 EQUB 38 ; R4 vertical total
\r
318 EQUB 0 ; R5 vertical total adjust
\r
319 EQUB 32 ; R6 vertical displayed
\r
320 EQUB 34 ; R7 vertical position
\r
321 EQUB 0 ; R8 interlace
\r
322 EQUB 7 ; R9 scanlines per row
\r
323 EQUB 32 ; R10 cursor start
\r
324 EQUB 8 ; R11 cursor end
\r
325 EQUB HI(&4000/8) ; R12 screen start address, high
\r
326 EQUB LO(&4000/8) ; R13 screen start address, low
\r
329 \ ******************************************************************
\r
330 \ * Values of palette regs for MODE 2
\r
331 \ ******************************************************************
\r
333 PAL_black = (0 EOR 7)
\r
334 PAL_blue = (4 EOR 7)
\r
335 PAL_red = (1 EOR 7)
\r
336 PAL_magenta = (5 EOR 7)
\r
337 PAL_green = (2 EOR 7)
\r
338 PAL_cyan = (6 EOR 7)
\r
339 PAL_yellow = (3 EOR 7)
\r
340 PAL_white = (7 EOR 7)
\r
343 EQUB &00 + PAL_black
\r
344 EQUB &10 + PAL_blue
\r
346 EQUB &30 + PAL_magenta
\r
347 EQUB &40 + PAL_green
\r
348 EQUB &50 + PAL_cyan
\r
349 EQUB &60 + PAL_yellow
\r
350 EQUB &70 + PAL_white
\r
354 \ ******************************************************************
\r
356 \ ******************************************************************
\r
358 ; contains ABS sine values
\r
359 ; we don't store the sign as it confuses the multiplication.
\r
360 ; we can tell the sign very easily from whether the index is >128
\r
362 ALIGN &100 ; so we don't incur page-crossed penalties
\r
365 EQUB ABS(SIN(n/128*PI)) * 255
\r
369 \ ******************************************************************
\r
371 \ ******************************************************************
\r
376 EQUB (SIN(n/128*PI) + 1) / 2.0001 * 7 + 1
\r
380 \ ******************************************************************
\r
381 \ * multiplication tables
\r
382 \ ******************************************************************
\r
384 ; This is a very quick way to do multiplies, based on the fact that:
\r
386 ; (a+b)^2 = a^2 + b^2 + 2ab (I)
\r
387 ; (a-b)^2 = a^2 + b^2 - 2ab (II)
\r
389 ; (I) minus (II) yields: (a+b)^2 - (a-b)^2 = 4ab
\r
391 ; or, rewritten: ab = f(a+b) - f(a-b),
\r
392 ; where f(x) = x^2 / 4
\r
394 ; We build a table of f(x) here with x=0..511, and then can perform
\r
395 ; 8-bit * 8-bit by 4 table lookups and a 16-bit subtract.
\r
397 ; In this case, we will discard the low byte of the result, so we
\r
398 ; only need the high bytes, and can do just 2 table lookups and a
\r
399 ; simple 8-bit subtract.
\r
412 \ ******************************************************************
\r
414 \ ******************************************************************
\r
416 ; contains the phase of this dot
\r
420 FOR n, 0, numdots-1
\r
425 ; contains the y position of the dot
\r
426 ; the dots are sorted by y positions, highest on screen first - this means we can do
\r
427 ; 'raster chasing'!
\r
428 ; the y positions are also biased so there are fewer at the poles, and more at the equator!
\r
432 FOR n, 0, numdots-1
\r
433 x = (n - numdots/2 + 0.5) / (numdots/2)
\r
434 y = (x - SIN(x*PI) * 0.1) * radius
\r
439 ; contains the radius of the ball at this y position
\r
443 FOR n, 0, numdots-1
\r
444 x = (n - numdots/2 + 0.5) / (numdots/2)
\r
445 y = (x - SIN(x*PI) * 0.1) * radius
\r
446 r = SQR(radius*radius - y*y) / 2
\r
451 \ ******************************************************************
\r
452 \ * End address to be saved
\r
453 \ ******************************************************************
\r
458 \ ******************************************************************
\r
459 \ * Space reserved for tables but not initialised with anything
\r
460 \ * Therefore these are not saved in the executable
\r
461 \ ******************************************************************
\r
463 ; these store the screen address of the last dot
\r
464 ; at the end of the frame, we go through these tables, storing zeroes to
\r
465 ; all these addresses in order to delete the last frame
\r
468 .olddotaddrlo SKIP numdots
\r
471 .olddotaddrhi SKIP numdots
\r
474 .olddotaddry SKIP numdots
\r
478 \ ******************************************************************
\r
480 \ ******************************************************************
\r
482 SAVE "Code", start, end
\r