```;
; Title:	ZX Spectrum Vector Output Routines
; Author:	Dean Belfield
; Created:	30/06/2012
; Last Updated:	05/07/2012
;
; Requires:	output.asm
;
; Modinfo:
;
; 03/07/2012:	Simplified line draw; now always draws the line down. Relabled to improve clarity
;		Fixed bug in point tables; one entry had 7 bits
; 04/07/2012:	Added Draw_Horz_Line_Solid & Draw_Horz_Line_Texture; special fast case used for drawing polygons
; 05/07/2012:	Added Draw_Circle routine; needs some optimisation, more a proof of concept at the moment
;

;
; Most of these routines will precalculate the screen address in HL and return a bit position in A (0-7).
; The bit position is used as an index into the table Plot_Point; this contains a pixel set in the correct
; position for each bit, so 0=>%10000000, 1=>%01000000 and so on.
;
; The line routine, for example, will only do this slow calculation once and will work relative to that position
; for the rest of the draw. So, to move the pixel right if the data is stored in register D you would RRC D.
; At this point, if the pixel is %00000010 it would then be %00000001. If the pixel is then rotated again, it would
; be %10000000 and the carry bit would be set. This is picked up in the code and an INC or DEC L is used to move
; HL to the next adjacent character position.
;
; The function Pixel_Address_Down takes HL and move down or up one pixel line, taking into account the
; Spectrum's strange screen layout. Again, this is quicker than calculating the address from scratch each
; time as most of the time it's just doing an INC H (or DEC H).
;
; For the sake of clarity I've used CALLs within loops; I wouldn't normally do this in speed critical code but felt
; that I'd lose clarity if I didn't. Feel free to inline any code that is called.
;
; Finally, this code uses a lot of self-modifying code; the code is in RAM so it is possible to use the code to
; modify itself. This is used in the line routine to adjust the line drawing loop to draw in all four quadrants.
;

; Plot routine
; B = Y pixel position
; C = X pixel position
;
Plot:			CALL Get_Pixel_Address		; Get screen address in HL, pixel position (0 to 7) in A
LD BC,Plot_Point		; Address of point lookup table
LD C,A
LD A,(BC)			; Get pixel data from table
OR (HL)				; OR with screen data
LD (HL),A			; Write back to screen
RET

; Unplot routine
; B = Y pixel position
; C = X pixel position
;
Unplot:			CALL Get_Pixel_Address		; Same as Plot...
LD BC,Unplot_Point
LD C,A
LD A,(BC)
AND (HL)			; AND with screen data
LD (HL),A
RET

; Draw Circle (Beta - uses Plot to draw the circle outline as a proof of concept)
; B = Y pixel position of circle centre
; C = X pixel position of circle centre
; A = Radius of circle
;
Draw_Circle:		AND A				; Zero radius
JR Z,Plot			; Just plot the point
LD (Draw_Circle_M1 + 1),BC	; Store circle origin

LD IXH,A			; IXH = Y
LD IXL,0			; IXL = X
;
; Calculate BC (D2) = 3-(R*2)
;
LD H,0				; HL = R
LD L,A
ADD HL,HL			; HL = R*2
EX DE,HL			; DE = R*2
LD HL,3
AND A
SBC HL,DE			; HL = 3-(R*2)
LD B,H
LD C,L
;
; Calculate HL (Delta) = 1-R
;
LD HL,1
LD D,0
LD E,IXL
AND A
SBC HL,DE			; HL = 1 - CR
;
; SET DE (D2) = 1
;
LD DE,1

Draw_Circle_Loop:	LD A,IXH			; Get Y in A
CP IXL				; Compare with X
RET C				; Return if X>Y
;
; The routine only calculates an eighth of the circle, so use symnmetry to draw
;
EXX
Draw_Circle_M1:		LD DE,0				; Get the circle origin

LD A,E
LD C,A
LD A,D
LD B,A
CALL Plot			; Plot CX+X,CY+Y
LD A,E
SUB IXL
LD C,A
LD A,D
LD B,A
CALL Plot			; Plot CX-X,CY+Y
LD A,E
LD C,A
LD A,D
SUB IXH
LD B,A
CALL Plot			; Plot CX+X,CY-Y
LD A,E
SUB IXL
LD C,A
LD A,D
SUB IXH
LD B,A
CALL Plot			; Plot CY+X,CX-Y
LD A,D
LD B,A
LD A,E
LD C,A
CALL Plot			; Plot CY+X,CX+Y
LD A,D
SUB IXL
LD B,A
LD A,E
LD C,A
CALL Plot			; Plot CY-X,CX+Y
LD A,D
LD B,A
LD A,E
SUB IXH
LD C,A
CALL Plot			; Plot CY+X,CX-Y
LD A,D
SUB IXL
LD B,A
LD A,E
SUB IXH
LD C,A
CALL Plot			; Plot CX+X,CY-Y
EXX
;
; Do the incremental circle thing here
;
BIT 7,H				; Check for Hl<=0
JR Z,Draw_Circle_1
JR Draw_Circle_2		;
INC BC
INC BC				; D2=D2+2
DEC IXH				; Y=Y-1
Draw_Circle_2:		INC BC				; D2=D2+2
INC BC
INC DE				; D1=D1+2
INC DE
INC IXL				; X=X+1
JR Draw_Circle_Loop

; Draw Triangle
; IY = Pointer to 3 bytes worth of coordinate data
;
Draw_Triangle:		LD C,(IY+0)
LD B,(IY+1)
LD E,(IY+2)
LD D,(IY+3)
CALL Draw_Line
LD C,(IY+2)
LD B,(IY+3)
LD E,(IY+4)
LD D,(IY+5)
CALL Draw_Line
LD C,(IY+4)
LD B,(IY+5)
LD E,(IY+0)
LD D,(IY+1)
JP Draw_Line

; Erase Triangle
; IY = Pointer to 3 bytes worth of coordinate data
;
Erase_Triangle:		LD C,(IY+0)
LD B,(IY+1)
LD E,(IY+2)
LD D,(IY+3)
CALL Erase_Line
LD C,(IY+2)
LD B,(IY+3)
LD E,(IY+4)
LD D,(IY+5)
CALL Erase_Line
LD C,(IY+4)
LD B,(IY+5)
LD E,(IY+0)
LD D,(IY+1)
JP Erase_Line

; Draw Horizontal Line routine
; B = Y coordinate
; C = X pixel position 1
; E = X pixel position 2
;
Draw_Horz_Line_Solid:	LD A,&FF			; Set texture to all 1's and drop through...

; Draw Horizontal Line routine with texture
; B = Y coordinate
; C = X pixel position 1
; E = X pixel position 2
; A = Texture byte
Draw_Horz_Line_Texture:	LD IXL,A			; Store the texture byte
LD A,E				; Make sure we're always drawing left to right
CP C
JR NC,Draw_Horz_Line_1
LD E,C				; Swap X coordinates round if not
LD C,A
Draw_Horz_Line_1:	CALL Get_Pixel_Address		; Get the screen address of the left point
EX AF,AF'			; Store in DE,A'
EX DE,HL			; So E (X pixel position 2) will now be in L
LD C,L
;
; At this point, we have:
; DE = Screen address of point 1
; A' = Pixel position of point 1 (0-7)
; HL = Screen address of point 2
; A  = Pixel position of point 2 (0-7)
;
LD BC,Plot_Line_RHS		; Point BC to the table for the RHS pixel data
LD C,A
LD A,L				; We have a special case if both points in same character
CP E
JR NZ,Draw_Horz_Line_2		; Special case if both points in same character square
EX AF,AF'
LD HL,Plot_Line_LHS		; Get the LHS pixel data
LD L,A
LD A,(BC)			; XOR the RHS and LHS pixel data
XOR (HL)
CPL				; CPL it (so all 1's become 0's and 0's become 1's)
AND IXL				; Mask with the texture byte
EX DE, HL			; This is now the data we are going to draw
OR (HL)
LD (HL),A
RET				; Nothing more to do at this point, so return
Draw_Horz_Line_2:	LD A,(BC)			; Plot the RHS end
AND IXL
OR (HL)
LD (HL),A
DEC L				; Go left one character
LD A,L				; Skip next bit if no between fill to do
CP E
JR Z,Draw_Horz_Line_End
Draw_Horz_Line_M1:	LD D,IXL			; Stick the texture byte in D for speed
Draw_Horz_Line_Loop:	LD (HL),D			; Draw a filler byte
DEC L				; Go left one character
LD A,E				; Loop until we reach the left hand point character square
CP L
JR NZ,Draw_Horz_Line_Loop
Draw_Horz_Line_End:	EX AF,AF'			; Plot the LHS end
LD BC,Plot_Line_LHS
LD C,A
LD A,(BC)
AND IXL
OR (HL)
LD (HL),A
RET

; Draw Line routine
; B = Y pixel position 1
; C = X pixel position 1
; D = Y pixel position 2
; E = X pixel position 2
;
Draw_Line:		LD A,D				; Check whether we are going to be drawing up
CP B
JR NC,Draw_Line_1

PUSH BC				; If we are, then this neat trick swaps BC and DE
PUSH DE				; using the stack, forcing the line to be always
POP BC				; drawn downwards
POP DE

Draw_Line_1:		CALL Get_Pixel_Address		; Get screen address in HL, pixel position (0-7) in A
;
; At this point we have
;  A = Pixel position (0-7)
; HL = Screen address of the start point
; BC = Start coordinate (B=Y1, C=X1)
; DE = End coordinates  (D=Y2, E=X2)
;
LD IX,Plot_Point		; Point to the Plot_Point table
ADD A,IXL			; Add the pixel position to get entry in table
LD IXL,A

LD A,D				; Calculate the line height in B (Y2-Y1)
SUB B
LD B,A

LD A,E				; Calculate the line width in C (X2-X1)
SUB C
JR C,Draw_Line_X1		; If carry set (negative result) then we are drawing from right to left
;
; This bit of code mods the main loop for drawing left to right
;
LD C,A				; Store the line width
LD A,&2C			; Code for INC L
LD (Draw_Line_Q1_M3),A		; Mod the code
LD (Draw_Line_Q2_M3),A
LD A,&0A			; Code for RRC D (CB 0A)
JR Draw_Line_X2			; Skip the next bit
;
; This bit of code mods the main loop for drawing right to left
;
Draw_Line_X1:		NEG				; The width of line is negative, so make it positive again
LD C,A				; Store the line width
LD A,&2D			; Code for DEC L
LD (Draw_Line_Q1_M3),A
LD (Draw_Line_Q2_M3),A
LD A,&02			; Code for RLC D (CB 02)
;
; We've got the basic information at this point
;
Draw_Line_X2:		LD (Draw_Line_Q1_M2 + 1),A	; A contains the code for RLC D or RRC D, so make the mods
LD (Draw_Line_Q2_M2 + 1),A
LD D,(IX+0)			; Get the pixel data from the Point_Plot table
LD A,B				; Check if B and C are 0
OR C
JR NZ,Draw_Line_Q		; There is a line to draw, so skip to the next bit
LD A,(HL)			; Here we've got a single point line, so plot and return
OR D
LD (HL),A
RET
;
; At this point
; HL = Screen address of the start point
;  B = Line height
;  C = Line width
;  D = Pixel data
;
Draw_Line_Q:		LD A,B				; Work out which diagonal we are on
CP C
JR NC,Draw_Line_Q2
;
; This bit of code draws the line where B<C (more horizontal than vertical)
;
Draw_Line_Q1:		LD A,C
LD (Draw_Line_Q1_M1 + 1),A	; Self-mod the code again to store the line width
LD C,B
LD B,A
LD E,C				; Calculate the error value
SRL E
Draw_Line_Q1_L:		LD A,(HL)			; Plot the pixel
OR D
LD (HL),A
LD A,E
SUB C
LD E,A
JR NC,Draw_Line_Q1_M2
Draw_Line_Q1_M1:	ADD A,0				; Add the line height (previously stored; self modifying code)
LD E,A
Draw_Line_Q1_M2:	RRC D				; Rotate the pixel right or left; more self-modifying code
JR NC,Draw_Line_Q1_S
Draw_Line_Q1_M3:	INC L				; If we get a carry then move to adjacent screen address; more self modifying code
Draw_Line_Q1_S:		DJNZ Draw_Line_Q1_L		; Loop until the line is drawn
RET
;
; This bit draws the line where B>=C (more vertical than horizontal, or diagonal)
;
Draw_Line_Q2:		LD (Draw_Line_Q2_M1 + 1),A
LD E,C				; Calculate the error value
SRL E
Draw_Line_Q2_L:		LD A,(HL)			; Plot the pixel
OR D
LD (HL),A
LD A,E				; Get the error value
SUB C				; Add the line length to it (X2-X1)
JR NC,Draw_Line_Q2_S		; Skip the next bit if we don't get a carry
Draw_Line_Q2_M1: 	ADD A,0				; Add the line height (previously stored; self modifying code)
Draw_Line_Q2_M2:	RRC D				; Rotates the pixel right with carry
JR NC,Draw_Line_Q2_S
Draw_Line_Q2_M3:	INC L				; If we get a carry then move to adjacent screen address; more self modifying code
Draw_Line_Q2_S:		LD E,A				; Store the error value back in
CALL Pixel_Address_Down		; And also move down
DJNZ Draw_Line_Q2_L
RET

; Erase Line routine
; B = Y pixel position 1
; C = X pixel position 1
; D = Y pixel position 2
; E = X pixel position 2
;
Erase_Line:		LD A,D				; Check whether we are going to be drawing up
CP B
JR NC,Erase_Line_1

PUSH BC				; If we are, then this neat trick swaps BC and DE
PUSH DE				; using the stack, forcing the line to be always
POP BC				; drawn downwards
POP DE

Erase_Line_1:		CALL Get_Pixel_Address		; Get screen address in HL, pixel position (0-7) in A
;
; At this point we have
;  A = Pixel position (0-7)
; HL = Screen address of the start point
; BC = Start coordinate (B=Y1, C=X1)
; DE = End coordinates  (D=Y2, E=X2)
;
LD IX,Unplot_Point		; Point to the Unplot_Point table
ADD A,IXL			; Add the pixel position to get entry in table
LD IXL,A

LD A,D				; Calculate the line height in B (Y2-Y1)
SUB B
LD B,A

LD A,E				; Calculate the line width in C (X2-X1)
SUB C
JR C,Erase_Line_X1		; If carry set (negative result) then we are drawing from right to left
;
; This bit of code mods the main loop for drawing left to right
;
LD C,A				; Store the line width
LD A,&2C			; Code for INC L
LD (Erase_Line_Q1_M3),A		; Mod the code
LD (Erase_Line_Q2_M3),A
LD A,&0A			; Code for RRC D (CB 0A)
JR Erase_Line_X2		; Skip the next bit
;
; This bit of code mods the main loop for drawing right to left
;
Erase_Line_X1:		NEG				; The width of line is negative, so make it positive again
LD C,A				; Store the line width
LD A,&2D			; Code for DEC L
LD (Erase_Line_Q1_M3),A
LD (Erase_Line_Q2_M3),A
LD A,&02			; Code for RLC D (CB 02)
;
; We've got the basic information at this point
;
Erase_Line_X2:		LD (Erase_Line_Q1_M2 + 1),A	; A contains the code for RLC D or RRC D, so make the mods
LD (Erase_Line_Q2_M2 + 1),A
LD D,(IX+0)			; Get the pixel data from the Unplot_Point table
LD A,B				; Check if B and C are 0
OR C
JR NZ,Erase_Line_Q		; There is a line to draw, so skip to the next bit
LD A,(HL)			; Here we've got a single point line, so plot and return
AND D
LD (HL),A
RET
;
; At this point
; HL = Screen address of the start point
;  B = Line height
;  C = Line width
;  D = Pixel data
;
Erase_Line_Q:		LD A,B				; Work out which diagonal we are on
CP C
JR NC,Erase_Line_Q2
;
; This bit of code draws the line where B<C (more horizontal than vertical)
;
Erase_Line_Q1:		LD A,C
LD (Erase_Line_Q1_M1 + 1),A	; Self-mod the code again to store the line width
LD C,B
LD B,A
LD E,C				; Calculate the error value
SRL E
Erase_Line_Q1_L:	LD A,(HL)			; Unplot the pixel
AND D
LD (HL),A
LD A,E
SUB C
LD E,A
JR NC,Erase_Line_Q1_M2
Erase_Line_Q1_M1:	ADD A,0				; Add the line height (previously stored; self modifying code)
LD E,A
Erase_Line_Q1_M2:	RRC D				; Rotate the pixel right or left; more self-modifying code
JR C,Erase_Line_Q1_S		; Note the change here from the Draw_Line routine
Erase_Line_Q1_M3:	INC L				; If we get no carry then move to adjacent screen address; more self modifying code
Erase_Line_Q1_S:	DJNZ Erase_Line_Q1_L		; Loop until the line is drawn
RET
;
; This bit draws the line where B>=C (more vertical than horizontal, or diagonal)
;
Erase_Line_Q2:		LD (Erase_Line_Q2_M1 + 1),A
LD E,C				; Calculate the error value
SRL E
Erase_Line_Q2_L:	LD A,(HL)			; Unplot the pixel
AND D
LD (HL),A
LD A,E				; Get the error value
SUB C				; Add the line length to it (X2-X1)
JR NC,Erase_Line_Q2_S		; Skip the next bit if we don't get a carry
Erase_Line_Q2_M1: 	ADD A,0				; Add the line height (previously stored; self modifying code)
Erase_Line_Q2_M2:	RRC D				; Rotates the pixel right with carry
JR C,Erase_Line_Q2_S		; Note the change here from the Draw_Line routine
Erase_Line_Q2_M3:	INC L				; If we get no carry then move to adjacent screen address; more self modifying code
Erase_Line_Q2_S:	LD E,A				; Store the error value back in
CALL Pixel_Address_Down		; And also move down
DJNZ Erase_Line_Q2_L
RET

; Note that the functions above only work if each of these tables are in a byte boundary
;
Plot_Point:		DB %10000000,%01000000,%00100000,%00010000,%00001000,%00000100,%00000010,%00000001
Unplot_Point:		DB %01111111,%10111111,%11011111,%11101111,%11110111,%11111011,%11111101,%11111110

Plot_Line_LHS:		DB %11111111,%01111111,%00111111,%00011111,%00001111,%00000111,%00000011,%00000001
Plot_Line_RHS:		DB %10000000,%11000000,%11100000,%11110000,%11111000,%11111100,%11111110,%11111111
```