********************************
*                              *
* Fast Apple II Graphics       *
* By Andy McFadden             *
* Version 0.3, Aug 2015        *
*                              *
* Point and line functions     *
* (Included by FDRAW.S)        *
*                              *
* Developed with Merlin-16     *
*                              *
********************************


********************************
*
* Draw a single point in the current color.
*
********************************
DrawPoint
]hbasl       equ   zptr0

             ldy   in_y0
             lda   ylooklo,y
             sta   ]hbasl
             lda   ylookhi,y
             ora   g_page
             sta   ]hbasl+1

             ldx   in_x0l       ;x coord, lo
             lda   in_x0h       ;>= 256?
             beq   :lotabl      ;no, use the low table
             ldy   div7hi,x
             lda   mod7hi,x
             bpl   :plotit      ;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.
:plotit
             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
             rts


********************************
*
* Draw a line between two points.
*
********************************
DrawLine

]hbasl       equ   zptr0
]xposl       equ   zloc0        ;always left edge
]xposh       equ   zloc1
]ypos        equ   zloc2        ;top or bottom
]deltaxl     equ   zloc3
]deltaxh     equ   zloc4
]deltay      equ   zloc5
]count       equ   zloc6
]counth      equ   zloc7
]diff        equ   zloc8
]diffh       equ   zloc9
]andmask     equ   zloc10
]wideflag    equ   zloc11       ;doesn't really need DP

* We use a traditional Bresenham run-length approach.
* Run-slicing is possible, but the code is larger
* and the increased cost means it's only valuable
* for longer lines.  An optimal solution would switch
* approaches based on line length.
*
* Start by identifying where x0 or x1 is on the
* left.  To make life simpler we always work from
* left to right, flipping the coordinates if
* needed.
*
* We also need to figure out if the line is more
* than 255 pixels long -- which, because of
* inclusive coordinates, means abs(x0-x1) > 254.
             lda   in_x1l       ;assume x0 on left
             sec
             sbc   in_x0l
             tax
             beq   checkvert    ;low bytes even, check hi
             lda   in_x1h
             sbc   in_x0h
             bcs   lx0left

* x1 is on the left, so the values are negative
* (hi byte in A, lo byte in X)
lx0right     eor   #$ff         ;invert hi
             sta   ]deltaxh     ;store
             txa
             eor   #$ff         ;invert lo
             sta   ]deltaxl
             inc   ]deltaxl     ;add one for 2s complement
             bne   :noinchi     ;rolled into high byte?
             inc   ]deltaxh     ;yes
:noinchi     lda   in_x1l       ;start with x1
             sta   ]xposl
             lda   in_x1h
             sta   ]xposh
             lda   in_y1
             sta   ]ypos
             sec
             sbc   in_y0        ;compute deltay
             jmp   lncommon

checkvert
             lda   in_x1h       ;diff high bytes
             sbc   in_x0h       ;(carry still set)
             blt   lx0right     ;width=256, x0 right
             bne   lx0left      ;width=256, x0 left
             jmp   vertline     ;all zero, go vert

* (branch back from below)
* This is a purely horizontal line.  We farm the job
* out to the raster fill code for speed.  (There's
* no problem with the line code handling it; its just
* more efficient to let the raster code do it.)
phorizontal
             ldy   ]ypos
             sty   rast_top
             sty   rast_bottom
             lda   ]xposl
             sta   rastx0l,y
             clc
             adc   ]deltaxl     ;easier to add delta back
             sta   rastx1l,y    ; in than sort out which
             lda   ]xposh       ; arg is left vs. right
             sta   rastx0h,y
             adc   ]deltaxh
             sta   rastx1h,y
             jmp   FillRaster

* x0 is on the left, so the values are positive
lx0left      stx   ]deltaxl
             sta   ]deltaxh
             lda   in_x0l       ;start with x0
             sta   ]xposl
             lda   in_x0h
             sta   ]xposh
             lda   in_y0        ;and y0
             sta   ]ypos
             sec
             sbc   in_y1        ;compute deltay

* Value of (starty - endy) is in A, flags still set.
lncommon
             bcs   :posy
             eor   #$ff         ;negative, invert
             adc   #$01
             sta   ]deltay
             lda   #$e8         ;INX
             bne   gotdy
:posy
_lmb         beq   phorizontal
             sta   ]deltay
             lda   #$ca         ;DEX
gotdy        sta   _hmody
             sta   _vmody
             sta   _wmody

             do    0            ;***** for regression test
             ldx   #$01
             lda   ]deltaxh
             bne   :iswide
             lda   ]deltaxl
             cmp   #$ff         ;== 255?
             beq   :iswide
             ldx   #$00         ;notwide
:iswide      stx   $300
             lda   ]xposl
             sta   $301
             lda   ]xposh
             sta   $302
             lda   ]ypos
             sta   $303
             ldx   ]deltaxl
             stx   $304
             ldx   ]deltaxh
             stx   $305
             ldx   ]deltay
             stx   $306
             lda   _hmody
             and   #$20         ;nonzero means inc,
             sta   $307         ; zero means dec
             fin                ;*****

* At this point we have the initial X position in
* ]startxl/h, the initial Y position in ]starty,
* deltax in ]deltaxl, deltay in ]deltay, and we've
* tweaked the Y-update instructions to either INC or
* DEC depending on the direction of movement.
*
* The next step is to decide whether the line is
* horizontal-dominant or vertical-dominant, and
* branch to the appropriate handler.
*
* The core loops for horiz and vert take about
* 80 cycles when moving diagonally, and about
* 20 fewer when moving in the primary direction.
* The wide-horiz is a bit slower.
             ldy   #$01         ;set "wide" flag to 1
             lda   ]deltaxl
             ldx   ]deltaxh
             bne   horzdom      ;width >= 256
             cmp   #$ff         ;width == 255
             beq   horzdom
             dey                ;not wide
             cmp   ]deltay
             bge   horzdom      ; for diagonal lines
             jmp   vertdom

* We could special-case pure-diagonal lines here
* (just BEQ a couple lines up).  It does
* represent our worst case.  I'm not convinced
* we'll see them often enough to make it worthwhile.


* horizontal-dominant
horzdom
             sty   ]wideflag
             sta   ]count       ;:count = deltax + 1
             inc   ]count
             lsr                ;:diff = deltax / 2
             sta   ]diff

* set Y to the byte offset in the line
* load the AND mask into ]andmask
             ldx   ]xposl
             lda   ]xposh       ;>= 256?
             beq   :lotabl      ;no, use the low table
             ldy   div7hi,x
             lda   mod7hi,x
             bpl   :gottab      ;always
* BREAK ;debug
:lotabl      ldy   div7lo,x
             lda   mod7lo,x
:gottab
             tax
             lda   andmask,x
             sta   ]andmask

* Set initial value for line address.
             ldx   ]ypos
             lda   ylooklo,x
             sta   ]hbasl
             lda   ylookhi,x
             ora   g_page
             sta   ]hbasl+1

             lda   ]wideflag    ;is this a "wide" line?
             beq   :notwide     ;nope, stay local
             jmp   widedom

:notwide     lda   colorline,y  ;set initial color mask
             sta   _hlcolor+1
             jmp   horzloop

hrts         rts

* bottom of loop, essentially
hnoroll      sta   ]diff        ;3
hdecc        dec   ]count       ;5 :count--
             beq   hrts         ;2 :while (count != 0)
                                ;= 7 or 10

* We keep the byte offset in the line in Y, and the
* line index in X, for the entire loop.
horzloop
_hlcolor     lda   #$00         ;2 start with color pattern
_lmdh        eor   (]hbasl),y   ;5 flip all bits
             and   ]andmask     ;3 clear other bits
             eor   (]hbasl),y   ;5 restore ours, set theirs
             sta   (]hbasl),y   ;6 = 21

* Move right.  We shift the bit mask that determines
* the pixel.  When we shift into bit 7, we know it's
* time to advance another byte.
*
* If this is a shallow line we would benefit from
* keeping the index in X and just doing a 4-cycle
* indexed load to get the mask. Not having the
* line number in X makes the line calc more
* expensive for steeper lines though.
             lda   ]andmask     ;3
             asl                ;2 shift, losing hi bit
             eor   #$80         ;2 set the hi bit
             bne   :noh8        ;3 cleared hi bit?
* We could BEQ away and branch back in, but this
* happens every 7 iterations, so on average it's
* a very small improvement.  If we happen to branch
* across a page boundary the double-branch adds
* two more cycles and we lose.
             iny                ;2 advance to next byte
             lda   colorline,y  ;4 update color mask
             sta   _hlcolor+1   ;4
             lda   #$81         ;2 reset
:noh8        sta   ]andmask     ;3 = 13 + ((12-1)/7) = 14

* Update error diff.
             lda   ]diff        ;3
             sec                ;2
             sbc   ]deltay      ;3 :diff -= deltay
             bcs   hnoroll      ;2+ :if (diff < 0) ...
                                ;= 11 level, 10 up/down
             adc   ]deltaxl     ;3 :  diff += deltax
             sta   ]diff        ;3
_hmody       inx                ;2 :  ypos++ (or --)
             lda   ylooklo,x    ;4 update hbasl after line
             sta   ]hbasl       ;3  change
             lda   ylookhi,x    ;4
_pg_or4      ora   #$20         ;2
             sta   ]hbasl+1     ;3
             bne   hdecc        ;3 = +27 this path -> 37
             BREAK
* horizontal: 10+21+14+11=56 cycles/pixel
* diagonal:   7+21+14+37=79 cycles/pixel


* Vertical-dominant line.  Could go up or down.
vertdom
             ldx   in_y0
             cpx   ]ypos        ;starting at y0?
             bne   :endy0       ;yup
             ldx   in_y1        ;nope
:endy0       stx   _vchk+1      ;end condition

             lda   ]deltay
             lsr
             sta   ]diff        ;:diff = deltay / 2

* set Y to the byte offset in the line
* load the AND mask into ]andmask
             ldx   ]xposl
             lda   ]xposh       ;>= 256?
             beq   :lotabl      ;no, use the low table
             ldy   div7hi,x
             lda   mod7hi,x
             bpl   :gottab      ;always
             BREAK              ;debug
:lotabl      ldy   div7lo,x
             lda   mod7lo,x
:gottab
             tax
             lda   andmask,x    ;initial pixel mask
             sta   ]andmask

             lda   colorline,y  ;initial color mask
             sta   _vlcolor+1

             ldx   ]ypos
             jmp   vertloop

* We keep the byte offset in the line in Y, and the
* line index in X, for the entire loop.

* Bottom of loop, essentially.
vnoroll      sta   ]diff        ;3

vertloop
             lda   ylooklo,x    ;4
             sta   ]hbasl       ;3
             lda   ylookhi,x    ;4
_pg_or5      ora   #$20         ;2
             sta   ]hbasl+1     ;3 = 16

_vlcolor     lda   #$00         ;2 start with color pattern
_lmdv        eor   (]hbasl),y   ;5 flip all bits
             and   ]andmask     ;3 clear other bits
             eor   (]hbasl),y   ;5 restore ours, set theirs
             sta   (]hbasl),y   ;6 = 21

_vchk        cpx   #$00         ;2 was this last line?
             beq   vrts         ;2 yes, done
_vmody       inx                ;2 :ypos++ (or --)

* Update error diff.
             lda   ]diff        ;3
             sec                ;2
             sbc   ]deltaxl     ;3 :diff -= deltax
             bcs   vnoroll      ;2 :if (diff < 0) ...
                                ;= 10 vert, 9 move right

             adc   ]deltay      ;3 :  diff += deltay
             sta   ]diff        ;3
* Move right.  We shift the bit mask that determines
* the pixel.  When we shift into bit 7, we know it's
* time to advance another byte.
             lda   ]andmask     ;3
             asl                ;2 shift, losing hi bit
             eor   #$80         ;2 set the hi bit
             beq   :is8         ;2+ goes to zero on 8th bit
             sta   ]andmask     ;3
             bne   vertloop     ;3 = 21 + (18/7) = 24
             BREAK

:is8         iny                ;2 advance to next byte
             lda   colorline,y  ;4 update color
             sta   _vlcolor+1   ;4
             lda   #$81         ;2 reset
             sta   ]andmask     ;3
             bne   vertloop     ;3 = 18
             BREAK
vrts         rts
* vertical: 3 + 16 + 21 + 6 + 10 = 56 cycles
* diagonal: 16 + 21 + 6 + 9 + 24 = 76 cycles


* "Wide" horizontally-dominant loop.  We have to
* maintain error-diff and deltax as 16-bit values.
* Most of the setup from the "narrow" version carried
* over, but we have to re-do the count and diff.
*
* Normally we set count to (deltax + 1) and decrement
* to zero, but it's actually easier to set it equal
* to deltax and check for -1.
widedom
             lda   ]deltaxh     ;:count = deltax
             sta   ]counth
             ldx   ]deltaxl
             stx   ]count
             stx   ]diff
             lsr                ;:diff = deltax / 2
             ror   ]diff
             sta   ]diffh
             ldx   ]ypos

             lda   colorline,y  ;set initial color mask
             sta   _wlcolor+1

* We keep the byte offset in the line in Y, and the
* line index in X, for the entire loop.
wideloop
_wlcolor     lda   #$00         ;2 start with color pattern
_lmdw        eor   (]hbasl),y   ;5 flip all bits
             and   ]andmask     ;3 clear other bits
             eor   (]hbasl),y   ;5 restore ours, set theirs
             sta   (]hbasl),y   ;6 = 21

* Move right.  We shift the bit mask that determines
* the pixel.  When we shift into bit 7, we know it's
* time to advance another byte.
             lda   ]andmask     ;3
             asl                ;2 shift, losing hi bit
             eor   #$80         ;2 set the hi bit
             bne   :not7        ;3 goes to zero on 8th bit
             iny                ; 2 advance to next byte
             lda   colorline,y  ; 4 update color mask
             sta   _hlcolor+1   ; 4
             lda   #$81         ; 2 reset
:not7        sta   ]andmask     ;3 = 13 usually, 25 every 7

* Update error diff, which is a positive number.  If
* it goes negative ("if (diff < 0)") we act.
             lda   ]diff
             sec
             sbc   ]deltay      ;:diff -= deltay
             bcs   wnoroll      ;didn't even roll low byte
             dec   ]diffh       ;check hi byte
             bpl   wnoroll      ;went 1->0, keep going

             adc   ]deltaxl     ;:  diff += deltax
             sta   ]diff
             lda   ]diffh
             adc   ]deltaxh
             sta   ]diffh
_wmody       inx                ;:  ypos++ (or --)
             lda   ylooklo,x    ;update hbasl after line
             sta   ]hbasl       ; change
             lda   ylookhi,x
_pg_or6      ora   #$20
             sta   ]hbasl+1
             bne   wdecc
             BREAK

wnoroll      sta   ]diff

wdecc        dec   ]count       ;5 :count--
             lda   ]count       ;3
             cmp   #$ff         ;2
             bne   wideloop     ;3 :while (count > -1)
             dec   ]counth      ;low rolled, decr high
             beq   wideloop     ;went 1->0, keep going
             rts


* Pure-vertical line.  These are common in certain
* applications, and checking for it only adds two
* cycles to the general case.
vertline
             ldx   in_y0
             ldy   in_y1
             cpx   in_y1        ;y0 < y1?
             blt   :usey0       ;yes, go from y0 to y1
             txa                ;swap X/A
             tay
             ldx   in_y1
:usey0       stx   ]ypos
             iny
             sty   _pvytest+1

             ldx   in_x0l       ;xc lo
             lda   in_x0h       ;>= 256?
             beq   :lotabl
             ldy   div7hi,x
             lda   mod7hi,x
             bpl   :gotit       ;always
:lotabl      ldy   div7lo,x
             lda   mod7lo,x

* Byte offset is in Y, mod-7 value is in A.
:gotit       tax
             lda   andmask,x
             sta   _pvand+1     ;this doesn't change

             lda   colorline,y
             sta   _pvcolor+1   ;nor does this

             ldx   ]ypos        ;top line

* There's a trick where, when (linenum & 0x07) is
* nonzero, you just add 4 to hbasl+1 instead of
* re-doing the lookup.  However, TXA+AND+BEQ
* followed by LDA+CLC+ADC+STA is 16 cycles, the same
* as our self-modified lookup, so it's not a win.
* (And if we used a second ylookhi and self-modded
* the table address, we could shave off another 2.)

* Main pure-vertical loop
pverloop
             lda   ylooklo,x    ;4
             sta   ]hbasl       ;3
             lda   ylookhi,x    ;4
_pg_or7      ora   #$20         ;2
             sta   ]hbasl+1     ;3 (= 16)

_pvcolor     lda   #$00         ;2 start with color pattern
_lmdpv       eor   (]hbasl),y   ;5 flip all bits
_pvand       and   #$00         ;2 clear other bits
             eor   (]hbasl),y   ;5
             sta   (]hbasl),y   ;6 (= 20)

             inx                ;2
_pvytest     cpx   #$00         ;2 done?
             bne   pverloop     ;3 = 7
             rts
* 43 cycles/pixel


********************************
*
* Set the line mode according to in_arg
*
* A slightly silly feature to get xdraw lines
* without really working for it.
*
********************************
SetLineMode
             lda   in_arg
             beq   :standard

* configure for xdraw
             lda   #$24         ;BIT dp
             sta   _lmb
             sta   _lmdh
             sta   _lmdv
             sta   _lmdw
             sta   _lmdpv
             rts

* configure for standard drawing
:standard    lda   #$f0         ;BEQ
             sta   _lmb
             lda   #$51         ;EOR (dp),y
             sta   _lmdh
             sta   _lmdv
             sta   _lmdw
             sta   _lmdpv
             rts


