-
-
Notifications
You must be signed in to change notification settings - Fork 20
/
vector.z80
473 lines (451 loc) · 14.2 KB
/
vector.z80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
;
; Title: ZX Spectrum Vector Output Routines
; Author: Dean Belfield
; Created: 30/06/2012
; Last Updated: 30/05/2020
;
; Requires: output
;
; 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
; 01/04/2020: Moved Draw_Horz_Line_Solid & Draw_Horz_Line_Texture to vector_filled.z80
; 21/05/2020: Fixed bug in Draw_Line where final pixel not plotted
; 30/05/2020: Fixed bug in implementation of Bresenham for Draw_Line and Erase_Line
;
;
; 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
ADD A,C ; Add pixel position to get entry in 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
ADD A,C
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 (D1) = 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
ADD A,IXL
LD C,A
LD A,D
ADD A,IXH
LD B,A
CALL Plot ; Plot CX+X,CY+Y
LD A,E
SUB IXL
LD C,A
LD A,D
ADD A,IXH
LD B,A
CALL Plot ; Plot CX-X,CY+Y
LD A,E
ADD A,IXL
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
ADD A,IXL
LD B,A
LD A,E
ADD A,IXH
LD C,A
CALL Plot ; Plot CY+X,CX+Y
LD A,D
SUB IXL
LD B,A
LD A,E
ADD A,IXH
LD C,A
CALL Plot ; Plot CY-X,CX+Y
LD A,D
ADD A,IXL
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
ADD HL,DE ; Delta=Delta+D1
JR Draw_Circle_2 ;
Draw_Circle_1: ADD HL,BC ; Delta=Delta+D2
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 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,0x2C ; Code for INC L
LD (Draw_Line_Q1_M3),A ; Mod the code
LD (Draw_Line_Q2_M3),A
LD A,0x0A ; 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,0x2D ; Code for DEC L
LD (Draw_Line_Q1_M3),A
LD (Draw_Line_Q2_M3),A
LD A,0x02 ; 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 Z,Draw_Line_P ; There is no line, so just plot a single point
;
; At this point
; HL = Screen address of the start point
; B = Line height (YL)
; C = Line width (XL)
; 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 ; A = XL
LD (Draw_Line_Q1_M1 + 1),A ; Self-mod the code to store XL in loop
LD C,B ; C = YL
LD B,A ; B = XL (loop counter)
LD E,B ; E = XL
SRL E ; E = XL / 2 (error)
Draw_Line_Q1_L: LD A,(HL) ; Plot the pixel
OR D
LD (HL),A
LD A,E ; Add the line height to the error (E = E - YL)
SUB C
LD E,A
JR NC,Draw_Line_Q1_M2
Draw_Line_Q1_M1: ADD A,0 ; Add the line width to the error (E = E + XL) - previously self-modded
LD E,A
CALL Pixel_Address_Down
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
Draw_Line_P: LD A,(HL) ; Plot the final point
OR D
LD (HL),A
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 ; Self-mod the code to store YL in loop
LD E,B ; E = YL
SRL E ; E = YL / 2 (error)
Draw_Line_Q2_L: LD A,(HL) ; Plot the pixel
OR D
LD (HL),A
LD A,E ; Add the line width to the error
SUB C ;
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 to the error (E = E + XL) - previously self-modded
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
JR Draw_Line_P ; Plot the final point
; 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,0x2C ; Code for INC L
LD (Erase_Line_Q1_M3),A ; Mod the code
LD (Erase_Line_Q2_M3),A
LD A,0x0A ; 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,0x2D ; Code for DEC L
LD (Erase_Line_Q1_M3),A
LD (Erase_Line_Q2_M3),A
LD A,0x02 ; 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,B ; 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
CALL Pixel_Address_Down
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
Erase_Line_P: LD A,(HL) ; Plot the final pixel
AND D
LD (HL),A
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,B ; 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
JR Erase_Line_P ; Plot the final pixel
; 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