******************************** * * * Fast Apple II Graphics * * By Andy McFadden * * Version 0.3, Aug 2015 * * * * Circle rendering * * (Included by FDRAW.S) * * * * Developed with Merlin-16 * * * ******************************** * TODO: if USE_FAST is 0, replace the outline circle * plot code with calls to DrawPoint (or maybe a * common sub-function so we don't trash the input * parameters). Saves a little space. ******************************** * * Draw a circle. The radius is in in_rad, and * the center is at in_x0l+in_x0h,in_y0. * ******************************** DrawCircle lda #$20 ;JSR cmp _cp08 ;configured for outline? beq :okay jsr fixcplot :okay jmp calc_circle ******************************** * * Draw filled circle. * ******************************** FillCircle lda #$2c ;BIT cmp _cp08 ;configured for fill? beq :okay jsr fixcplot :okay jsr calc_circle jmp FillRaster * Calculate a circle, using Bresenham's algorithm. The * results are placed into the rasterization buffers. * * in_rad must be from 0 to 255. The x/y center * coordinates must be on the screen, but the circle * can extend off the edge. * * The computed values are stored in the rasterization * tables. For an outline circle, we also plot the * points immediately. do USE_FAST ;***** * local storage -- not used often enough to merit DP circ_8bit ds 1 circ_clip ds 1 fin ;***** calc_circle max_fast_rad equ 41 ]cxl equ zloc0 ]cxh equ zloc1 ]cy equ zloc2 ]dlo equ zloc3 ]dhi equ zloc4 ]xsav equ zloc5 ]ysav equ zloc6 ]min_x equ zloc7 ;min/max offsets from center ]max_x equ zloc8 ;(min is above center, max ]min_y equ zloc9 ; is below) ]max_y equ zloc10 ]hitmp equ zloc11 * only used by hplot for outline circles ]hbasl equ zptr0 ]andmask equ zloc11 ;overlaps with ]hitmp ]savxreg equ zloc12 ]savyreg equ zloc13 * Special-case radius=0. It removes an annoying * edge case (first y-- becomes 0xff, but 6502 cmp * is unsigned). lda in_rad bne :notzero ldy in_y0 sty rast_top sty rast_bottom lda in_x0l sta rastx0l,y sta rastx1l,y lda in_x0h sta rastx0h,y sta rastx1h,y rts * Use different version of function for small * circles, because we can do it all in 8 bits. :notzero do USE_FAST ;***** ldy #$01 cmp #max_fast_rad ;in_rad in Acc blt :use_fast dey :use_fast sty circ_8bit fin ;***** lda in_x0l ;copy center to DP for speed sta ]cxl lda in_x0h sta ]cxh lda in_y0 sta ]cy * Compute min/max values, based on offset from center. * These are compared against offset-from-center x/y. * We need tight bounds on Y because we use it to * compute the rast_render top/bottom. Getting tight * bounds on X is not so important, but we still need * it for the no-clip optimization. ldx #$04 ;count edges needing clip lda #NUM_ROWS-1 ;191 sec sbc ]cy ;maxY = 191-cy cmp in_rad blt :ylimok lda in_rad ;clamp to radius dex :ylimok sta ]max_y ;maxY = 191-cy lda ]cy ;minY = cy cmp in_rad blt :ylimok2 lda in_rad ;clamp to radius dex :ylimok2 sta ]min_y lda ]cxh beq :xlimlo * Examples (note # bad, must use rad * cx=24, 23-24=255 + carry clear --> ok, chk rad * cx=255, 23-255=24 + carry clear --> ok, chk rad :xlimlo lda # 255) ? cmp in_rad blt :xlimok2 lda in_rad ;clamp to radius dex :xlimok2 sta ]min_x :xlimdone do USE_FAST ;***** stx circ_clip fin ;***** * set top/bottom rows for rasterizer lda ]cy clc adc ]max_y sta rast_bottom lda ]cy sec sbc ]min_y sta rast_top DO 0 ;debug debug debug LDA ]min_x ;save a copy where the STA $0380 ; monitor won't trash it LDA ]max_x STA $0381 LDA ]min_y STA $0382 LDA ]max_y STA $0383 FIN * Set initial conditions for Bresenham. ldx #0 ;:x = 0 stx ]xsav ldy in_rad ;:y = rad sty ]ysav lda #1 ;:d = 1 - rad sec sbc ]ysav ;in_rad sta ]dlo bcs :hizero ;C==1 if in_rad<=1 ldx #$ff ;C was 0, make neg :hizero stx ]dhi * * Outer loop -- plot 8 points, then update values. * circ_loop do USE_FAST ;***** lda circ_clip beq ncypy jmp with_clip * Quick version, no clipping required * row cy+y: cx-x and cx+x ncypy lda ]ysav clc adc ]cy tay ;y-coord in Y-reg lda ]cxl sec sbc ]xsav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp00 jsr cplotl lda ]cxl clc adc ]xsav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp01 jsr cplotrn * row cy-y: cx-x and cx+x ncymy lda ]cy sec sbc ]ysav tay ;y-coord in Y-reg lda ]cxl sec sbc ]xsav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp02 jsr cplotl lda ]cxl clc adc ]xsav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp03 jsr cplotrn * row cy+x: cx-y and cx+y ncypx lda ]xsav ;off bottom? clc adc ]cy tay ;y-coord in Y-reg lda ]cxl sec sbc ]ysav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp04 jsr cplotl lda ]cxl clc adc ]ysav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp05 jsr cplotrn * row cy-x: cx-y and cx+y ncymx lda ]cy sec sbc ]xsav tay ;y-coord in Y-reg lda ]cxl sec sbc ]ysav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp06 jsr cplotl lda ]cxl clc adc ]ysav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp07 jsr cplotrn * CLICK jmp circ_plot_done fin ;***** (USE_FAST) * * Same thing, but this time clipping edges. * with_clip * row cy+y: cx-x and cx+x ccypy lda ]ysav ;off bottom? cmp ]max_y beq :cypy_ok bge cypy_skip ;completely off screen :cypy_ok clc adc ]cy tay ;y-coord in Y-reg ldx ]xsav ;handle cx-x cpx ]min_x blt :cxmx_ok beq :cxmx_ok lda #0 ;clip at 0 sta rastx0l,y sta rastx0h,y beq cxmx_done0 ;always BREAK :cxmx_ok lda ]cxl sec sbc ]xsav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp08 jsr cplotl cxmx_done0 cpx ]max_x ;handle cx+x blt :cxpx_ok beq :cxpx_ok lda #NUM_COLS-1 sta rastx1h,y bne cxpx_done0 ;always BREAK :cxpx_ok lda ]cxl clc adc ]xsav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp09 jsr cplotr cxpx_done0 cypy_skip * row cy-y: cx-x and cx+x ccymy lda ]ysav ;off top? cmp ]min_y beq :cymy_ok bge cymy_skip :cymy_ok lda ]cy sec sbc ]ysav tay ;y-coord in Y-reg ldx ]xsav ;handle cx-x cpx ]min_x blt :cxmx_ok beq :cxmx_ok lda #0 ;clip at 0 sta rastx0l,y sta rastx0h,y beq cxmx_done1 ;always BREAK :cxmx_ok lda ]cxl sec sbc ]xsav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp10 jsr cplotl cxmx_done1 cpx ]max_x ;handle cx+x blt :cxpx_ok beq :cxpx_ok lda #NUM_COLS-1 sta rastx1h,y bne cxpx_done1 ;always BREAK :cxpx_ok lda ]cxl clc adc ]xsav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp11 jsr cplotr cxpx_done1 cymy_skip * row cy+x: cx-y and cx+y ccypx lda ]xsav ;off bottom? cmp ]max_y beq :cypx_ok bge cypx_skip :cypx_ok clc adc ]cy tay ;y-coord in Y-reg ldx ]ysav ;handle cx-y cpx ]min_x blt :cxmy_ok beq :cxmy_ok lda #0 ;clip at 0 sta rastx0l,y sta rastx0h,y beq cxmy_done2 ;always BREAK :cxmy_ok lda ]cxl sec sbc ]ysav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp12 jsr cplotl cxmy_done2 cpx ]max_x ;handle cx+y blt :cxpy_ok beq :cxpy_ok lda #NUM_COLS-1 sta rastx1h,y bne cxpy_done2 ;always BREAK :cxpy_ok lda ]cxl clc adc ]ysav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp13 jsr cplotr cxpy_done2 cypx_skip * row cy-x: cx-y and cx+y ccymx lda ]xsav ;off top? cmp ]min_y beq :cymx_ok bge cymx_skip :cymx_ok lda ]cy sec sbc ]xsav tay ;y-coord in Y-reg ldx ]ysav ;handle cx-y cpx ]min_x blt :cxmy_ok beq :cxmy_ok lda #0 ;clip at 0 sta rastx0l,y sta rastx0h,y beq cxmy_done3 ;always BREAK :cxmy_ok lda ]cxl sec sbc ]ysav sta rastx0l,y lda ]cxh sbc #$00 sta rastx0h,y _cp14 jsr cplotl cxmy_done3 cpx ]max_x ;handle cx+y blt :cxpy_ok beq :cxpy_ok lda #NUM_COLS-1 sta rastx1h,y bne cxpy_done3 ;always BREAK :cxpy_ok lda ]cxl clc adc ]ysav sta rastx1l,y lda ]cxh adc #$00 sta rastx1h,y _cp15 jsr cplotr cxpy_done3 cymx_skip circ_plot_done * Update X/Y/D. Up to about radius=41 we can maintain * 'd' in an 8-bit register. do USE_FAST ;***** lda circ_8bit beq circ_slow * * Bresenham update, with 8-bit 'd'. * ldx ]xsav lda ]dlo bmi :dneg txa ;:d = d + ((x-y)*4) +5 sec sbc ]ysav ;x <= y, may be neg or 0 asl asl clc ;can't know carry adc #5 clc ;still don't want carry adc ]dlo sta ]dlo dec ]ysav ;:y-- jmp :loopbot :dneg txa ;:d = d + (x*4) +3 asl asl ;x always pos, C=0 DO 0 BCC :TEST ;debug BREAK ;debug :TEST ;debug FIN adc #3 adc ]dlo sta ]dlo :loopbot inx ;:x++ stx ]xsav cpx ]ysav beq :again bge circ_done :again jmp circ_loop fin ;***** * * Bresenham update, with 16-bit 'd' * circ_slow CLICK ldx ]xsav lda ]dhi bmi :dneg lda ]dlo clc adc #5 sta ]dlo bcc :noinc inc ]dhi :noinc txa ;:d = d + ((x-y)*4) +5 ldy #$00 sty ]hitmp sec sbc ]ysav ;x <= y, may be neg or 0 beq :xeqy ;if x==y, nothing to add ldy #$ff sty ]hitmp asl rol ]hitmp asl rol ]hitmp clc adc ]dlo sta ]dlo lda ]dhi adc ]hitmp sta ]dhi :xeqy dec ]ysav ;:y-- jmp :loopbot :dneg lda ]dlo ;:d = d + (x*4) + 3 clc adc #3 sta ]dlo bcc :noinc2 inc ]dhi :noinc2 txa ldy #0 ;x always positive sty ]hitmp asl rol ]hitmp asl rol ]hitmp clc ;not needed? adc ]dlo sta ]dlo lda ]dhi adc ]hitmp sta ]dhi :loopbot inx ;:x++ stx ]xsav cpx ]ysav beq :again bge circ_done :again jmp circ_loop circ_done rts * Plot a point for outline circle rendering. * * X and Y must be preserved. Y holds the current line * number. * * Most DP locations are in use -- see the variable * declarations at the start of the circle function. * cplotl is the entry point for the leftmost point. cplotl stx ]savxreg sty ]savyreg lda ylooklo,y sta ]hbasl lda ylookhi,y _pg_or2 ora #$20 sta ]hbasl+1 * Convert the X coordinate into byte/bit. ldx rastx0l,y ;x coord, lo lda rastx0h,y ;>= 256? beq :lotabl ;no, use the low table ldy div7hi,x lda mod7hi,x bpl cplotcom ;always BREAK ;debug :lotabl ldy div7lo,x lda mod7lo,x jmp cplotcom * cplotr is the entry point for the rightmost point. * We use rastx1 instead of rastx0. cplotr lda ylooklo,y sta ]hbasl lda ylookhi,y _pg_or3 ora #$20 sta ]hbasl+1 * If we just plotted the left point on the same line, * we can skip the Y-lookup by jumping here. cplotrn stx ]savxreg sty ]savyreg ldx rastx1l,y ;x coord, lo lda rastx1h,y ;>= 256? beq :lotabl ;no, use the low table ldy div7hi,x lda mod7hi,x bpl cplotcom ;always BREAK ;debug :lotabl ldy div7lo,x lda mod7lo,x * Plot the point. The byte offset (0-39) is in Y, * the bit offset (0-6) is in A. cplotcom tax lda colorline,y ;start with color pattern eor (]hbasl),y ;flip all bits and andmask,x ;clear other bits eor (]hbasl),y ;restore ours, set theirs sta (]hbasl),y ldx ]savxreg ldy ]savyreg rts * Reconfigure calc_circle to either JSR to cplotl/r, * or just BIT the address (a 4-cycle no-op). The * desired instruction is in A. fixcplot do USE_FAST ;***** sta _cp00 sta _cp01 sta _cp02 sta _cp03 sta _cp04 sta _cp05 sta _cp06 sta _cp07 fin ;***** sta _cp08 sta _cp09 sta _cp10 sta _cp11 sta _cp12 sta _cp13 sta _cp14 sta _cp15 rts