;
; Title:	ZX Spectrum 48K Sprite Routines
; Author:	Dean Belfield
; Created:	29/01/2020
; Last Updated:	29/01/2020
;
; Requires:	output.asm
;
; Modinfo:
;

; This routine goes through the sprite logic table and runs the logic routine for each sprite
;
Handle_Sprites: 	LD IX,Sprite_Data			; The sprite data block
			LD B,Sprite_Max				; The number of sprites to handle
Handle_Sprites_1:	ld A,(IX+Sprite_Logic+1)		; Get the high address of the handler routine
			AND A					; If it is zero
			JR Z,Handle_Sprites_3			; Then don't process the sprite
			LD HL,Handle_Sprites_2			; Set to the return address
			PUSH BC					; Push the loop counter
			PUSH IX					; Push the index register
			PUSH HL					; Push the return address (to simulate a call)
			LD H,A					; Set H to the previously fetched high address of handler routine
			LD L,(IX+Sprite_Logic)			; Fetch the low address of the handler routine
			LD A,(IX+Sprite_X)			; Store the current X and Y coordinates (for erase routine)
			LD A,(IX+Sprite_Y)
			JP (HL)					; Jump to the handler. Return address is stacked, so RET from that routine
Handle_Sprites_2:	POP IX					; Pop the index register
			CALL Sort_Sprite			; Sort the sprite
			POP BC					; Pop the loop counter
Handle_Sprites_3:	LD DE,Sprite_Data_Block_Size		; Go to next sprite data block
			ADD IX,DE
			DJNZ Handle_Sprites_1			; Loop until all sprites have been processed
			RET

; Create the sorted table of sprites
;
Sort_Sprite:		LD HL,Sprite_Sort_Table+1		; Address of the sort table
			LD A,(IX+Sprite_Y)			; Get the sprite Y address
			AND 0xF0				; Get the top 16 bits
			SRL A					; Divide by 8
			SRL A
			SRL A
			LD C,A					; Stick in BC
			LD B,0
			ADD HL,BC				; Point it to the correct bucket
Sort_Sprite_1:		LD A,(HL)				; See if bucket clear
			AND A					; Check for A=0
			JR Z,Sort_Sprite_2			; If zero, then bucket empty - jump to insert code
			INC HL
			INC HL
			JR Sort_Sprite_1
Sort_Sprite_2:		LD A,IXH
			LD (HL),A				; Store the sprite data address in the bucket
			DEC HL
			LD A,IXL
			LD (HL),A
			RET

; Clear the Render_Sprites bucket table - uses the stack for speed
; The loop could be unbundled; left as-is for the moment
;
Clear_Sprite_Table:	LD (Clear_Sprite_Table_SP+1),SP		; Store the SP for later
			LD SP,Sprite_Sort_Table_End		; Set it to the end of the self modifying code area
			LD B,Sprite_Sort_Table_Size 		; Number of buckets to clear
			LD DE,0					; We're zeroing the memory
Clear_Sprite_Table_1:	PUSH DE					; Push 4 words into the area
			DJNZ Clear_Sprite_Table_1
Clear_Sprite_Table_SP:	LD SP,0					; Restore the stack pointer
			RET

; Render the sprites
;
Render_Sprites:		LD IY,Sprite_Sort_Table
			LD B,Sprite_Sort_Table_Size
Render_Sprites_0:	PUSH BC
			LD A,(IY+1)
			AND A
			JR Z,Render_Sprites_1
			LD IXH,A
			LD A,(IY+0)
			LD IXL,A
			LD E,(IX+Sprite_Image)
			LD D,(IX+Sprite_Image+1)
			LD B,(IX+Sprite_Y)
			LD C,(IX+Sprite_X)
			CALL Render_Sprite
Render_Sprites_1:	POP BC
			INC IY
			INC IY
			DJNZ Render_Sprites_0
			RET

; This routine draws a single sprite; again, work in progress. No off-screen clipping or masking yet
; B = Y pixel postion
; C = X pixel position
; DE = Address of sprite table (8 words; one word per pre-shifted sprite definition)
;
Render_Sprite:		CALL Get_Pixel_Address			; HL = Screen Address, A = Pixel in Row
			EX DE,HL				; HL = Sprite, DE = Screen
			SLA A					; Multiply pixel shift by 2
			LD B,0
			LD C,A
			ADD HL,BC				; Add base address of sprite table

			LD A,(HL)				; Get sprite definition address
			INC HL
			LD H,(HL)
			LD L,A					; HL = Sprite, DE = Screen
			LD (Render_Sprite_SP+1),SP		; Preserve the stack pointer
			LD SP, HL				; Store in SP
			EX DE,HL				; HL = Screen, SP = Sprite

			LD B,16					; Height of sprite, in pixels
Render_Sprite_1:	POP DE					; Fetch first word of sprite (E = mask, D = sprite)
			LD A,(HL)				; Fetch screen data
			AND E					; AND with mask
			OR D					; OR with data
			LD (HL),A				; Store back in screen
			INC L					; To next screen location
			POP DE					; And repeat again...
			LD A,(HL)
			AND E
			OR D
			LD (HL),A
			INC L
			POP DE					; And a third time...
			LD A,(HL)
			AND E
			OR D
			LD (HL), A
			DEC L					; Go back to original screen address
			DEC L
			INC H					; Drop down to the next pixel line of the screen
			LD A, H
			AND 0x07
			JR NZ, Render_Sprite_2
			LD A, L
			ADD A, 32
			LD L, A
			JR C, Render_Sprite_2
			LD A, H
			SUB 8
			LD H,A
Render_Sprite_2:	DJNZ Render_Sprite_1			; Restore the stack pointer
Render_Sprite_SP:	LD SP,0
			RET
		
Sprite_Image:		EQU 0x00
Sprite_X:		EQU 0x02
Sprite_Y:		EQU 0x03
Sprite_W:		EQU 0x04
Sprite_H:		EQU 0x05
Sprite_Logic:		EQU 0x06
Sprite_Flags:		EQU 0x08
Sprite_Data_1:		EQU 0x09
Sprite_Data_2:		EQU 0x0A
Sprite_Data_3:		EQU 0x0B
Sprite_Data_4:		EQU 0x0C
Sprite_Data_5:		EQU 0x0D
Sprite_Data_6:		EQU 0x0E
Sprite_Data_7:		EQU 0x0F

Sprite_Data_Block_Size:	EQU 0x10
Sprite_Max:		EQU 0x10
Sprite_Data_Len:	EQU Sprite_Max * Sprite_Data_Block_Size
Sprite_Sort_Table_Size:	EQU Sprite_Max * 2
Sprite_Sort_Table_Len:	EQU Sprite_Sort_Table_Size * 2

Sprite_Data:		DEFS Sprite_Data_Len, 0
Sprite_Sort_Table:	DEFS Sprite_Sort_Table_Len, 0
Sprite_Sort_Table_End:	EQU  $