; ==============================================================================
;
;  3D Copper Demo
;  D. Munsie 2003, 2011
;
;  Based on the 3D Copper demo for the GBA
;
; ==============================================================================

; ------------------------------------------------------------------------------
;
;  Revision History:
;    Oct 2011   Many changes to reduce ROM size and RAM usage, added 2 additional
;               copper bars.  Reset switch now pauses animation, Color/B&W switch
;               switches between 4 and 6 copper bar mode.
;    06 Jul 03  Implemented changes suggested by DEBRO to get 262 scan lines and
;               insure 2600jr compatibility
;    02 Jul 03  Initial release
;
; ------------------------------------------------------------------------------
  processor 6502
  include vcs.h

; ------------------------------------------------------------------------------
;  Consts 
; ------------------------------------------------------------------------------
  seg.u Consts
TopBlankLines     = 50    ; number of blank lines on the top
NumTopLines       = 25    ; number of lines in the copper bar area above the text
NumMiddleLines    = 40    ; number of lines in the copper bar area with text
NumBottomLines    = 26    ; number of lines in the copper bar area below the text
BottomBlankLines  = 51    ; number of blank lines on the bottom
NumColorLines     = NumTopLines + NumMiddleLines + NumBottomLines

; ------------------------------------------------------------------------------
;  Zero Page Variables
; ------------------------------------------------------------------------------
  seg.u ZeroPage
  org $80
LineColor       ds NumColorLines  ; wow -- i'm pretty insane... i'm using over half of
                                  ; my RAM for a color table... oh well, it is just a
                                  ; demo :)

NumBars         ds 1      ; number of bars we are displaying
BarPositions    ds 6      ; Middle lines of the bar
BarDepths       ds 6      ; Depths of the bars
BarAngles       ds 6      ; Angles of the bars
BarColors       ds 6      ; Colors of the bars
TmpVar          ds 1      ; temporary variable

; make sure we didn't go over our RAM allotment
  if . > $100
    echo "ERR", . - $80, "bytes of RAM used, but only $80 available."
    err
  endif

; ------------------------------------------------------------------------------
;   Start of ROM
; ------------------------------------------------------------------------------
  seg Code
  org $f800
ROMStart

; ------------------------------------------------------------------------------
;   Playfield tables -- located at start of the ROM to minimize ROM space waste
;   due to alignment requirements
; ------------------------------------------------------------------------------
PF0FirstTable
  byte %00100000
  byte %00100000
  byte %11100000
  byte %00100000
  byte %11000000
PF1FirstTable
  byte %10101001
  byte %10101001
  byte %10101111
  byte %10101001
  byte %01110110
PF2FirstTable
  byte %01010010
  byte %01010010
  byte %01001110
  byte %01010010
  byte %01001110
PF0SecondTable
  byte %11000000
  byte %10000000
  byte %00000000
  byte %01000000
  byte %10000000
PF1SecondTable
  byte %11001100
  byte %00010010
  byte %10011110
  byte %01010000
  byte %10001100
PF2SecondTable
  byte %00100010
  byte %01010101
  byte %01010101
  byte %01010101
  byte %00100010

; ------------------------------------------------------------------------------
;   Variable initialization
; ------------------------------------------------------------------------------
VarInit4Bars subroutine
  lda #$04                ; save off the number of bars we are currently displaying
  sta NumBars
  tay
.loop
  dey
  lda Bar4StartAngleValues,y
  sta BarAngles,y
  lda BarStartColorValues,y
  sta BarColors,y
  cpy #0
  bne .loop               ; done?
  rts

VarInit6Bars subroutine
  lda #$06                ; save off the number of bars we are currently displaying
  sta NumBars
  tay
.loop
  dey
  lda Bar6StartAngleValues,y
  sta BarAngles,y
  lda BarStartColorValues,y
  sta BarColors,y
  cpy #0
  bne .loop               ; done?
  rts

; ------------------------------------------------------------------------------
;   Main entry point
; ------------------------------------------------------------------------------
Main subroutine
  sei                     ; disable interrupts
  cld                     ; turn off decimal mode
  ldx #$ff                ; setup the stack pointer
  txs
  lda #0                  ; clear out zero page
.zeroPageLoop
  pha                     ; by using the stack pointer
  dex            
  bne .zeroPageLoop
  pha                     ; get mem $00 as well, and reset the SP back to $ff

; ------------------------------------------------------------------------------
;   Main Loop
; ------------------------------------------------------------------------------
MainLoop
  lda #$08                ; check the status of the B/W & Color switch
  bit SWCHB
  bne .colorSet           ; color is selected == 6 bars
  lda NumBars             ; see if we currently have 4 bars selected
  cmp #$04
  beq .checkReset         ; yes -- move on, nothing to do here
  jsr VarInit4Bars
  jmp UpdateBars          ; skip the reset check this frame

.colorSet
  lda NumBars             ; see if we currently have 6 bars selected
  cmp #$06
  beq .checkReset         ; yes -- move on, nothing to do here
  jsr VarInit6Bars
  jmp UpdateBars          ; skip the reset check this frame

.checkReset
  lda #$01                ; check if someone is holding down reset
  bit SWCHB
  beq EraseBars           ; yes -- skip the updates

; ------------------------------------------------------------------------------
;   Update bar positions
; ------------------------------------------------------------------------------
UpdateBars subroutine
  ldy NumBars
.loop
  dey                     ; move to the next bar

  ldx BarAngles,y         ; get the bar angle
  dex                     ; move it
  stx BarAngles,y         ; store it for next time

  lda PositionTable,x     ; get the postion
  sta BarPositions,y

  txa                     ; DepthTable is packed.  Each entry represents 8 angles
  lsr                     ; so divide by 8
  lsr
  lsr
  cmp #$10                ; check if we are indexing into the non-existant half
  bcc .skipInvert         ; nope
  sta TmpVar              ; we are -- save off the current index
  lda #$10                ; we need to subtract it off from $10
  clc                     ; we DO NOT want to use the carry bit
  sbc TmpVar              ; subtract the index from $10
  and #$0f                ; and make sure it's < $10 now
.skipInvert
  tax
  lda DepthTable,x        ; get the depth value
  sta BarDepths,y         
      
  cpy #0
  bne .loop               ; done?
      
; ------------------------------------------------------------------------------
;   Sort bar depths
;
;   This routine sorts the depths so that when the bars are "drawn", the ones
;   with a smaller depth are drawn first.  It does this by comparing depth 4 to
;   depths 1-3, followed by depth 3 to depths 1-2, and depth2 to depth 1.  After
;   doing this, the bars will be sorted.
; ------------------------------------------------------------------------------
SortBars subroutine
  ldx NumBars             ; start comparing

.outerLoop
  dex                     ; move to the next depth to check
  beq .exit               ; is this the end?
  txa                     ; move over the current depth for the inner loop
  tay
      
.innerLoop
  dey                     ; check the next depth
  lda BarDepths,x         ; is this depth
  cmp BarDepths,y         ; more than this one?
  bcs .noSwap             ; if so, we don't need to swap

  pha                     ; swap the depths - we already have BarDepths,x
  lda BarDepths,y
  sta BarDepths,x
  pla
  sta BarDepths,y

  lda BarColors,x         ; swap the colors
  pha
  lda BarColors,y
  sta BarColors,x
  pla
  sta BarColors,y

  lda BarPositions,x      ; swap the position
  pha
  lda BarPositions,y
  sta BarPositions,x
  pla
  sta BarPositions,y

  lda BarAngles,x         ; swap the angle
  pha
  lda BarAngles,y
  sta BarAngles,x
  pla
  sta BarAngles,y
      
.noSwap
  cpy #0                  ; did we just compare against the last depth?
  bne .innerLoop          ; nope -- go back and compare some more
  beq .outerLoop          ; yep -- go back to the outer loop

.exit

; ------------------------------------------------------------------------------
;   Erase Bars 
; ------------------------------------------------------------------------------
EraseBars subroutine
  ldx #NumColorLines      ; we need to blank out the colors first
  lda #0
.eraseLoop
  dex
  sta LineColor,x         ; color be gone!
  bne .eraseLoop          ; done?

; ------------------------------------------------------------------------------
;   Vertical Blanking Code
; ------------------------------------------------------------------------------
VBlank
  lda INTIM               ; wait for the overscan to be done
  bne VBlank
  ldx #0                  ; set everything up
  lda #2
  sta WSYNC

  sta VSYNC               ; start the vsync
  sta WSYNC               ; first vsync line
  sta WSYNC               ; second vsync line
  sta WSYNC               ; third vsync line
  stx VSYNC               ; turn off vsync
      
  lda #44                 ; setup the timer
  sta WSYNC
  sta TIM64T
  
; ------------------------------------------------------------------------------
;   Update color table
;
;   This routine draws out the copper bars in order of depth from back to front.
;   For each bar it sets the appropriate number of lines for the depth and
;   position to the color
; ------------------------------------------------------------------------------
DrawBars subroutine
  lda #$00                ; start with the farthest back bar
.barsLoop
  pha                     ; save off the current bar index

; ------------------------------------------------------------------------------
;   DrawBar
;
;   Draws a single bar -- A points to the offset in the bar table to be drawn
; ------------------------------------------------------------------------------
DrawBar
  tay                     ; get the current bar into Y
  lda BarPositions,y      ; get the position of the bar
  sec
  sbc BarDepths,y         ; subtract off half the lines
  tax                     ; save it for later -- this is our vertical pos
  lda BarColors,y         ; get the bar color
  sta TmpVar              ; save it off
  lda BarDepths,y         ; get half the number of lines we need to draw
  tay                     ; put it into Y
  lda TmpVar              ; get the color back into A
.topLoop
  sta LineColor - 2,x     ; draw the color -- X value is 2 ahead of where we want
  clc
  adc #2                  ; move to the next color
  inx                     ; and the next line
  dey
  bne .topLoop

  ; bottom half of the line
  sta TmpVar              ; save the color
  pla                     ; get the current bar index back out again
  pha                     ; and save it back for the DrawBars loop
  tay                     ; put the index back into Y
  lda BarDepths,y         ; get half the number of lines we need to draw
  tay                     ; put it into Y
  lda TmpVar              ; get our color back
.bottomLoop
  sec                     ; colors are moving down now
  sbc #2
  sta LineColor - 2,x     ; draw the color
  inx                     ; move to the next line
  dey
  bne .bottomLoop

  pla                     ; get the current bar value
  clc                     ; move the index to the next bar
  adc #1
  cmp NumBars
  bne .barsLoop           ; done?
    
; ------------------------------------------------------------------------------
;   Kernel code
; ------------------------------------------------------------------------------
Kernel subroutine
  lda INTIM               ; wait until it's time to start drawing
  bne Kernel
  sta WSYNC               ; we're ready to start
  sta VBLANK              ; turn the display back on

; Top blank section
  ldx #TopBlankLines      ; load the line count
.topLoop
  sta WSYNC               ; move to the next line
  dex                     ; update line pointer
  bne .topLoop

; Top section of copper bars only
  ldx #NumTopLines - 1    ; load the line count
.topBarLoop
  lda LineColor + NumMiddleLines + NumBottomLines,x
                          ; get the background color
  sta COLUBK              
  dex                     ; move to the next line index
  sta WSYNC               ; (3 = 0) and wait for the next line to start
  bne .topBarLoop         ; (2 = 2)

; one line of copper bar only -- used to prep for text area
  lda LineColor + NumMiddleLines + NumBottomLines
                          ; setup the copper bar color for this line
  sta COLUBK
  ldx #NumMiddleLines - 1 ; load the line count -- we stop the loop after hitting 0
  ldy #(NumMiddleLines - 1) / 8
                          ; setup the playfield count as well
  lda LineColor + NumBottomLines,x
                          ; get the next lines color
  sta WSYNC               ; (3 = 0) wait for the next line
  nop $00                 ; (3 = 3) burn 3 cycles to make sure our count matches

; Rotating copper bars and "ATARI 2600" text
;
; background color is stored in RAM, playfield color is based off background
; color.  Playfield is stored in table at the end of the ROM.
.barLoop                 
  sta COLUBK              ; (3 = 6) store off the background color
                          ;   (loaded at the end of the line)
  clc                     ; (2 = 8)
  adc #$02                ; (2 = 10) get the playfield color
  sta COLUPF              ; (3 = 13)

  lda PF0FirstTable,y     ; (4 = 19) get the value for PF0
  sta PF0                 ; (3 = 22)

  lda PF1FirstTable,y     ; (4 = 26) get the value for PF1
  sta PF1                 ; (3 = 29)

  lda PF2FirstTable,y     ; (4 = 31) get the value for PF2 on the left
  sta PF2                 ; (3 = 34)

  ; safe to write to PF0 after cycle 28
  lda PF0SecondTable,y    ; (4 = 38) get the value for PF0
  sta PF0                 ; (3 = 41)

  ; safe to write to PF1 after cycle 39
  lda PF1SecondTable,y    ; (4 = 45) get the value for PF1
  sta PF1                 ; (3 = 47)

  ; safe to write to PF2 after cycle 50
  lda PF2SecondTable,y    ; (4 = 51) get the value for PF2
  sta PF2                 ; (3 = 54)

  dex                     ; (2 = 57) move to the next line
  bmi .exitBarLoop        ; (2 = 59) time to be done?
  txa                     ; (2 = 61) calculate the offset into the PF data
  lsr                     ; (2 = 63) divide by 8
  lsr                     ; (2 = 65)
  lsr                     ; (2 = 67)
  tay                     ; (2 = 69) stuff it into y
  lda LineColor + NumBottomLines,x
                          ; (4 = 73) get the background color
  nop $00                 ; (3 = 76/0) burn the 3 remaining cycles to get to the next line
  jmp .barLoop            ; (3 = 3) do it again

; Bottom section of copper bars only
.exitBarLoop
  sta WSYNC               ; (3 = 0) finish out the last line of text
  inx                     ; (2 = 2) bump X back up to 0
  stx PF0                 ; (3 = 5) make sure the playfield registers are clear
  stx PF1                 ; (3 = 8)
  stx PF2                 ; (3 = 11)
  ldx #NumBottomLines     ; (3 = 14) load the line count
.bottomBarLoop
  lda LineColor - 1,x     ; (4 = 18/9) get the background color
  sta COLUBK              ; (3 = 21/12)
  sta WSYNC               ; (3 = 0) wait for the next line to start
  dex                     ; (2 = 2) move to the next line index
  bne .bottomBarLoop      ; (3 = 5) 
  
; Bottom blank section
  stx COLUBK              ; set the background back to black
  ldx #BottomBlankLines - 1
                          ; load the line count
.bottomLoop
  sta WSYNC               ; move to the next line
  dex                     ; update line pointer
  bne .bottomLoop         ; done?
      
; ------------------------------------------------------------------------------
;   Overscan code
; ------------------------------------------------------------------------------
Overscan subroutine
  ldx #2
  lda #35                 ; set the timer to go off in 30 lines
  sta WSYNC
  sta TIM64T
  sta VBLANK              ; start up V-Blank
      
  jmp MainLoop            ; let's do it all again
      
; ------------------------------------------------------------------------------
;   End of code
; ------------------------------------------------------------------------------

; starting values for the BarColors and BarAngles variables
Bar4StartAngleValues
  byte $00, $40, $80, $c0
Bar6StartAngleValues
  byte $00, $2a, $7f, $d4, $55, $aa
BarStartColorValues
  byte $c0, $80, $40, $f0, $60, $20

; position for the bar, indexed by angle
PositionTable
  byte $30, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3a, $3b, $3c, $3d, $3e 
  byte $3f, $40, $41, $41, $42, $43, $44, $45, $46, $47, $47, $48, $49, $4a, $4a, $4b 
  byte $4c, $4c, $4d, $4e, $4e, $4f, $50, $50, $51, $51, $52, $52, $53, $53, $54, $54 
  byte $54, $55, $55, $55, $56, $56, $56, $57, $57, $57, $57, $57, $57, $57, $57, $57 
  byte $58, $57, $57, $57, $57, $57, $57, $57, $57, $57, $56, $56, $56, $55, $55, $55 
  byte $54, $54, $54, $53, $53, $52, $52, $51, $51, $50, $50, $4f, $4e, $4e, $4d, $4c 
  byte $4c, $4b, $4a, $4a, $49, $48, $47, $47, $46, $45, $44, $43, $42, $41, $41, $40 
  byte $3f, $3e, $3d, $3c, $3b, $3a, $39, $38, $37, $36, $35, $34, $33, $32, $31, $30 
  byte $30, $2f, $2e, $2d, $2c, $2b, $2a, $29, $28, $27, $26, $25, $24, $23, $22, $21 
  byte $20, $1f, $1e, $1e, $1d, $1c, $1b, $1a, $19, $18, $18, $17, $16, $15, $15, $14 
  byte $13, $13, $12, $11, $11, $10, $0f, $0f, $0e, $0e, $0d, $0d, $0c, $0c, $0b, $0b 
  byte $0b, $0a, $0a, $0a, $09, $09, $09, $08, $08, $08, $08, $08, $08, $08, $08, $08 
  byte $08, $08, $08, $08, $08, $08, $08, $08, $08, $08, $09, $09, $09, $0a, $0a, $0a 
  byte $0b, $0b, $0b, $0c, $0c, $0d, $0d, $0e, $0e, $0f, $0f, $10, $11, $11, $12, $13 
  byte $13, $14, $15, $15, $16, $17, $18, $18, $19, $1a, $1b, $1c, $1d, $1e, $1e, $1f 
  byte $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2a, $2b, $2c, $2d, $2e, $2f 

DepthTable
  byte $07, $07, $07, $07, $06, $06, $06, $05, $05, $04, $04, $03, $03, $03, $03, $03 

  echo "ROM size =", . - ROMStart

  org $fffc
  word Main
  word Main
  end
