.........1.........2.........3.........4.........5.........6.........7.........8

LIGHT SCREEN DESIGNER
ZX Computing, February/March 1986
Part 10 of 13


In this, the penultimate [Not quite - three more parts followed. JimG]
part of the Light Screen Designer program, I shall be covering
ellipses. The program has two ellipse procedures; ELLIPSE and
QUARTER_ELLIPSE. First though, I'd like to talk about last issue's
article. One bug cropped up, which was that if the cursor was set to
print in italics, and cursor left was repeatedly pressed, strange
things would happen at the left hand edge of the screen. The bug
occurred at address E45E, where the byte reading 20 should have read 32.

The other error I made - a rather silly one - was that I forgot to
actually link the procedure into the rest of the program! This is of
course simple to do - you just store the address of the start of the
procedure in the command addresses table. The alterations in figure 3
will therefore (a) cure the bug, (b) link in the text procedure, and
(c) link in this edition's ellipse procedures as well (well, we might
as well while we're at it).

-----------------------------------------------------------------------------
[The first address was printed as E452, but that's in TC_RIGHT, not TC_LEFT,]
[and thus conflicts with the comments in the article.                   JimG]
E45E 32                 DEFB 32
DB46 1BE7               DEFW E71B,Q_ELLIPSE (QUARTER_ELLIPSE)
DB5E 90E6               DEFW E690,ELLIPSE
DB86 61E5               DEFW E561,TEXT_MODE (TEXT)

Figure 3. Alterations to last issue's routines.
-----------------------------------------------------------------------------


Ellipses

Anyway - ellipses. This is rather different from all the other line or
curve drawing routines we've covered so far because it doesn't make
any use of a ready made ROM routine. It can't, because there isn't
one. When we covered circles and arcs we were able to make use of the
ROM's CIRCLE or DRAW_ARC routines, but the Spectrum was never designed
to draw ellipses. This is something we have to arrange all by ourselves.

The program makes extensive use of the calculator memories - in fact
it needs sixteen of them. If you refer to the diagram in Figure 1
[PART10A.GIF], and compare it to the chart below (Figure 4), you'll
see exactly how these memories are used.

-----------------------------------------------------------------------------
M0 not used    Note that memories M0, M1 and M2
M1 not used    are corrupted by the functions
M2 not used    SIN and COS
M3 i           The increment of the angle t on each pass of the loop
M4 s           The inclination of the major axis to the horizontal
M5 t           Eccentric angle of point (x,y) on ellipse
M6 a           Half of the major axis
M7 b           Half of the minor axis
M8 Mx-Px
M9 My-Py
MA a*cos t
MB b*sin t
MC cos s
MD sin s
ME Px          x coordinate of centre of ellipse
MF Py          y coordinate of centre of ellipse

Figure 4. Use of calculator memories
-----------------------------------------------------------------------------

If you don't understand any of the terms in the list you should be
able to suss it all out by looking at the diagram. Onto the program.

The first subroutine is called ELL_SUB. This subroutine calculates the
coordinates (x,y) of a point on the ellipse. For each different angle,
t, a different point on the ellipse will be calculated. The resulting
coordinates will be left on the top of the calculator stack.

The next subroutine is called ELL_Q. This is the routine which
actually draws the ellipse. The first thing the routine does is to
calculate N, which is the number of points around the ellipse needed
to give a smooth looking curve. (Actually the number is 4*N, since N
is the number of points needed for a quarter ellipse). Then it
calculates i, the angle needed to ensure that 4*N points are plotted
evenly around the ellipse. NB is transferred into the BC register
pair, and i is stored in memory three. Note that the routine will only
work if memories M4 to MF are first assigned as above. Anyway, the
subroutine then proceeds to draw the curve by running into the
subroutine CURVE.

CURVE is the subroutine which draws a curve. It is completely general
and will in fact draw any curve whatsoever, be it an ellipse, a
spirograph pattern, or a superior epitrochoid! It requires three
things:
(1) That BC contains the number of line segments to be drawn.
(2) That HL contains the address of a subroutine which calculates the
    next point on the curve, and possibly
(3) if this subroutine requires any quantities stored in calculator
    memories, that such memories are initialised.

CREATE_MEM will create new calculator memories. It requires that BC
contains five times the number of memories needed. On return the new
memories will be numbered from M0 upwards and should all be considered
to contain rubbish. Note that it is usually necessary to restore all
calculator memories to normal before returning to BASIC, and this may
be done by loading MEMBOT (at address 5C68) with the value 5C92. [The
author kept referring to 5C68 as MEM, but it's called MEMBOT in the
Spectrum manual. JimG]

At address E690 is the ELLIPSE subroutine itself. All it requires is
that the three cursors are in the right place on the screen. It works
by first calculating Px and Py - the coordinates of the centre of the
ellipse, and then calculating a, the length of half of the major axis.
It then calls the ANGLE subroutine listed in Part 8 to work out s, the
inclination of the ellipse. Next it has to work out b, the length of
the minor axis, which it does by first working out u and v (see Figure
1 [PART10A.GIF]). It sets up the memories ready to draw the ellipse
and uses the ELL_Q subroutine to do the actual drawing.

STK_REGS is quite a boring subroutine really. All it does is to put
the contents of the registers onto the calculator stack in the order,
L,H,E,D,C,B - with B being at the top.

At address E71B is the QUARTER_ELLIPSE procedure. This first of all
works out which quadrant the quarter-ellipse falls into, then sets up
the memories and uses ELL_Q to do the drawing.


How to use the Procedures

To use the ELLIPSE procedure you need to place the Origin Cursor and
the Main Cursor at either end of the major axis, with the Marker
Cursor at any other point on the ellipse. Figure 2 [PART10B.GIF] will
show you what I mean.

The QUARTER_ELLIPSE is a little different. Whereas a whole ellipse may
be drawn at any angle, a quarter ellipse must always be upright (with
the major axis horizontal). A quarter ellipse will always be drawn
anticlockwise from the origin cursor to the main cursor. Again, Figure
2 [PART10B.GIF] will show you what I mean. One final thing I should
point out is that BREAK will work throughout the drawing of these
curves. If you press CAPS SHIFT with SPACE whilst the program is in
the middle of drawing a curve then the program will simply stop
drawing the curve and return to the main Light Screen Designer program.

The next article will be the final part in the Light Screen Designer
series. It will complete the program, by talking about painting, or
colouring in, outlines. See you then.

Toni Baker

-----------------------------------------------------------------------------
E5FE EF       ELL_SUB   RST  #28           ;Engage the calculator
E5FF EE                 recall ME          Px
     E5                 recall M5          Px,t
E601 20                 cos                Px,cos t
     E6                 recall M6          Px,cos t,a
E603 04                 multiply           Px,a*cos t
E604 CA                 store MA           (MA= a*cos t)
     EC                 recall MC          Px,a*cos t,cos s
     04                 multiply           Px,a*cos s*cos t
E607 0F                 add                Px+a*cos s*cos t
E608 E5                 recall M5          Px+a*cos s*cos t,t
E609 1F                 sin                Px+a*cos s*cos t,sin t
E60A E7                 recall M7          Px+a*cos s*cos t,sin t,b
E60B 04                 multiply           Px+a*cos s*cos t,b*sin t
E60C CB                 store MB           (MB= b*sin t)
     ED                 recall MD          Px+a*cos s*cos t,b*sin t,sin s
E60E 04                 multiply           Px+a*cos s*cos t,b*sin s*sin t
E60F 03                 subtract           Px+a*cos s*cos t-b*sin s*sin t
E610 A2                 const half         Px+a*cos s*cos t-b*sin s*sin t,1/2
E611 0F                 add                Px+a*cos s*cos t-b*sin s*sin t+1/2
E612 27                 int                x
E613 EF                 recall MF          x,Py
E614 EA                 recall MA          x,Py,a*cos t
     ED                 recall MD          x,Py,a*cos t,sin s
     04                 multiply           x,Py,a*sin s*cos t
E617 0F                 add                x,Py+a*sin s*cos t
E618 EB                 recall MB          x,Py+a*sin s*cos t,b*sin t
E619 EC                 recall MC          x,Py+a*sin s*cos t,b*sin t,cos s
     04                 multiply           x,Py+a*sin s*cos t,b*cos s*sin t
     0F                 add                x,Py+a*sin s*cos t+b*cos s*sin t
E61C A2                 const half         x,Py+a*sin s*cos t+b*cos s*sin t,1/2
E61D 0F                 add                x,Py+a*sin s*cos t+b*cos s*sin t+1/2
E61E 27                 int                x,y
E61F E5                 recall M5          x,y,t
E620 E3                 recall M3          x,y,t,i
E621 0F                 add                x,y,t+i
E622 C5                 store M5           (M5= newly incremented angle t+i)
E623 02                 delete             x,y
E624 38                 end calc           ;Disengage the calculator
E625 C9                 RET
-----------------------------------------------------------------------------
E626 F5       ELL_Q     PUSH AF            ;Stack the carry flag
E627 EF                 RST  #28           ;Engage the calculator
E628 E6                 recall M6          a
     E7                 recall M7          a,b
E62A 0F                 add                a+b
E62B 34B00E2C19         stk data pi/(2*sqr 2) a+b,pi/(2*sqr 2)
E630 04                 multiply           (a+b)*pi/(2*sqr 2)
E631 27                 int                int((a+b)*pi/(2*sqr 2))
E632 A1                 const one          int((a+b)*pi/(2*sqr 2)),1
E633 0F                 add                N
E634 31                 duplicate          N,N
     A3                 const pi/2         N,N,pi/2
     01                 exchange           N,pi/2,N
E637 05                 divide             N,i
E638 C3                 store M3           (M3= i, the increment in angle)
     02                 delete             N
     38                 end calc           ;Disengage the calculator
E63B CDA22D             CALL FP_TO_BC      ;BC= N (number of increments in 
                                           ;a quarter ellipse)
E63E F1                 POP  AF            ;Restore the carry flag
E63F 3806               JR   C,DR_ELL      ;Jump if only quarter ellipse reqd
E641 60                 LD   H,B
E642 69                 LD   L,C
E643 29                 ADD  HL,HL
E644 29                 ADD  HL,HL
E645 44                 LD   B,H
E646 4D                 LD   C,L           ;BC= 4*N
E647 21FEE5   DR_ELL    LD   HL,ELL_SUB    ;HL= address of ellipse subroutine
-----------------------------------------------------------------------------
E64A C5       CURVE     PUSH BC            ;Stack number of passes
E64B E5                 PUSH HL            ;Stack address of subroutine
E64C CD2C16             CALL CALL_JUMP     ;Call subroutine required
E64F CDDC22             CALL PLOT          ;Plot the first point
E652 E1                 POP  HL            ;HL= subroutine address
E653 C1                 POP  BC            ;BC= number of passes
E654 C5       CV_LOOP   PUSH BC
E655 E5                 PUSH HL
E656 CD2C16             CALL CALL_JUMP     ;Call subroutine
E659 CD0723             CALL STK_TO_BC     ;B= x; C= y
E65C 110101             LD   DE,#0101
E65F 78                 LD   A,B           ;A= x
;[I think that the commands at E660 & E66B should be swapped around,]
;[as COORDS holds (x,y), not (y,x).                             JimG]
E660 FD9644             SUB  (COORDS+1)    ;A= x displacement
E663 3004               JR   NC,CV_1
E665 16FF               LD   D,#FF         ;D= FF to indicate x disp negative
E667 ED44               NEG                ;A= abs(x displacement)
E669 47       CV_1      LD   B,A           ;B= abs(x displacement)
E66A 79                 LD   A,C           ;A= y
E66B FD9643             SUB  (COORDS)      ;A= y displacement
E66E 3004               JR   NC,CV_2
E670 1EFF               LD   E,#FF         ;E= FF to indicate y disp negative
E672 ED44               NEG
E674 4F       CV_2      LD   C,A           ;C= abs(y displacement)
E675 CDBA24             CALL DRAW_3        ;Draw line segment
E678 CD541F             CALL BREAK_KEY     ;Test if CAPS SHIFT/SPACE pressed
E67B E1                 POP  HL
E67C C1                 POP  BC
E67D 3005               JR   NC,CV_EXIT    ;Jump if so
E67F 0B                 DEC  BC
E680 78                 LD   A,B
E681 B1                 OR   C
E682 20D0               JR   NZ,CV_LOOP    ;Loop back if not finished
E684 215827   CV_EXIT   LD   HL,#2758
E687 D9                 EXX                ;HL'= 2758
E688 C9                 RET
-----------------------------------------------------------------------------
E689 F7       CREATEMEM RST  #30           ;Create memory in workspace
E68A 1B                 DEC  DE            ;DE points to first byte of area
E68B ED53685C           LD   (MEMBOT),DE   ;Point calculator memory to area
E68F C9                 RET

E690 CD22E0   ELLIPSE   CALL TEST_MRKR     ;Return if marker cursor not in use
E693 C5                 PUSH BC            ;Stack cursor coordinates
E694 CD0AE2             CALL STARTLINE     ;Get cursor coords into calc mems
E697 EF                 RST  #28           ;Engage the calculator
E698 E2                 recall M2          My
     E3                 recall M3          My,Mx
     E0                 recall M0          My,Mx,Oy
E69B E4                 recall M4          My,Mx,Oy,Cy
     0F                 add                My,Mx,Cy+Oy
     A2                 const half         My,Mx,Cy+Oy,1/2
E69E 04                 multiply           My,Mx,Py
E69F 31                 duplicate          My,Mx,Py,Py
     E4                 recall M4          My,Mx,Py,Py,Cy
     03                 subtract           My,Mx,Py,Py-Cy
E6A2 31                 duplicate          My,Mx,Py,Py-Cy,Py-Cy
     04                 multiply           My,Mx,Py,(Py-Cy)^2
     E5                 recall M5          My,Mx,Py,(Py-Cy)^2,Cx
E6A5 E1                 recall M1          My,Mx,Py,(Py-Cy)^2,Cx,Ox
E6A6 0F                 add                My,Mx,Py,(Py-Cy)^2,Cx+Ox
E6A7 A2                 const half         My,Mx,Py,(Py-Cy)^2,Cx+Ox,1/2
E6A8 04                 multiply           My,Mx,Py,(Py-Cy)^2,Px
E6A9 C3                 store M3           (M3= Px)
     E5                 recall M5          My,Mx,Py,(Py-Cy)^2,Px,Cx
     03                 subtract           My,Mx,Py,(Py-Cy)^2,Px-Cx
E6AC 31                 duplicate          My,Mx,Py,(Py-Cy)^2,Px-Cx,Px-Cx
     04                 mutliply           My,Mx,Py,(Py-Cy)^2,(Px-Cx)^2
     0F                 add                My,Mx,Py,(Py-Cy)^2+(Px-Cx)^2
E6AF E3                 recall M3          My,Mx,Py,(Py-Cy)^2+(Px-Cx)^2,Px
E6B0 01                 exchange           My,Mx,Py,Px,(Py-Cy)^2+(Px-Cx)^2
     28                 sqr                My,Mx,Py,Px,a
     01                 exchange           My,Mx,Py,a,Px
E6B3 C3                 store M3           (M3= Px)
     02                 delete             My,Mx,Py,a
     01                 exchange           My,Mx,a,Py
E6B6 C2                 store M2           (M2= Py)
     E3                 recall M3          My,Mx,a,Py,Px
     38                 end calc           ;Disengage the calculator
E6B9 CD65E2             CALL ANGLE         ;Calculate the inclination, s,
                                           ;of the major axis to the horizontal
E6BC 015000             LD   BC,#0050
E6BF CD89E6             CALL CREATEMEM     ;Create sixteen calculator memories
E6C2 EF                 RST  #28           ;Re-engage the calculator
                                           My,Mx,a,Py,Px,s
E6C3 C4                 store M4           (M4= s)
     02                 delete             My,Mx,a,Py,Px
     CE                 store ME           (ME= Px)
E6C6 02                 delete             My,Mx,a,Py
E6C7 CF                 store MF           (MF= Py)
E6C8 02                 delete             My,Mx,a
E6C9 C6                 store M6           (M6= a)
     02                 delete             My,Mx
E6CB C8                 store M8           (M8= Mx)
E6CC 02                 delete             My
E6CD C9                 store M9           (M9= My)
E6CE 02                 delete
E6CF E4                 recall M4          s
     20                 cos                cos s
     CC                 store MC           (MC= cos s)
E6D2 E4                 recall M4          cos s,s
     1F                 sin                cos s,sin s
     CD                 store MD           (MD= sin s)
E6D5 E8                 recall M8          cos s,sin s,Mx
E6D6 EE                 recall ME          cos s,sin s,Mx,Px
     03                 subtract           cos s,sin s,Mx-Px
E6D8 C8                 store M8           (M8= Mx-Px)
E6D9 04                 multiply           cos s,(Mx-Px)*sin s
E6DA 1B                 negate             cos s,-(Mx-Px)*sin s
E6DB 01                 exchange           -(Mx-Px)*sin s,cos s
     E9                 recall M9          -(Mx-Px)*sin s,cos s,My
     EF                 recall MF          -(Mx-Px)*sin s,cos s,My,Py
E6DE 03                 subtract           -(Mx-Px)*sin s,cos s,My-Py
E6DF C9                 store M9           (M9= My-Py)
E6E0 04                 multiply           -(Mx-Px)*sin s,(My-Py)*cos s
E6E1 0F                 add                u

E6E2 A1                 const one          u,1
E6E3 E8                 recall M8          u,1,Mx-Px
E6E4 EC                 recall MC          u,1,Mx-Px,cos s
     04                 multiply           u,1,(Mx-Px)*cos s
     E9                 recall M9          u,1,(Mx-Px)*cos s,My-Py
E6E7 ED                 recall MD          u,1,(Mx-Px)*cos s,My-Py,sin s
E6E8 04                 multiply           u,1,(Mx-Px)*cos s,(My-Py)*sin s
E6E9 0F                 add                u,1,v
E6EA 31                 duplicate          u,1,v,v
     04                 multiply           u,1,v^2
     E6                 recall M6          u,1,v^2,a
E6ED 31                 duplicate          u,1,v^2,a,a
     04                 multiply           u,1,v^2,a^2
     05                 divide             u,1,v^2/a^2
E6F0 03                 subtract           u,1-v^2/a^2
E6F1 28                 sqr                u,sqr(1-v^2/a^2)
     05                 divide             b
E6F3 C7                 store M7           (M7= b, the minor axis)
E6F4 02                 delete
E6F5 A0                 const zero         0
E6F6 C5                 store M5           (M5= t= 0)
E6F7 02                 delete
E6F8 38                 end calc           ;Disengage the calculator
E6F9 CD38DF             CALL CANCEL_MK     ;Cancel the marker cursor
E6FC A7                 AND  A             ;Reset carry to indicate ellipse
E6FD CD26E6   ELL_2     CALL ELL_Q         ;Draw the ellipse
E700 CDBF16             CALL SET_WORK      ;Clear workspace & restore calc mems
E703 C399E0             JP   CC_MOVE       ;Move the origin cursor and return
-----------------------------------------------------------------------------
E706 C5       STK_REGS  PUSH BC
E707 D5                 PUSH DE
E708 E5                 PUSH HL
E709 0603               LD   B,#03
E70B E1       STRG_LOOP POP  HL            ;HL= next reg pair from stack
E70C C5                 PUSH BC            ;Stack B
E70D E5                 PUSH HL            ;Stack H
E70E 7D                 LD   A,L
E70F CD282D             CALL STACK_A       ;Stack one reg onto calc stack
E712 E1                 POP  HL
E713 7C                 LD   A,H
E714 CD282D             CALL STACK_A       ;Stack one reg onto calc stack
E717 C1                 POP  BC
E718 10F1               DJNZ STRG_LOOP
E71A C9                 RET
-----------------------------------------------------------------------------
E71B C5       Q_ELLIPSE PUSH BC            ;Stack cursor coordinates
E71C ED5B0EDB           LD   DE,(ORIGIN+2) ;DE= Origin  cursor coords
E720 7A                 LD   A,D           ;A= Oy
E721 90                 SUB  B             ;A= difference in y coords
E722 3810               JR   C,QEL_2
E724 67                 LD   H,A           ;H= b, minor axis
E725 7B                 LD   A,E           ;A= Ox
E726 91                 SUB  C             ;A= difference in x coords
E727 3805               JR   C,QEL_1
E729 6F                 LD   L,A           ;L= a, major axis
E72A 3E01               LD   A,#01         ;A= quadrant number
E72C 1818               JR   QEL_5
E72E ED44     QEL_1     NEG
E730 6F                 LD   L,A           ;L= a, major axis
E731 AF                 XOR  A             ;A= 0, quadrant number
E732 180A               JR   QEL_3
E734 ED44     QEL_2     NEG
E736 67                 LD   H,A           ;H= b, minor axis
E737 7B                 LD   A,E           ;A= Ox
E738 91                 SUB  C             ;A= difference in x coords
E739 3806               JR   C,QEL_4
E73B 6F                 LD   L,A           ;L= a, major axis
E73C 3E02               LD   A,#02         ;A= quadrant number
E73E 4B       QEL_3     LD   C,E           ;BC= coords of ellipse centre
E73F 1806               JR   QEL_6
E741 ED44     QEL_4     NEG
E743 6F                 LD   L,A           ;L= a, major axis
E744 3E03               LD   A,#03         ;A= quadrant number
E746 42       QEL_5     LD   B,D           ;BC= coords of ellipse centre
E747 1600     QEL_6     LD   D,#00         ;D= 0, inclination of ellipse
E749 5F                 LD   E,A           ;E= quadrant number
E74A CDEADE             CALL ADJUST_B      ;Adjust coords to ROM convention
E74D CD06E7             CALL STK_REGS      ;Put regs onto calculator stack
E750 015000             LD   BC,#0050
E753 CD89E6             CALL CREATEMEM     ;Create sixteen calculator memories

E756 EF                 RST  #28           ;Engage the calculator
                                           a,b,q,0,Px,Py
E757 CF                 store MF           (MF= Py)
E758 02                 delete             a,b,q,0,Px
E759 CE                 store ME           (ME= Px)
     02                 delete             a,b,q,0
E75B C4                 store M4           (M4= s= 0; inclination of ellipse)
     CD                 store MD           (MD= sin s= 0)
     02                 delete             a,b,q
E75E A1                 const one          a,b,q,1
E75F CC                 store MC           (MC= cos s= 1)
     03                 subtract           a,b,q-1
     A3                 const pi/2         a,b,q-1,pi/2
E762 04                 multiply           a,b,(q-1)*pi/2
E763 C5                 store M5           (M5= t= angle corresponding 
                                            to start of curve
E764 02                 delete             a,b
E765 C7                 store M7           (M7= b)
E766 02                 delete             a
E767 C6                 store M6           (M6= a)
     02                 delete
E769 38                 end calc           ;Disengage the calculator
E76A 37                 SCF                ;Set carry to indicate
                                           ;quarter ellipse only
E76B C3FDE6             JP   ELL_2         ;Draw curve and finish off
-----------------------------------------------------------------------------
