@PEN1
     *****************************************************************
     ***    Demonstration of Line Clipping in STOS Basic.          ***
     *****************************************************************

@PEN2
               by Jason J. Railton, for ICTARI User Group.




@PEN3
  This file is not intended to be read from the Desktop, but it may be
printed from there.  Run the program 'CLIPPING.PRG' or 'CLIPPING.BAS' with
this file ('CLIPPING.TXT') in the current directory.  The demonstration
program will display this file page by page, and insert graphical displays
at appropriate points.
@PEN1

@PAGE
  I hope you all saw last month's demonstration of calculating relative
positions from a moving, turning viewpoint.  Note that that was only for
points on a simple 2-D map.  3-D games such as flight simulators use those
same principles in three axes (X and Y for position on a map, and Z for
height), not just two.  They also have three angles of rotation for roll,
pitch (angle of elevation of the aircraft's nose) and yaw (heading, or
compass bearing), so three sets of those rotation formulae are combined
into some large equations.

  What I didn't have time to describe in detail before was the system of
angles I was using.

@PAGE
  When drawing a map, compass bearings are given going round clockwise,
strating from zero at due North (straight up the map).  When talking
geometry, we have the X-axis to the right (East on a map) and the Y-axis
straight up (North on a map).  Angles are then given (in degrees or radians)
as going round anti-clockwise, from zero on the X-axis.

  So, a horizontal line running to the right has an angle of 0 degrees.
A 30 degree line would run to the right and up a bit, and a line straight
up would have an angle of 90 degrees.  180 degrees is to the left, and
270 degrees is down.

@PAGE
  Now the position of the viewer on the map was given in X and Y co-
ordinates and a rotation angle.  The viewer would be looking due East along
the X-axis when this angle is zero.

  When I want to display 3-D graphics, I need to have three values.  One
is how far across the view a point is, from left to right.  The second
is how high above or below the viewer it is, and the third is how far
dead ahead of the viewer it is.

  The second point, height, is constant in my maze program.  The floor and
ceiling are always at the same height.  As for the first and third, these
are what I get from the rotation formulae.  I want these as simple X and Y
co-ordinates.  X being left to right across the view, and Y being distance
into the view.  For this reason my formulae must give me an extra 90 degrees
of rotation.

@PAGE
  This means that for a viewer looking along the X-axis, at zero degrees,
any points along the X-axis of the map are directly in front of the viewer.
So, points on the X-axis are turned 90 degrees so as to lie along the
view's Y-axis, into the screen.  Points lying on the Y-axis of the original
map, to the left of the viewer, get turned around to the view's negative
X-axis, so that they are drawn to the left of the screen.

  Points on the map's negative Y-axis (downward on the map) are turned
round to the view's positive X-axis, so that they appear to te right on the
final view.

  Imagine yourself standing on the map, looking East, and then think
where things placed on the compass points would appear to you.

@PAGE
  Now, I know I said last month that I'd go on to describe how to turn
relative co-ordinates into 3-D views, but there is one important step to go
before you can start drawing mazes in 3-D.  Small objects on the screen
aren't much of a problem, but when you start drawing an environment which
surrounds the viewer, some problems start to arise.

  When working in 3-D, most objects consist of straight lines, or polygons
(shapes with straight-line edges) which are described mathematically by
sets of co-ordinates.  The whole idea of a 3-D object is that it can be
viewed from any angle at any distance.  The drawback is that if you allow
the viewer complete freedom of movement, you don't know how big your objects
will appear on screen, and where they will be drawn.

  Let us assume for the moment that you just wish to draw the outline of
a cube on screen.  If the cube is small, and in the middle of the screen,
it doesn't present much of a problem to draw.  If the viewer moves in closer
to the cube, it will appear larger and larger, until it fills the screen.
If it gets too large, it may not entirely fit on the screen.

@PAGE
  So, sometimes lines can lie partly or wholly off the edge of the
screen, or outside the screen window in which the graphics are to
appear.  In the case of graphics in a window, this can spoil the
display.  With graphics being drawn off the edge of the screen by your
own code, you risk corrupting memory outside the screen memory area,
and crashing the computer.

  The following demo shows this happenning with a simple rectangular
shape, supposed to be drawn within a window on screen.


N.B. STOS Basic will actually stop with an error report if you try and draw
a line off screen.

@PAGE
Demonstration #1: '...sometimes lines can lie partly or wholly off the edge
of the screen, or outside the screen window in which the graphics are to
appear...'

@DEMO1
@PEN1
  So, before we can start drawing 3-D objects, we need to have a line
drawing routine that will allow for lines going off the edge of the screen.
The way to do this is simply to add a piece of code at the start of your
line drawing routine to cut off any part of the line which lies off the
screen, and only hand over to the drawing routine parts of lines which are
within the screen area.  This process is called 'clipping' the lines.

  This has to be working perfectly before you can start asking it to draw
3-D graphics, or else your program will fail and you won't know if it's
the 3-D maths or the graphics routines at fault.  The way I worked was to
write the program in STOS, building it up bit by bit (regular readers will
have seen my earlier demonstrations of cubes before I moved onto a full
maze) and then gradually replacing more and more of the BASIC with machine
code.  First the fill routines, then the line drawing, then the maths for
rotating the map, and finally the control routines to allow the program
to run on its own.

  (Don't worry about the word 'window' on the previous page.  It's just
that, for demonstration purposes, I'm going to clip the graphics to a
rectangle which is smaller than the entire screen, so that I can show you
what happens.  You may want to do this if your 3-D view has a fancy
border).

@PAGE
  The first thing to do is define the line to be clipped.  I'll use a
line defined by two points P and Q.  Each of these has co-ordinates
(Px,Py) and (Qx,Qy).

@DEMO2
@PEN1
  This line must be clipped against each edge of the screen.  First,
I'll start with the left-hand edge of the screen, to explain the
principles in detail.


  Now, there are four possible results from checking the line against
the left hand edge of the screen/window:

  1) The entire line lies to the left of the left-hand edge.

  2) The entire line lies to the right of the left-hand edge.

  3) P is to the left, but Q is to the right, so the line crosses
     the screen edge.

  4) P is to the right, but Q is to the left, so the line again
     crosses the screen edge.

@PAGE
  To analyse this mathematically, we need to define some more points.
Let's say that the left-hand edge of the screen, where the line is to
be clipped, is a vertical line at position Cx.  The four cases are
now...

1) If both Px and Qx are less than this value Cx, the entire line lies
   off the left-hand edge, and need not be drawn at all.

2) If both Px and Qx are greater than Cx, then the line lies to the
   right of this edge, so this stage of the clipping can be skipped.

3) If Px is less than Cx, but Qx is greater than or equal to Cx, then
   part of the line (the P-end) lies off screen, and part (the Q-end)
   is on screen, and should be drawn.  This is the case I will examine
   in detail.

4) If Px is greater than Cx, but Qx is less than Cx, then the P-end
   lies on screen, but the Q-end is off the left hand side of the
   screen.  More on this later.

@PAGE
  Look at the following diagram.  The vertical line represents the
left-hand edge of the screen at position Cx.  The line P-Q crosses it
at point C (Cx,Cy).  To clip the line we need to calculate Cy.  Then
we can draw line C-Q on screen [That is, draw (Cx,Cy)-(Qx,Qy)].

@DEMO3
@PEN1
  To find point C, we are going to work out what fraction of the line
is off the left-hand edge of the screen, then move that far along the
line to reach the clipping point C.  The line to draw is then the on-
screen part C-Q, and we forget about the off-screen part P-C.

@PAGE
 This process is made possible by the fact that the fraction of part
P-C of the overall line P-C-Q is the same whether we consider:

1) Distance along the line,  =    |PC| / |PQ|
2) Distance horizontally,    = (Cx-Px) / (Qx-Px)
3) Distance vertically,      = (Cy-Py) / (Qy-Py)

  The result of all three of the above is the same (although a
horizontal line would give zero/zero for the third option, and crash,
so we won't be actually doing that sum).

  The first sum requires a square root in the Pythagorus theorem to
work out lengths along the lines (that's where you do SQR(x^2 + y^2)
to find a distance), so we'll forget about that one.

  Of equations 2 and 3, we know Px, Py, Qx, Qy and also Cx.  The only
unknown is Cy, so here's how to solve it.  First we note that 2 and 3
give the same fraction of the line's length, so 2) = 3)...

@PEN2
  (Cx-Px) / (Qx-Px)   =   (Cy-Py) / (Qy-Py)
@PEN1

@PAGE
Now, taking it slowly...

@PEN2
  (Cx-Px) / (Qx-Px)   =   (Cy-Py) / (Qy-Py)
@PEN1

I'll re-write that...

@PEN2
          (Cx - Px)       (Cy - Py)
         -----------  =  -----------
          (Qx - Px)       (Qy - Py)
@PEN1

Now to multiply each side by (Qy-Py).  Watch what happens...

@PEN2
      (Qy - Py) * (Cx - Px)        (Qy - Py) * (Cy - Py)
    -------------------------  = -------------------------
      (Qx - Px)                    (Qy - Py)

@PEN3
                                   '---.----'
                                       :
@PEN1
                              This is like saying 'a/a' or '3/3',
                            simply a value of 1.  So this side is
                            now saying '1 * (Cy-Py)'.
@PAGE
So, in effect:

@PEN2
      (Qy - Py) * (Cx - Px)        (Qy - Py) * (Cy - Py)
    -------------------------  = -------------------------
      (Qx - Px)                    (Qy - Py)
@PEN1

becomes:

@PEN2
      (Qy - Py) * (Cx - Px)
    -------------------------  =   (Cy - Py)
      (Qx - Px)
@PEN1

Adding Py to both sides...

@PEN2
           (Qy - Py) * (Cx - Px)
  Py  +  -------------------------  =  Py  +  (Cy - Py)
           (Qx - Px)
@PEN1

@PAGE
Simplifying:

@PEN2
           (Qy - Py) * (Cx - Px)
  Py  +  -------------------------  =  Py  +  (Cy - Py)
           (Qx - Px)
@PEN3
                                       '------.------'
                                              :
@PEN1
                          This part says:     Py + Cy - Py
                          Re-arrange to get:  Py - Py + Cy
                                         or:     0    + Cy
                                    or just:            Cy

This gives:

@PEN2
           (Qy - Py) * (Cx - Px)
  Py  +  -------------------------  =  Cy
           (Qx - Px)
@PEN1

So now we know how to find Cy...

@PAGE
  Now, the point C is defined by co-ordinates Cx and Cy.  We know
where Cx is (the edge of the screen, for example '0'), and we can now
work out Cy using:

@PEN2
               (Qy - Py) * (Cx - Px)
Cy  =  Py  +  -----------------------
                     (Qx - Px)
@PEN1

  To implement this, do 'Qy-Py' and 'Cx-Px' first, and multiply the
results together.  Divide the answer by the result of 'Qx-Px'.
Finally, add Py to get the value of Cy...

@PEN2
Cy = Py + [  { (Qy-Py) * (Cx-Px) }  /  (Qx-Px)  ]
@PEN1

  Now you simply replace the values of Px and Py (the off-screen end
of the line) with the values of Cx and Cy.  Your line drawing routine
will suspect nothing and simply draw the line C-Q (all on-screen)
instead of P-Q (partially off-screen).

@PAGE
  All very well, you may say, but what if it is the P-end that is on-
screen, and the Q-end that is off-screen?

  Well, in that case, you use exactly the same value of Cx, and
exactly the same formula for Cy.  You just have to replace Qx and Qy
(not Px and Py) with the values of Cx and Cy.

  This makes your line drawing routine draw line section P-C, not C-Q.

@PAGE
  As for the right-hand side of the screen, again we use exactly the
same formula.  You just put the position of the right-hand edge in as
Cx (e.g. 319 for Low res or 639 for Med/Hi res), and calculate Cy as
before.  Point C is then a point on your line just on-screen, and you
just replace the off-screen point (either P or Q) with point C as
before.

  Just remember that your line might extend from off one side of the
screen, right the way across and off the other.  To get round this,
simply clip first one edge, and then the line which results from that
process is clipped against the opposite edge.  The line resulting from
the second clipping function is the one you draw.

  In this way, the line is slowly trimmed down by each edge-clipping
function in turn, until it is ready to be drawn.

  The next demo shows this clipping in action...

@PAGE
Where a point (end of a line) extends off the right or left edge of
the defined window, that point is replaced with the calculated edge-
point C...
@PEN2
Cx = x of edge,  Cy = Py + [   { (Qy-Py) * (Cx-Px) }  /  (Qx-Px)  ]
@PEN1

@DEMO4
@PEN1
  Now we come to the clipping at the top and bottom of the screen.
This works in exactly the same way as clipping at the right and left-
hand edges, we just swap all the Xs for Ys and vice-versa.

  In this case, we know the vertical position of the screen edge, so
we know Cy, the Y-co-ordinate of point C, just on screen.  We have to
work out Cx.  We could draw the same diagram as before, but swapping
the X and Y co-ordinates, but the result is obvious.  It's just the
same formula with the Xs and Ys exchanged:

@PEN3
    Cx = Px + [   { (Qx-Px) * (Cy-Py) }  /  (Qy-Py)  ]
@PEN1

  Again, first check to see if Py and Qy are both off the top or both
off the bottom of the screen.  If they are, no part of the line need
be drawn.

  If both are within the vertical screen range (e.g. 0-199 Lo/Med res
or 0-399 in Hi res), then the whole line can be drawn.

  If one end is off screen, replace that end's X/Y co-ordinates with
the values of Cx and Cy for that line at the screen edge.

@PAGE
  This is the demonstration of the full line clipping in action, but
I've not finished yet.  Anyway:
@PEN2
Cx = x of edge,  Cy = Py + [   { (Qy-Py) * (Cx-Px) }  /  (Qx-Px)  ]
@PEN3
Cy = y of edge,  Cx = Px + [   { (Qx-Px) * (Cy-Py) }  /  (Qy-Py)  ]
@PEN1

@DEMO5
  Finally, some tips on putting all this together.

  Start with a simple line-drawing routine.  If you're using STOS,
that's simply the command:

@PEN2
DRAW Px,Py to Qx,Qy
@PEN1

  Generate Px,Py,Qx and Qy at random, or from keyboard entry, and plot
some lines within the screen area.

  If you're using machine code, look on ICTARI disk #31 for Peter Hibbs'
line-drawing code, or write your own.  (Back issues available from the
usual address, etc...)

@PAGE
  Next add some code between where you generate the values and where
you plot the lines.  This code should skip the draw command if both
points are to the left of the screen edge.

  Then add some lines to check each end of the line against the left
screen edge.  If either point is off the screen edge, calculate Cy,
and replace the point's co-ordinates with (Cx,Cy).

  Increase the range of the numbers you're generating to test the
program.  Give it some points deliberately off the left-hand edge, and
some valid points, to test it.

@PAGE
  Now add similar code to check and clip at the right-hand edge.

  Test this, then do the top, and finally the bottom of the screen.

  Save a copy of this, and then see if your code can be speeded up at
all.

@PAGE
  Bear in mind that a line just off the top of the screen may still be
clipped in line with the left and right edges, before being rejected
by the top edge test.  This is necessary, as you can not be sure the
line is completely off the top of the screen until it has been clipped
to the left and to the right.

  Do the checks and clipping for the left and right edges separately
from those for the top and bottom edges.  Do not mix the two.

  However, you could do all the top and bottom checks and clipping
first, then the left and right checks and clipping.  Pick whichever
you think will reject the most lines as early as possible, to speed
things up.

  In my maze game, the walls of the maze extend far to your left and
right, and not so much above or below.  It makes sense to do the left
and right checks first, as this rejects the most lines at the first
stage of checking and so saves time.

@PAGE
  Finally, a note on programming the formula for calculating Cx or Cy:

@PEN2
   Given Cx, Cy = Py + [   { (Qy-Py) * (Cx-Px) }  /  (Qx-Px)  ]
@PEN3
or given Cy, Cx = Px + [   { (Qx-Px) * (Cy-Py) }  /  (Qy-Py)  ]
@PEN1

  The result of either formula is required as a screen co-ordinate, an
integer value.  However, doing a division of one number by a number of
similar size using integer maths (e.g 150/200) gives a very inaccurate
result.  So, is it possible to avoid real numbers?  This would
certainly help with machine code.

  The answer is 'yes'.  By carefully ordering the formula when you
program it in, you can save the divide instruction until the end, so
it is the last operation (well, next-to-last; we still have to add Px
or Py, but by then we're back to integers).

  By performing the multiplication first, we ensure that the number
we're dividing into later is much larger than the number we're
dividing by, so that an integer result isn't too bad.

@PAGE
  Another tip - if you really want to use fractions in machine code,
but don't want to write floating-point mathematics libraries, simply
multiply all your numbers by 2, 4, 256, 65536 or whatever (shift them
left a few places I mean) and do all the maths with the larger values.

  For example, if you multiply your numbers by 256 (shift left 8
places) you can use numbers from -128.0 to +127.9961, with an accuracy
down to 1/256 (0.0039), in 16 bits.  That means you can accurately
represent numbers to two decimal places, and you can still perform the
16-bit operations add, subtract, divide and multiply on these values.

  Just remember that when you divide or multiply, the results will be
16 (not 8) bits off, and will require shifting by 8 bits to correct
this.  Also remember to shift your numbers back when you want integer
values.

@PAGE
  If you want to write 32-bit multiply and divide code, to complement
the 32-bit add and subtract functions (it's not that hard - for the
divide you just DIVS once on the top 16 bits on their own, then DIVS
the remainder from that operation with the other 16 bits stuck on, and
stick the results together), then you can use 32 bit numbers with the
top 16 bits as the integer part, and the bottom 16 bits the fraction.

  This gives you a range of -32768.0 to +32767.99998, with an accuracy
of 1/65536 (0.0000153) or slightly better than 4 decimal places (not
quite accurate enough for 5).

  Also, taking an integer value is as simple as doing the 'SWAP'
instruction, and you have all the 16-bit functions on hand for your
integer maths.

  To convert an integer into a fraction of this sort, SWAP it and
CLR.W
to clear the fractional part (the bottom 16 bits), whether it's a
positive or negative number.

  Anyay, one last look at that clipping demo...

@PAGE
The formulae for clipping to the edge of the screen are:

@PEN 2
Cx = x of left/right edge, Cy = Py + [ {(Qy-Py) * (Cx-Px)} / (Qx-Px) ]
@PEN3
Cy = y of top/bottom edge, Cx = Px + [ {(Qx-Px) * (Cy-Py)} / (Qy-Py) ]
@PEN1

@DEMO5
@PEN1
Bye for now!

@EOF
