********************************
*                              *
* Amper-fdraw                  *
* By Andy McFadden             *
* For fdraw version 0.3        *
*                              *
* Applesoft ampersand          *
* interface for fdraw.         *
*                              *
* Developed with Merlin-16     *
*                              *
********************************

         lst   off
         org   $1d60

* All of the handler entry points can fit on a single
* page, so it's possible to save a few bytes by
* dropping the high jump table and just hardcoding
* the first page into the jump.  This requires that
* the ORG be at $xx00.

         PUT   FDRAW.DEFS

* Applesoft BASIC tokens.
tok_plot equ   $8d
tok_hgr2 equ   $90
tok_hgr  equ   $91
tok_hcolor equ $92
tok_hplot equ  $93
tok_draw equ   $94
tok_xdraw equ  $95
tok_inverse equ $9e
tok_clear equ  $bd
tok_new  equ   $bf
tok_to   equ   $c1
tok_at   equ   $c5
*tok_sgn equ $d2
tok_scrn equ   $d7
tok_exp  equ   $dd
tok_cos  equ   $de
tok_sin  equ   $df

* System locations.
PCL      equ   $3a        ;used by monitor
PCH      equ   $3b        ;used by monitor
A1L      equ   $3c        ;used by monitor
A1H      equ   $3d        ;used by monitor
LINNUM   equ   $50        ;50-51
FACLO    equ   $a1
CHRGET   equ   $b1        ;advance ptr, get next tok
CHRGOT   equ   $b7        ;get next tok (no advance)
TXTPTR   equ   $b8
HPAG     equ   $e6        ;$20 or $40

AMPERV   equ   $3f5

TXTCLR   equ   $c050
TXTSET   equ   $c051
MIXCLR   equ   $c052
MIXSET   equ   $c053
LOWSCR   equ   $c054
HISCR    equ   $c055
LORES    equ   $c056
HIRES    equ   $c057

ERROR    equ   $d412      ;error based on X reg
FRMNUM   equ   $dd67
SynError equ   $dec9      ;throw SYNTAX ERROR
CHKCOM   equ   $debe
IllQError equ  $e199      ;throw ILLEGAL QUANTITY ERROR
GETADR   equ   $e752
GETBYT   equ   $e6f8      ;gets byte, in X/FACLO
HFNS     equ   $f6b9      ;get hi-res x/y for hplot

* Prepare the ampersand vector.
*
* Ideally we'd check to see if the existing vector is
* different from ours, and if so, jump to it when we
* get a token we don't recognize.  Not convinced
* there's an actual use case for this.
init
         lda   #$4c       ;JMP, in case it got
         sta   AMPERV     ; trashed
         lda   #<dispatch
         sta   AMPERV+1
         lda   #>dispatch
         sta   AMPERV+2
         rts

* Entry point from BASIC.  The token is in A.
dispatch
         ldx   #:cmdend-:cmdtab-1
]loop    cmp   :cmdtab,x
         beq   :match
         dex
         bpl   ]loop
         jmp   SynError

:match
         lda   :jmptabh,x
* lda #>h_new ;all on first page
         pha
         lda   :jmptabl,x
         pha
         jmp   CHRGET     ;eat token, jump


:cmdtab  dfb   tok_new
         dfb   tok_hgr
         dfb   tok_hgr2
         dfb   tok_scrn
         dfb   tok_hcolor
         dfb   tok_inverse
         dfb   tok_clear
         dfb   tok_hplot
         dfb   tok_xdraw
         dfb   tok_draw
         dfb   tok_exp
         dfb   tok_cos
         dfb   tok_sin
         dfb   tok_at
         dfb   tok_plot
:cmdend

:jmptabl dfb   <h_new-1
         dfb   <h_hgr-1
         dfb   <h_hgr2-1
         dfb   <h_scrn-1
         dfb   <h_hcolor-1
         dfb   <h_inverse-1
         dfb   <h_clear-1
         dfb   <h_hplot-1
         dfb   <h_xdraw-1
         dfb   <h_draw-1
         dfb   <h_exp-1
         dfb   <h_cos-1
         dfb   <h_sin-1
         dfb   <h_at-1
         dfb   <h_plot-1
:jmptabh dfb   >h_new-1
         dfb   >h_hgr-1
         dfb   >h_hgr2-1
         dfb   >h_scrn-1
         dfb   >h_hcolor-1
         dfb   >h_inverse-1
         dfb   >h_clear-1
         dfb   >h_hplot-1
         dfb   >h_xdraw-1
         dfb   >h_draw-1
         dfb   >h_exp-1
         dfb   >h_cos-1
         dfb   >h_sin-1
         dfb   >h_at-1
         dfb   >h_plot-1


********************************
* &NEW - initialize
h_new
         lda   #$20       ;match Init result
         sta   g_cur_page
         lda   #$00
         sta   g_hcolor
         tax              ;init "previous hplot"
         tay              ; coord to zero
         jsr   storeprv
         ldx   #139       ;279/2
         ldy   #0
         lda   #95        ;191/2
         jsr   storeac
         jmp   f_Init

********************************
* &HGR - show page 1 with mixed text, and clear screen.
* Sets the color to zero.
h_hgr
         ldx   #$20       ;page 1
         lda   #$00       ;$c054
         beq   hgr_com

********************************
* &HGR2 - show page 2 with no text, and clear screen.
* Sets the color to zero.
h_hgr2
         ldx   #$40       ;page 2
         lda   #$01       ;$c055
                          ;fall through to hgr_com

* We go slightly out of our way to clear the screen
* before tripping the softswitches.  This avoids
* flashing the previous hi-res page contents when
* entering from text mode.
*
* We also want to go nomix-page2 but page1-mix
* (note reverse order) to avoid flashing text pg 2.
hgr_com  stx   f_in_arg
         stx   g_cur_page
         stx   HPAG       ;probably useful
         pha
         jsr   f_SetPage
         lda   #$00
         sta   f_in_arg
         jsr   f_SetColor
         jsr   f_Clear
         lda   g_hcolor   ;restore color
         sta   f_in_arg
         jsr   f_SetColor
         bit   TXTCLR     ;$c050
         bit   HIRES      ;$c057
         pla
         beq   :pg1
         bit   MIXCLR     ;$c052
         bit   HISCR      ;$c055
         rts
:pg1     bit   LOWSCR     ;$c054
         bit   MIXSET     ;$c053
         rts

********************************
* &SCRN({1,2}) - set the current hi-res page
h_scrn
         jsr   GETBYT
         cpx   #1
         beq   :okay
         cpx   #2
         beq   :okay
         jmp   IllQError
:okay    jsr   CHRGET     ;eat ')' (we assume)
         txa              ;X/Y unaltered
         asl
         asl
         asl
         asl
         asl              ;multiply x32
         sta   g_cur_page
         sta   f_in_arg
         jmp   f_SetPage

********************************
* &HCOLOR={0-7} - set the current color
h_hcolor
         jsr   GETBYT     ;get color
         cpx   #8
         blt   :okay
         jmp   IllQError
:okay    stx   f_in_arg
         stx   g_hcolor
         jmp   f_SetColor

********************************
* &INVERSE - flip pages
*
* If we're currently drawing on $20, we set the page
* to $40 and hit $c054 to show $20.  And vice-versa.
* The goal is to make double-buffered animation easy.
h_inverse
         lda   g_cur_page
         eor   #$60
         sta   g_cur_page
         ldx   #$00
         cmp   #$40       ;about to start drawing on 2?
         beq   :showpg1   ;yes, show page 1
         inx              ;no, show page 2
:showpg1 ldy   LOWSCR,x
         sta   f_in_arg
         jmp   f_SetPage

********************************
* &CLEAR - clear current page to current color.
h_clear
         jmp   f_Clear    ;well, that was easy

********************************
* &XDRAW left,top,right,bottom - draw rectangle outline
h_xdraw
         jsr   getltrb
         jmp   f_DrawRect

********************************
* &DRAW left,top,right,bottom - draw filled rectangle
h_draw
         jsr   getltrb
         jmp   f_FillRect

********************************
* &EXP {0,1} - set line draw mode
h_exp
         jsr   GETBYT
         cpx   #2
         blt   :okay
         jmp   IllQError
:okay    stx   f_in_arg
         jmp   f_SetLineMode

********************************
* &COS cx,cy,rad - draw outline circle
h_cos
         jsr   getcxcyr
         jmp   f_DrawCircle

********************************
* &SIN cx,cy,rad - draw filled circle
h_sin
         jsr   getcxcyr
         jmp   f_FillCircle

********************************
* &AT x,y - select center for array draw
h_at
         jsr   HFNS
         jmp   storeac

********************************
* &PLOT vertexAddr, indexAddr, indexCount [AT cx,cy]
*  draw lines from arrays of vertices and indices
h_plot   jmp   array_draw

********************************
* &HPLOT x,y - draw a point
* &HPLOT TO x,y - draw a line from last point to x,y
* &HPLOT x0,y0 to x1,y1 - draw a line
         lst   on         ;last token handler --
h_hplot  equ   *          ; must be on first page
         lst   off        ; to omit high byte table

         jsr   CHRGOT     ;check next token
         lst   off
         cmp   #tok_to    ;is this an "HPLOT TO"?
         beq   :leadingto
         jsr   getx1y1    ;get the first coord
         jsr   copy1to0
         jsr   CHRGOT     ;see if single point
         cmp   #tok_to
         beq   :hplot_to  ;nope, draw line
         jsr   copy0toprev ;draw point, and save x/y
         jmp   f_DrawPoint ; for subsequent HPLOT TO

:leadingto                ;"HPLOT TO", restore the
         lda   g_prevxl   ; previous coord to x0/y0
         sta   f_in_x0l   ;(can't rely on f_in_zzz
         lda   g_prevxh   ; being there -- we might
         sta   f_in_x0h   ; have drawn a rect)
         lda   g_prevy
         sta   f_in_y0
:hplot_to
         jsr   CHRGET     ;eat the TO
         jsr   getx1y1    ;get the coords
         jsr   f_DrawLine ;draw it
         jsr   copy1to0   ;shift 1->0 for next round
         jsr   CHRGOT
         cmp   #tok_to    ;another TO?
         beq   :hplot_to  ;yes, branch
         jmp   copy0toprev ;no, save prev and bail

* Get coordinates and store in X1/Y1.
getx1y1
         jsr   HFNS
store1   stx   f_in_x1l   ;store X/Y/A in coord1
         sty   f_in_x1h
         sta   f_in_y1
         rts

* Save x0/y0 as our "previous" coordinate.
copy0toprev
         ldx   f_in_x0l
         ldy   f_in_x0h
         lda   f_in_y0
storeprv stx   g_prevxl   ;store X/Y/A in g_prev
         sty   g_prevxh
         sta   g_prevy
         rts

* Copy X1/Y1 into X0/Y0.
copy1to0
         ldx   f_in_x1l
         ldy   f_in_x1h
         lda   f_in_y1
store0   stx   f_in_x0l   ;store X/Y/A in coord 0
         sty   f_in_x0h
         sta   f_in_y0
         rts

* Store X/Y/A into array-center.
storeac  stx   g_ac_xl
         sty   g_ac_xh
         sta   g_ac_y
         rts

* Get left/top/right/bottom coordinates.
getltrb
         jsr   HFNS
         jsr   store0     ;save as X0/Y0
         jsr   CHKCOM     ;eat a comma
         jsr   HFNS
         jsr   store1     ;save as X1/Y1
         rts

* Get center coordinates and radius.
getcxcyr
         jsr   HFNS       ;get CX and CY
         jsr   store0     ;save as X0/Y0
         jsr   CHKCOM     ;eat a comma
         jsr   GETBYT     ;convert to 0-255
         stx   f_in_rad
         rts

* Array-draw handler.
*
* We know that fdraw doesn't use LINNUM or A1L/A1H,
* so it's safe to use them here.
array_draw
]vertices equ  A1L        ;2b
]indices equ   LINNUM     ;2b
]count   equ   PCL
]cur     equ   PCH

         jsr   FRMNUM     ;get vertex buffer address
         jsr   GETADR
         lda   LINNUM     ;copy to A1L
         sta   ]vertices
         lda   LINNUM+1
         sta   ]vertices+1
         jsr   CHKCOM     ;eat the comma
         jsr   FRMNUM     ;get index buffer address
         jsr   GETADR     ;leave it in LINNUM
         jsr   CHKCOM
         jsr   GETBYT     ;get the count
         cpx   #128       ;range check (0-127)
         blt   :countok
         jmp   IllQError
:countok txa
         beq   :done      ;nothing to do
         asl              ;double it
         sta   ]count     ;stash it
         lda   #$00
         sta   ]cur

* Check for optional AT cx,cy.
         jsr   CHRGOT
         cmp   #tok_at
         bne   :noat
         JSR   CHRGET     ;eat the AT
         lda   LINNUM     ;the code that reads the
         pha              ; hi-res coordinates will
         lda   LINNUM+1   ; overwrite LINNUM, so
         pha              ; we have to save & restore
         jsr   h_at
         pla
         sta   LINNUM+1
         pla
         sta   LINNUM
:noat

]loop    jsr   getvertex
         bcs   :skip2
         jsr   store0
         jsr   getvertex
         bcs   :skip
         jsr   store1
         jsr   f_DrawLine
         dfb   $2c        ;BIT addr
:skip2   inc   ]cur
:skip    lda   ]cur
         cmp   ]count
         blt   ]loop
:done    rts

* Get the Nth vertex, specified by ]cur, and load it
* into X/Y/A (xlo/xhi/y).  Returns with carry set if
* the vertex is invalid.
*
* Increments ]cur by 1.
getvertex
         ldy   ]cur
         inc   ]cur
         lda   (]indices),y
         bmi   :badv      ;must be 0-127
         jsr   :calcvertex

         ldx   g_out_x
         ldy   g_out_x+1
         beq   :xok       ;0-255, ok
         cpy   #1
         bne   :badv      ;512+
         cpx   #280-256
         bge   :badv      ;280-511
:xok
         lda   g_out_y+1
         bne   :badv      ;Y is neg or > 255
         lda   g_out_y
         cmp   #192
         bcc   :goodv
:badv
         sec
:goodv   rts

* Get VX and VY, merging with AC, and store in
* 16-bit g_out_x and g_out_y.  Range not checked
* here.  On entry, A has vertex index.
:calcvertex
         asl
         tay
         ldx   #$00       ;hi byte of vertex
         lda   (]vertices),y ;x-coord
         bpl   :xpos
         dex              ;sign-extend hi byte
:xpos    clc
         adc   g_ac_xl
         sta   g_out_x
         txa
         adc   g_ac_xh
         sta   g_out_x+1

         iny
         ldx   #$00
         lda   (]vertices),y ;y-coord
         bpl   :ypos
         dex              ;sign-extend hi byte
:ypos    clc
         adc   g_ac_y
         sta   g_out_y
         bcc   :nocarry
         inx
:nocarry stx   g_out_y+1
         rts



********************************
* Global variables

g_cur_page ds  1          ;$20 or $40
g_hcolor ds    1
g_prevxl ds    1
g_prevxh ds    1
g_prevy  ds    1
g_ac_xl  ds    1          ;Center-point coordinates
g_ac_xh  ds    1          ; for array-based line
g_ac_y   ds    1          ; draw (&AT, &PLOT).
g_out_x  ds    2          ;16-bit coordinates for
g_out_y  ds    2          ; array-based line draw



         lst   on
end      equ   *
         sav   amperfdraw
         lst   off