[comp.sys.atari.8bit] 3D Stars!

brett@pigpen (Brett S Bourbin) (08/31/88)

Here is the demo that was talking about before. Sorry for the delay, but I
had to retype the whole thing into my UNIX system, since I do not have my 850
interface anymore.

Please tell me what you think.  Again, it is a RAW demo, it has no sounds or
bells or whistles.  Maybe when I release the demo where the stars start to
rotate according to the joystick and.. whoops, I guess I shouldn't talk about
that yet! 8^)


;
; Three-D Scrolling Stars Demo
;
; Version 1.0-Atari800
; Written by Brett Bourbin, Selgus Limited.
;
; This program is released into the public domain.  It may be copied, used,
; hacked to death, as long as you don't try to claim ownership of the code.
;
; This version was written for the MAC/65 assembler, but I did not use any
; of the special features, so it should work fine under the Atari Assem/Ed.
;
sdlstl	=	$0230	; display list shadow register
colpf0	=	$02c4	; playfield shadow color register 0
colbk	=	$02c8	; background shadow color register
;
dlist	=	$3800	; space for the custom display list
screen	=	$4000	; space for the screen memory
code	=	$5100	; guess what this is the starting address for? 8^)
;
random	=	$d20a	; random number register
vcount	=	$d40b	; vertical line counter register
;
stnum	=	12	; the number of stars on the screen as a time
;
;
	*=	$80	; page zero usage
starx	*=	*+stnum	; star X co-ordinates
stary	*=	*+stnum	; star Y co-ordinates
starz	*=	*+stnum	; star Z co-ordinates
oldx	*=	*+stnum	; old screen locations for erase routine
oldy	*=	*+stnum
plotx	*=	*+stnum	; new star locations
ploty	*=	*+stnum
color	*=	*+1	; star color
yofset	*=	*+1	; Y offset used to plot the star
pixel	*=	*+1	; more plotting storage
limit	*=	*+1	; random number upper limit
lo	*=	*+1	; page zero pointer
hi	*=	*+1
temp	*=	*+1
mplier	*=	*+1	; multiplication registers
mcand	*=	*+1
divsor	*=	*+1	; division register
starnm	*=	*+1	; current star number working on
;
;
	*=	code	; start of the code section (like you didn't know)
;
bits	.byte	$40,$10,$04,$01	; star pixel offsets inside a screen byte
;
lotabl	*=	*+$60	; table to hold screen memory line locations
hitabl	*=	*+$60
;
;
; Get a random number from zero to the value passed in the ACCUM.
; This routine could be better, but is not needed for a demo. 8^)
rand	sta	limit	; save the upper limit
rnd	lda	random	; get the hardware random number
	cmp	limit	; check against the limit
	bcs	rnd	; loop back until it is within the limit
	rts
;
; Clear out the screen memory.
clear	lda	# <screen	; get the address of the screen memory
	ldy	# >screen
	sta	lo	; save in page zero pointer
	sty	hi
	ldx	#$0f	; initialize the number of 256 byte pages
nextpg	ldy	#0	; initialize index
	tya		; copy zero into the ACCUM
clrpg	sta	(lo),y	; clear out the memory
	dey
	bne	clrpg	; go through the total page
	inc	hi	; increase the pointer to the next page
	dex		; decrease the page counter
	bne	nextpg	; clear all the pages needed
	rts
;
; Initialize the screen memory line address table and create the custom
; display list.  A table is used to keep the addresses of each line of the
; display so that no math is needed to find the correct Y index into the
; screen memory at plot time. (ie. no need to multiply Y co-ordinate by 40)
initad	lda	# <screen	; get the address of the screen memory
	ldy	# >screen
	sta	lo	; save in page zero pointer
	sty	hi
	ldy	#0	; initialize the index register
initlp	lda	lo	; get the LSB of the pointer
	sta	lotabl,y	; save in the table
	lda	hi	; do the same for the MSB of the pointer
	sta	hitabl,y
	lda	#40	; get the number of bytes per line
	clc
	adc	lo	; add to the lo pointer
	sta	lo
	lda	#0	; clear the ACCUM
	adc	hi	; catch any carry overflow
	sta	hi
	iny		; increase the line index
	cpy	#$5f	; check if we are at the end
	bcc	initlp	; no, do the next line
;
	ldx	#$70	; number of lines to initialized
	lda	#$0d	; mode 13 OPCODE for ANTIC
makedl	sta	dlist,x	; save in the display list memory
	dex
	bne	makedl	; initialize the whole block
	lda	#$70	; 8 blank line OPCODE for ANTIC
	sta	dlist	; make top 24 lines blank in display list
	sta	dlist+1
	sta	dlist+2
	lda	#$4d	; mode 13 OPCODE plus a reload memory latch
	sta	dlist+3
	lda	# <screen	; save the address of the screen memory
	sta	dlist+4	; into the display list
	lda	# >screen
	sta	dlist+5
	lda	#$41	; jump at end of display list OPCODE
	sta	dlist+99
	lda	# <dlist	; save the start address of the list
	sta	dlist+100
	lda	# >dlist
	sta	dlist+101
	rts
;
; Mulitply the absoulte value of the ACCUM by the value passed in the Y
; register. The result is returned in ACCUM (LSB) and Y (MSB) registers.
mult	ldx	#0	; initialize the sign value
	cmp	#0	; check the value in the ACCUM
	bpl	posgn	; it was positive
	eor	#$ff	; take the one's complement
	clc
	adc	#1	; make into the two's complement
	dex		; change sign value to -1
posgn	sta	mcand	; save the arguments passed for the multiply function
	sty	mplier
	lda	#0
	sta	temp	; initialize the temporary storage
	ldy	#8	; initialize the bit counter
mult1	asl	temp	; shift out a bit from the past result
	rol	mplier	; rotate it in
	bcc	mult2	; wasn't set so no need to update
	lda	temp
	clc
	adc	mcand	; add in the multiplicand
	sta	temp	; save the new value back
	lda	mplier	; get the multiplier
	adc	#0	; catch any carry overflow
	sta	mplier
mult2	dey		; try the next bit
	bne	mult1
	lda	temp	; get the LSB of the result
	ldy	mplier	; and the MSB
	rts
;
; Divide the value in DIVSOR (two bytes) by the value passed in the ACCUM.
; The result is an 8 bit value returned in the ACCUM.
divide	sta	temp	; save the value passed
	lda	divsor+1	; get the MSB of the divisor
	ldy	#8	; initialize the bit counter
div1	asl	divsor	; shift the divsor LSB
	rol	a	; rotate with the MSB of the divisor
	cmp	temp	; test the LSB
	bcc	div2	; no need to increase the result
	sbc	temp	
	inc	divsor	; increase the result register
div2	dey		; do the next bit
	bne	div1
	lda	divsor	; get the result
	rts
;
; Create a new random star location with the ranges:
;	40 <= X <= 50  |  Y = RND(X+1) - X/2  ! Z = RND(X+1) - X/2
; The X register holds the current star number as an index.
newpt	lda	#11	; set upper limit of the X co-ordinate
	jsr	rand	; get the random number
	clc
	adc	#40	; add 40 to it
	sta	starx,x	; save the co-ordinate
	sta	temp	; save a copy for more calculations
	clc
	adc	#1	; add one to the random number
	jsr	rand	; get a random number from 0 to X+1
	lsr	temp	; divide the saved value by two
	sec
	sbc	temp	; subtract from the new random number
	sta	stary,x	; save the co-ordinate
	jsr	rnd	; use the same upper limit as before
	sec
	sbc	temp	; subtract from the new random number
	sta	starz,x	; save the co-ordinate
	rts
;
; Main code of the stars demo.  This part of the program does the basic
; setup and initialization, before going into the actual star movements.
stars	lda	#0	; get a zero for clearing purposes
	ldx	#$7f	; initialize index
clrpg0	sta	$80,x	; clear out bottom half of page zero
	dex
	bpl	clrpg0	; loop through all locations
	sta	colbk	; change background color to black
	jsr	initad	; initialize the screen memory line addresses
	jsr	clear	; clear out screen memory
	lda	# <dlist	; save the start address of the display list
	ldy	# >dlist
	sta	sdlstl	; save in the shadow registers
	sty	sdlstl+1
	lda	#$0e	; make the stars white
	sta	colpf0	; save in playfield color register 0
;
	ldx	#stnum-1	; get the number of stars to initialize
initst	jsr	newpt	; create new co-ordinates for the star
	dex		; decrease the star counter
	bpl	initst
;
showst	ldx	#stnum-1	; get the number of stars to move
nextst	stx	starnm	; save the value for later
;
; First part is to check if the star is still on the screen.  The way I do
; this is by checking if |Y| <= X AND |Z| <= X. If not, then a new point
; is created.
	lda	stary,x	; get the Y co-ordinate
	bpl	chkof1	; it is already positive, no need for ABS function
	eor	#$ff	; take the one's complement
	clc
	adc	#1	; make into the two's complement
chkof1	cmp	starx,x	; compare it to the X co-ordinate
	beq	notgn1	; still on the screen
	bcs	getnew	; star off the screen, must create a new one
notgn1	lda	starz,x	; get the Z co-ordinate
	bpl	chkof2	; it is already positive, no need for ABS function
	eor	#$ff	; take the one's ... well, you know! 8^)
	clc
	adc	#1
chkof2	cmp	starx,x	; compare it to the X co-ordinate
	beq	notgn2	; still on the screen
	bcc	notgn2	; once again, still on the screen
getnew	jsr	newpt	; off the screen, create a new star
;
; Now, once I know it is still on the screen, I use a perspective formula to
; transfer the 3D image to a 2D screen co-ordinate.  The formula that I am
; using is X' = 79 * (D * Y / X) + 79  and Y' = 47 * (D * Z / X) + 47 where
; D equals 1 (D is the focal point).
notgn2	lda	stary,x	; get the Y co-ordinate
	ldy	#79	; get the middle screen X co-ordinate
	jsr	mult	; multiply them together
	sta	divsor	; use the result as the divisor in the division
	sty	divsor+1
	ldy	starnm	; get the star number as an index
	lda	starx,y	; get the X co-ordinate
	jsr	divide	; divide by the X co-ordinate
	cpx	#0	; check the sign of the result
	bpl	ncomp1	; no need for the absolute value
	eor	#$ff	; you must know the procedure by now! 8^)
	clc
	adc	#1
ncomp1	clc
	adc	#79	; add the offset to 0,0
	ldx	starnm	; get the star index once again
	sta	plotx,x	; save the new star screen location
	lda	starz,x	; get the Z co-ordinate
	ldy	#47	; get the middle screen Y co-ordinate
	jsr	mult	; multiply them together
	sta	divsor	; do all the same stuff as above (OK, I am lazy! 8^) )
	sty	divsor+1
	ldy	starnm
	lda	starx,y
	jsr	divide
	cpx	#0
	bpl	ncomp2
	eor	#$ff
	clc
	adc	#1
ncomp2	sta	temp	; save for a second
	lda	#47	; get the offset to 0,0
	sec
	sbc	temp	; subtract our number from it
	ldx	starnm	; get our star index
	sta	ploty,x	; save the new star screen location
;
	dec	starx,x	; now move the star by changing the X co-ordinate
	bne	skip1
	jsr	newpt	; if it hits zero, then it is off the screen
skip1	dex		; decrease the star counter
	bpl	nextst	; calculate all the positions
;
; Next, we wait for the TOF (top-of-frame) to occur so we have a nice display.
waitlp	lda	vcount	; get the vertical line counter
	cmp	#$7a	; at the bottom of the screen yet?
	bne	waitlp	; no, keep in a tight loop
sameln	cmp	vcount	; check against the current vertical line
	beq	sameln	; wait for the next line to be drawn
;
; After that, we must erase all the old stars on the screen before anything
; new can be plotted.
	ldx	#stnum-1	; get the number of stars to erase
erase	ldy	oldy,x	; get the old Y co-ordinate
	lda	lotabl,y	; get the address of that screen line
	sta	lo	; save in a page zero pointer
	lda	hitabl,y
	sta	hi
	ldy	oldx,x	; get the old X co-ordinate
	lda	#0	; clear out the whole byte, no need to mask bits
	sta	(lo),y	; save back in screen memory
	dex		; erase the next star
	bpl	erase
;
; Finally, here is where we plot the new stars on the screen.  I am using a
; 160 by 96 line screen which has four pixels per byte in screen memory.  I
; mask in the new pixel for each star and then I am done, since each star is
; just one pixel long and wide.
	ldx	#stnum-1	; get the number of stars to plot
plotit	lda	ploty,x	; get the Y location
	sta	oldy,x	; save for the erase routine
	tay		; make a copy for an index
	lda	lotabl,y	; get the address of that screen line
	sta	lo	; save in a page zero pointer
	lda	hitabl,y
	sta	hi
	lda	plotx,x	; get the X co-ordinate
	and	#3	; keep only 2 least significant bits
	tay		; use as in index
	lda	bits,y	; get the correct bit pattern
	sta	pixel	; save in a temporary register
	lda	plotx,x	; get the X again [could use PHA/PLA but its a demo!]
	lsr	a	; divide by four
	lsr	a
	sta	oldx,x	; save as the new X location
	tay		; use as the index into the current line
	lda	(lo),y	; get the screen byte
	ora	pixel	; mask in the pixel bitmap
	sta	(lo),y	; save back in screen memory
	dex		; plot the next star
	bpl	plotit
;
	jmp	showst	; redo all the calculations from the start
;
	*=	$02e0	; set the run address
	.word	stars	; the first routine that is executed
	.end


- Brett 
 __  __   _  __  _
|  ||  | / ||  || \   Brett S Bourbin
|  ||  ||  ||  ||  |  INTERNET: brett@PIGPEN.UMD.EDU
|  ||  ||  ||  ||  |
 \_||_/ |__||__||__|  Instructional Computing Programs    
     College Park