



                   The Definitive Guide to Bouncing Planets
                       (a document with a modest title)


                                  Simon Hern

                             22 Harrington Drive
                               Bedford MK41 8DB
                                   England


          This document describes how to create a computer animation
          of   a   rotating   planet  using  simple  texture-mapping
          techniques. It  should  make  an  interesting  programming
          project  for  someone  with  a basic knowledge of assembly
          language and computer graphics.





   August, 1992
   Revised December, 1992
   Revised April, 1994





   1.  Introduction


   1.1.  Planets, Past and Present

        In the spring of 1990 a computer animation  of  a  rotating  planet
   lived  out  a  brief,  anonymous but happy existence. Years have passed.
   Now here's the sequel.

        The greatest improvement of this program over the  hacked-together-
   in-Basic  original is the neatness and completeness of its design.  More
   interesting improvements are:

        *   Faster animation techniques.

        *   Choice of planet sizes and axes of rotation.

        *   Generation of semi-convincing planet surfaces.

        *   Illumination effects.

        The makers of the original 'Star Trek' would've died  to  use  this
   animation as a special effect. That's how good it is.


   1.2.  Disclaimers and Apologies

        There are a few things I ought to mention before going on.

        Firstly, I am by  no  stretch  of  the  imagination  an  expert  in
   computer  graphics,  or  for  that  matter  computer  programming of any
   description.  Nearly all of what follows I figured out for myself during
   boring  physics  lessons (something I've always felt quite proud of). If
   there are any books that describe how to do this  sort  of  thing,  I've
   never seen them (though I would certainly like to).

        Secondly, a work of stunning originality this isn't. When I started
   this project I had never seen or heard of anything like it. But that was
   probably more by chance than anything else since I've now had reports of
   people  writing  spinning planet animations (along very similar lines to
   what I'm describing here) as far back as the mid-eighties.

        Finally, I must apologize for the naming of some  of  the  concepts
   described  here  (Gush,  Complexion,  Riff, etc). They are not technical
   terms, merely the inventions of a warped mind (I was reading  a  lot  of
   Clive Barker books at the time).

        That's enough of that. Now on with the show.


   1.3.  Overview of the Program

        The planet program divides up into three  basic  segments  which  I
   will cover (in long-winded detail) in turn.

        The first part of the program creates a large array of data  called
   the  Gush  which  is required for the rapid animation of the planet. See
   Section 2.

        The second part of the program generates a random surface  for  the
   planet  and  gives  it  colour.  The  resulting data array is called the
   Complexion.  See Section 3.

        The third part of the program uses these two data arrays to animate
   the  planet  in real time. For each frame of animation the entire planet
   is redrawn, rotated as required. See Section 5.

        Sections 4 and 6 contain  details  on  how  the  animation  can  be
   implemented in practice.


   1.4.  Planet Shapes and Sizes

        The following constant values appear in various  places  throughout
   the program. They control certain aspects of the planet's appearance.

   RADIUS  This (integer) value is the radius in pixels of the planet as it
           appears on the screen.

   DIST    This is the apparent distance of the observer from  the  planet.
           It  is  measured  in  pixels  from the planet's centre and would
           usually take a value in the order of several times the width  of
           the  screen.  It  must have a value greater than that of RADIUS.
           (The effect this value has is, to be honest, pretty minimal.)

   rho     This value (an  integer)  is  the  resolution  of  the  planet's
           surface.   It  is  the  number  of  points of latitude (north to
           south) and half the number of points of  longitude  (around  the
           equator) used when giving coordinates to the landscape.

   phi     This is the angle through which the  axis  of  rotation  of  the
           planet  is  tilted  backwards  from the vertical (as seen by the
           observer).

   theta   This is the angle through which the  axis  of  rotation  of  the
           planet is turned clockwise (as seen by the observer).

        phi and theta do not need to be in any particular units since it is
   their  sines  and cosines that are required and the units conversion can
   take place when these are calculated.

        These constants will be discussed in more detail as they are used.


   2.  Writing the Gush: Data for Spinning a Planet


   2.1.  From Screen to Surface

        A lot of calculations need to be performed before drawing a planet.
   To  speed  up  the  animation  procedure,  the  results of most of these
   calculations are worked out in advance and stored in a data array  which
   I  call  the Gush.  The Gush consists of data for transforming points of
   the planet as seen on the screen to points on the mercator  map  of  the
   planet's surface.

        Two stages are  required  to  create  the  Gush.  The  first  stage
   considers  the  Gush  as  a two-dimensional grid (the Square Gush), with
   each point corresponding to a pixel on  the  screen,  and  performs  the
   lengthy  floating-point  calculations.   In the second stage the data in
   the Gush is rearranged  to  make  it  more  readily  accessable  to  the
   animation routines. This second stage Gush is called the Line Gush.


   2.2.  Constants and the Gush

        All of the constants described in Section 1.4 are  referred  to  in
   the Gush calculations.

        It is worth  noting  that  the  values  of  DIST,  phi,  theta  and
   (possibly)  RADIUS  are  needed  only  when  creating  the Gush and will
   probably not appear anywhere else in the program. As  a  result,  Gushes
   created   using  different  values  for  these  constants  can  be  used
   interchangeably.


   2.3.  The Square Gush

        The Square Gush is an array of size 2*RADIUS  by  2*RADIUS  and  is
   referenced  by  variables xs and ys, both of which take values from 0 to
   2*RADIUS-1.  Each entry in the array holds two values: xm and  ym,  both
   non-negative integers of size depending on the choice of rho.

        The array will be described using the notation  gush[ys][xs].xm_val
   and  gush[ys][xs].ym_val  to mean the values of xm and ym at the (xs,ys)
   position in the array.

        The points of the Square Gush correspond directly to the pixels  of
   the  planet's  image on the screen; the empty space around the planet is
   represented by null values in the array.

        The values of xm and ym for a point (xs,ys) are calculated  in  the
   following way.

        First, define a new constant, g, as

            g = DIST * RADIUS / sqrt( DIST*DIST + RADIUS*RADIUS )
                        [sqrt() meaning 'square root']

        Next, to make the calculations  faster,  work  out  the  sines  and
   cosines of phi and theta, calling them sph, sth, cph and cth.

        For each point (xs,ys) evaluate the following temporary values:

                            xg = xs - RADIUS + 0.5
                            yg = ys - RADIUS + 0.5

                          j = sqrt( xg*xg + yg*yg )

        If j is greater than RADIUS then (xs,ys) is  not  a  point  in  the
   planet's  image  and  should be ignored; set xm = ym = -1 (or some other
   null value). Otherwise,

              h = sqrt( DIST*DIST * ( g*g - j*j ) + g*g * j*j )
                 k = ( DIST*DIST - h ) / ( DIST*DIST + j*j )

                                 xp = k * xg
                                 yp = k * yg
                       zp = sqrt( g*g - xp*xp - yp*yp )

        (The  vector  (xp,yp,zp),  called  P,  can  be  used  to  calculate
   illumination effects. See Section 6.2.)

                    xt = xp*cth - yp*sth*cph + zp*sth*sph
                    yt = xp*sth + yp*cth*cph - zp*cth*sph
                             zt = yp*sph + zp*cph

                           w = sqrt( g*g - yt*yt )
                            alpha = acos( yt / g )
                           beta = acos( - xt / w )
                      [acos() meaning 'inverse cosine']

        All of these calculations use floating-point arithmetic. xm and ym,
   on the other hand, are integers, and when converting from floating-point
   to integer values in the next set of calculations the 'integer part'  of
   the  number (the largest integer not greater than that number) should be
   taken.

                          xm = (int) beta * rho / pi
                         ym = (int) alpha * rho / pi

          [pi is (predictably enough) the constant 3.14159265(etc).
         All the trigonometric calculations here operate in radians.]

                      if xm = rho then let xm = rho - 1
                      if ym = rho then let ym = rho - 1
                    if zt < 0 then let xm = 2*rho - 1 - xm

        xm takes values from 0 to 2*rho-1 and ym takes  values  from  0  to
   rho-1.   These are the coordinates of a point on the mercator map of the
   planet's surface.

        The values of xm and ym can now be stored in the Gush array.

                           gush[ys][xs].xm_val = xm
                           gush[ys][xs].ym_val = ym


   2.4.  The Line Gush

        The Line Gush is all of the important data  from  the  Square  Gush
   rearranged  in  such  a  way  as  to make the final animation as fast as
   possible (this being where the name 'Gush' comes in).

        The Line Gush takes the form of a one-dimensional array,  a  string
   of values in the order they are used to draw the planet.

        The actual format of the Line Gush is determined by  the  procedure
   used  to  display  the  planet. Here is an example Line Gush format. For
   more details see Sections 4 and 5.

        The first entry in the Line Gush is the number of horizontal  lines
   needed  to  draw the planet, i.e., the planet's height in pixels.  It is
   equal to 2*RADIUS.

        Since the planet is circular, these horizontal  lines  will  be  of
   different lengths.  The Square Gush treats all the lines as being of the
   same length and fills in the gaps with 'null' entries.  These nulls  are
   missed out when putting together the Line Gush.

        Now, for each horizontal line of the planet (corresponding to  each
   value taken by ys), the following values are stored in the Line Gush:

   (1)  The relative screen position of the start of the line.   Since  the
        lines   are  all  of  different  lengths,  the  Gush  must  provide
        information on how to arrange them to give a circular  shape.  This
        information will probably take the form of an amount to be added to
        the screen address of the last point plotted on the  previous  line
        to give the address of the first point to plot on this new line.

   (2)  A function of the length of the line;  this  value  determines  the
        number  of points to plot. (For speed of animation, it may be other
        than simply the number of points on the line.)

   (3)  For each point on the line, that point's xm and ym values (possibly
        combined into a single value).

        It takes a considerable amount of time to create a full (Line) Gush
   so  it  is  advisable  to save the completed data to disk rather than to
   create new data each time.

        There is no simple formula for the size of the Line  Gush,  but  in
   the above example it will be smaller than the Square Gush which consists
   of 2*4*RADIUS*RADIUS bytes (or words, depending on how  xm  and  ym  are
   stored).

        An alternative approach is to create a Compiled Gush, an executable
   mixture  of  data and machine code which allows for very fast animation,
   but which takes up a large amount of memory.


   3.  Third Day Song


   3.1.  Making Planets

        This part of the  program  creates  a  random,  vaguely  realistic-
   looking  planet surface which is something like fractally generated. (In
   my brief search around the library I couldn't find any  (comprehensible)
   information about generating realistic spherical planet surfaces.)

        The method used is as follows. The planet's surface (a  sphere)  is
   cut around a random equator into two hemispheres. One of the hemispheres
   is selected and the altitudes of all points on it increased or decreased
   relative  to  the other hemisphere. This is repeated many times with the
   relative changes in altitude gradually reducing in significance.

        This process acts on an array of altitude values called  the  Skin,
   which  describes  in a two dimensional mercator map the spherical planet
   surface.  When the Skin is complete it is transformed into an  array  of
   colour  values  called  the  Complexion. It is this which is used in the
   animation.


   3.2.  The Skin

        The Skin is a two-dimensional array of  size  rho  by  2*rho  (this
   being  the definition of the value rho), and each entry in it represents
   the altitude of a point on the surface of the planet.  The Skin will  be
   referred  to  by  skin[y][x] where y is the colatitude of a point on the
   surface and takes values from 0 to rho-1, and x is the  longitude  of  a
   point  on  the surface and takes values from 0 to 2*rho-1. Note that the
   skin wraps around in the x sense (so x=2*rho is equivalent to x=0),  but
   not  in  the  y sense; y=0 marks the north pole, y=rho-1 marks the south
   pole.

        The elements in this array could be floating-point numbers, but, to
   speed  up  calculations, it is probably best to use integers. All of the
   altitude values are initially set to zero,  or  some  other  appropriate
   base value.


   3.3.  The Riffs

        To generate the planet's surface, the Skin must be treated as if it
   is  spherical  when it is, in fact, flat. This is achieved with the help
   of a set of data arrays, called the Riffs, which describe the shapes  of
   the  possible  fractures  that can be made in the surface. The Riffs are
   unchanging; the random planet surfaces (for fixed rho) are  all  created
   using the same set of Riffs.

        There are rho/2 Riffs, numbered from 0 to rho/2-1, each  consisting
   of  rho/2  (again  0  to  rho/2-1)  integer  values  between 0 and rho/2
   inclusive.  Let the Riffs be represented by riff[A][t] where  A  is  the
   Riff number and t is the position within that Riff.

        The Riffs are generated in the following way  with  A  and  t  both
   taking values 0 to rho/2-1:

                        omega = ( A + 0.5 ) * pi / rho
                        sigma = ( t + 0.5 ) * pi / rho
                   gamma = atan( tan(omega) * cos(sigma) )
                riff[A][t] = rho/2 - round( gamma * rho / pi )

                   [round() means 'to the nearest integer'.
                       atan() means 'inverse tangent'.]

        It can take a little time to create all the Riffs, so it is  not  a
   bad idea to save them to disk for future use.


   3.4.  The Song

        This section  describes  the  actual  routine  that  generates  the
   planet's surface. It is fairly complicated.

        For this part of the program the Skin (with all  entries  initially
   set to zero), the Riffs, and the constant rho are required.  A couple of
   new constants also need to be defined.

   FULL_BLAST  This value determines the size of the fractures made in  the
               surface; it is the actual size of the first fracture made.

   POWER       This  constant  controls  the  style  of  formation  of  the
               surface.  A value of 1 results in mostly straight coastlines
               whilst a higher value gives more jagged shapes. Try a  value
               of 3, 4 or 5.

        The number of 'strikes' used to make the  surface  (and  hence  the
   time  taken)  is equal to FULL_BLAST to the power of POWER. Something in
   the order of several thousand strikes I find are needed  to  generate  a
   convincing surface.

        The procedure that follows is written in an vague approximation  of
   the C language.

        FULL_BLAST = 20.0
        POWER = 3.0

        int skin[rho][2*rho];  /* Using integer altitude values */
        int riff[rho/2][rho/2];  /* Riff data already generated */

        int strike;  /* Number of fractures to make */
        int displac = 0;  /* Move everything upwards; rebalance later */

        int blast;  /* Depth of next fracture */
        int rf_num;  /* Riff to use on this fracture */
        int r1;  /* Preliminary random choice of riff */
        int half;  /* Random choice of which half of surface to hit */
        int dirn;  /* Random choice of whether to blast up or down */
        int x_start;  /* Position around equator to start at */
        float r2, dis;  /* Weight riff probabilities */

        int x, y;  /* Every program needs some x and y coordinates */
        int x1, x2, x3, x4;  /* A few spare x coordinates */


        /* 'strike' is initially FULL_BLAST to the power of POWER */
        /* We repeat until strike (decremented by one each time) is zero */
        for ( strike = pow( FULL_BLAST, POWER ) ; strike > 0 ; strike-- ) {

            /* pow(A,B) means A to the power of B (A^B or A**B) */
            /* Watch out for conversions between 'int' and 'float' here */
            blast = FULL_BLAST / pow( strike, 1.0/POWER );

            /* Choose random values for next fracture */
            r1 = random(rho/2);  /* int from 0 to rho/2-1 (inclusive) */
            x_start = random(2*rho);  /* 0 to 2*rho-1 (inclusive) */
            half = random(2);  /* 0 or 1 */
            dirn = random(2);  /* 0 or 1 */
            r2 = ((float)rand())/(float)RAND_MAX;  /* between 0 and 1 */

            /* Balance choice of riffs evenly over the surface */
            /* (Avoids most fractures being along the equator) */
            dis = sin( pi * (r1+0.5) / rho );  /* All in floating-point */
            if ( r2 <= dis*dis ) rf_num = r1;
            else rf_num = rho/2 - 1 - r1;

            /* We only displace in the northern half of the planet */
            /* then make up for it at the end using 'displac' */
            if ( dirn == 0 ) blast = -blast;
            if ( half == 0 ) displac = displac - blast;

            /* This loop would be best converted to assembly language */
            /* The loop goes from x equal to 0 to x equal to rho/2-1 */
            /* Note: A%B means the remainder of A divided by B (A mod B) */
            /*       A+=B means A=A+B */
            for ( x = 0 ; x < rho/2 ; x++ ) {
                x1 = ( x_start + x ) % (2*rho);
                x2 = ( x_start + 2*rho-1 - x ) % (2*rho);
                for ( y = 0 ; y < riff[rf_num][x] ; y++ ) {
                /* If riff[rf_num][x] is zero this loop is not executed */
                    skin[y][x1] += blast;
                    skin[y][x2] += blast;
                }
                x3 = ( x_start + rho + x ) % (2*rho);
                x4 = ( x_start + rho-1 - x ) % (2*rho);
                for ( y = 0 ; y < rho-1 - riff[rf_num][x] ; y++ ) {
                /* If riff[rf_num][x] is rho-1 this loop is not executed */
                    skin[y][x3] += blast;
                    skin[y][x4] += blast;
                }
            }
        }

        /* Finally, shift all the altitudes to rebalance the zero level */
        /* (This is not strictly necessary since altitudes are relative) */
        for( y = 0 ; y < rho ; y++ ) for( x = 0 ; x < 2*rho ; x++ )
            skin[y][x]+=displac;


        This routine assumes that the Skin is made up of integer values and
   so  uses  mostly  integer variables. The advantages of using ints rather
   than floats are that they use less memory, they operate faster, and they
   allow the program to be more easily converted into assembly language.

        Bear in mind that integer variables  can  'overflow'  if  they  are
   required  to  store very large numbers. If the program begins to produce
   unexpected results, this could be the cause.


   3.5.  The Complexion

        The Complexion is an array of the same size and shape as  the  Skin
   and  is  referred  to  by  xion[y][x]. Each entry in the Complexion is a
   colour value and the array as a whole forms a  coloured-in  map  of  the
   planet's surface.

        The Complexion is derived from the Skin by assigning colours  based
   on the altitude at each point. A simple way to do this is as follows.

        Choose a set of K colours such that colour 1 represents the  lowest
   altitude and colour K represents the highest altitude.  Look at the Skin
   to find the maximum and minimum altitude values and divide up the  range
   of  altitudes into K equal-sized groups, each corresponding to a colour.
   Then, if the maximum and minimum altitudes are the values max and min,

        xion[y][x] = int{ ( skin[y][x] - min ) * K / (max-min+1) } + 1

        For a very simple planet design, colour any point with an  altitude
   of (max+min)/2 or less sea-blue, and any other point grass-green.


   4.  Notes on Implementation


   4.1.  Attachment to Hardware

        The details of writing this program depend greatly on the  hardware
   being  used: the CPU instruction set, the available memory, the graphics
   specifications and the computer's speed, among other things.

        In this section I'll sketch out how I envisage this  program  being
   implemented to make the most of the hardware available.


   4.2.  Choice of Screen Mode

        The way in which the screen memory of  the  computer  is  organised
   determines to a large extent the structure of the animation routine.

        The simplest way to write to the screen would be to use a  function
   of  the  type  plot(x_pos, y_pos, colour), but this would give painfully
   slow animation. It is usually much better to write  directly  to  screen
   memory.

        The nicest screen mode for this sort of animation would  have  each
   pixel  controlled  by  one byte of screen memory (suggesting a mode with
   256 colours) and would have consecutive pixels along a  horizontal  line
   corresponding  to consecutive bytes in memory, with the horizontal lines
   arranged in order top to bottom. With other kinds of screen  arrangement
   animation routines become a little more tricky to put together.

        Some screen modes use pixels which aren't exact squares,  and  this
   results  in  planets  that  are  elliptical  instead  of  circular.   To
   compensate for this the (xs,ys) coordinates used in the Square Gush have
   to be rescaled (the result being, in effect, a Rectangular Gush).

        The choice of screen resolution is a balancing of two factors:  for
   planets  of a fixed size, a low-resolution display gives a less detailed
   image whilst  a  high-resolution  display  means  slower  animation.   A
   resolution  of about 300 pixels by 200 pixels with a planet radius of 50
   pixels (which is to say RADIUS=50) is a reasonable place to start.


   4.3.  Gush Structure

        The amount of memory needed for  the  Gush  depends  on  a  lot  of
   factors;  anything from two to ten bytes may be needed for each point to
   be plotted, the number of which  will  be  around  PI*RADUIS*RADIUS.   A
   straightforward  Line  Gush  for a planet with RADIUS=50 and rho=128 may
   take up around 20k. A Compiled Gush for the  same  planet  may  take  up
   closer to 60k.

        The value DIST has only a very slight effect on the  appearance  of
   the planet. I usually give it a value somewhere in the thousands. It can
   alternatively be omitted altogether: change  the  Gush  calculations  so
   that  g=RADIUS  and  k=1.  This  is equivalent to making DIST infinitely
   large (and has the advantage of speeding up the Gush calculations.)

        As default values  for  phi  and  theta,  try  45  and  30  degrees
   respectively.


   4.4.  Complexion Structure

        The resolution of the planet's surface,  rho,  should  be  given  a
   value  in  compromise  of  two  factors:  if rho is not large enough the
   planet's surface will be too blocky, but  as  rho  becomes  larger  more
   memory  is  required  for  the  Complexion  and  more  time is needed to
   generate a planet surface. Also,  very  large  values  of  rho  give  no
   obvious improvement to the appearance of the planet.

        In addition, the value of rho determines the  number  of  different
   frames  seen  when  the  planet  is animated; the number of positions of
   rotation the planet can be viewed in is equal to 2*rho.

        In general, rho can be given any value, but a value of 128 may give
   certain  advantages. Since the array xion[y][x] uses values of x between
   0 and 2*rho-1 and values of y between 0 and rho-1,  rho=128  means  that
   both  x  and  y  values  are byte-sized.  Also, the offset into the xion
   array is 2*rho*y+x which becomes 256*y+x, so x and y  are  now  the  low
   order and high order bytes of a CPU-friendly 16-bit word.

        Each point in the Complexion is a colour value; if no more than 256
   colours  are  used,  then each entry in the array will take up one byte.
   The total amount of memory used will then be 2*rho*rho bytes.


   5.  Magrathea in Scattered Jumps: Plotting a Planet


   5.1.  Animation Routines

        And so, at last, we  arrive  at  the  heart  of  the  program:  the
   animation routine.

        What I'm going to do in this section is describe a  progression  of
   methods  for  displaying  planets, starting with simple routines to help
   explain the process and then moving on to more effective methods.

        Since the speed of the display routine is critical it  will  almost
   certainly  need  to  be written in assembly language. With this in mind,
   the descriptions of the routines are in a fairly low-level pseudo-code.

        Four bits of data are needed by  the  routines  when  displaying  a
   planet:

   xion  The routine must have access to some  Complexion  data;  the  name
         xion  will be used to mean, depending upon the routine, either the
         entire array xion[y][x] or the  address  in  memory  of  (i.e.,  a
         pointer to) the first byte in the array.

   gush  The routine needs Gush data of some description.  Like xion,  gush
         will  refer  to either an array or a memory address as the routine
         requires.

   scr   This value determines where on the screen the planet is displayed.
         It  could  be  either  the  x  and y coordinates of a point on the
         screen (referred to as scr.x and scr.y), or the address of a point
         in  screen  memory.   The  point  in question will be the top-left
         'corner' of the planet.

   rot   This is the state of rotation of the planet, the degree  to  which
         it  appears  rotated  about  its  axis  when  drawn. It is a value
         between 0 and 2*rho-1 with wrap-around meaning that  rot=2*rho  is
         equivalent to rot=0.


   5.2.  Using the Square Gush

        This is the simplest of the display methods;  it  uses  the  Square
   Gush  as  input, treating it as an array, and plots points to the screen
   using a plot function.  This method could be implemented in a high-level
   language  to test how well the program works before going to the trouble
   of writing a faster assembly language routine.

        In addition to the parameters which are passed to the routine  (see
   5.1),  five local variables are used here: ys and xs are the coordinates
   of a point in the Gush and on the screen;  col  is  the  colour  of  the
   current point; xm and ym are values taken from the Gush data which refer
   to a point in the Complexion.

        Remember that the Square Gush includes a number of  'null'  entries
   for which both xm_val and ym_val are taken here to be equal to -1.

        ys = 0
        L1:
        xs = 0
        L2:

        xm = gush[ys][xs].xm_val
        ym = gush[ys][xs].ym_val
        if ( xm = -1 ) and ( ym = -1 ) jump to L3
        xm = ( xm + rot ) % (2*rho)
        col = xion[ym][xm]
        plot( (scr.x + xs), (scr.y + ys), col )

        L3:
        xs += 1
        if ( xs != 2*RADIUS ) jump to L2
        ys += 1
        if ( ys != 2*RADIUS ) jump to L1


        It should be obvious roughly what this routine does. For each point
   in  an  area  of  the  screen,  xm  and  ym  values  are  taken from the
   corresponding point in the Square Gush.  If the point  is  valid  (i.e.,
   part  of  the planet) then a colour value is derived from the Complexion
   and the point is plotted.

        The value rot is added to xm, the result being trimmed so  that  it
   remains  between  0  and  2*rho-1.  In this way, the planet's surface is
   effectively made to rotate around the planet. (This  is  the  only  time
   that rot is used in the program.)

        (Incidentally, 'A != B' means 'A not equal to B' and 'A % B'  means
   'remainder of A divided by B'.)


   5.3.  Using the Line Gush

        Because the Line Gush does not include the 'null'  entries  of  the
   Square  Gush,  a  routine  using  it  can run faster. The Line Gush is a
   sequence of values in memory which are accessed in order.  The  notation
   x=[gush++]  will be used to mean 'load the next Gush value into x' - the
   variable/register gush holds the address of the next value to  be  used;
   this  value  is  loaded into x and gush is incremented by an appropriate
   amount.

        The format of the Line Gush is defined  by  the  structure  of  the
   routine.   In this example, the first value is the total number of lines
   to draw and is followed by the appropriate data for each line consisting
   of:

   (1)  An amount to add to or subtract from scr.x, the x-coordinate of the
        next  point  to  be  plotted, so that this line is drawn to fit the
        circular shape of the planet.

   (2)  The number of points to be plotted on this line.

   (3)  For each point on this line, a value for xm followed by a value for
        ym.

        The local variables used in this routine are the same as those used
   in  Section  5.2  except that xs and ys are renamed xcount and ycount to
   reflect their slightly different application.

        ycount = [gush++]
        L1:
        scr.x += [gush++]
        xcount = [gush++]
        L2:

        xm = [gush++]
        ym = [gush++]
        xm = ( xm + rot ) % (2*rho)
        col = xion[ym][xm]
        plot( scr.x, scr.y, col )
        scr.x += 1

        xcount -= 1
        if ( xcount != 0 ) jump to L2

        scr.y += 1
        ycount -= 1
        if ( ycount != 0 ) jump to L1


        Note that the value of RADIUS is no longer needed here  -  all  the
   required data is stored in the Gush.


   5.4.  Screen Memory Addresses

        The most obvious way  of  increasing  the  speed  of  the  previous
   routine  is  to  avoid  using  the  plot  function  and to instead write
   directly to the screen memory.

        Section 4.2 lists the assumptions I am making about the  layout  of
   the  screen  memory.  The  variable/register named scr will now hold the
   memory address of the next point of  the  screen  to  be  altered.   The
   notation  [scr]=x  will  be used to mean that the byte or word at screen
   memory address scr is to be loaded with the value x.

        Apart from scr, the only other difference between this routine  and
   the  last  one  is in the Line Gush: the value which before was added to
   scr.x is now added to scr. This new value is the difference between  the
   address  of  the  last  point on the previous line of the planet and the
   address of the first point on the new line of the planet.

        ycount = [gush++]
        L1:
        scr += [gush++]
        xcount = [gush++]
        L2:

        xm = [gush++]
        ym = [gush++]
        xm = ( xm + rot ) % (2*rho)
        col = xion[ym][xm]
        [scr] = col
        scr += 1

        xcount -= 1
        if ( xcount != 0 ) jump to L2
        ycount -= 1
        if ( ycount != 0 ) jump to L1


   5.5.  Games with Xion

        All of the routines so far have treated xion as an array; in a low-
   level language the address in memory of a value is needed instead of its
   position in the array, so we replace the line

                              col = xion[ym][xm]


   with the line

                        col = [ xion + 2*rho*ym + xm ]


        (This assumes that one byte is used for each of the values  in  the
   Complexion.)


   5.5.1.

        The use of xm and ym to  point  to  values  in  the  Complexion  is
   somewhat  inefficient.  One fairly simple way of speeding the process up
   would be to store the value of 2*rho*ym or  even  xion+2*rho*ym  in  the
   Gush instead of just ym.  The modulus ('%') calculation can also be made
   faster by choosing a value for rho that is a  power  of  two;  then  the
   modulus  operation can be replaced by a logical AND operation which most
   microprocessors can perform much  faster.  If  this  is  done  then  the
   central loop of the routine will look something like:

        xm = [gush++]
        ym2 = [gush++]
        xm = ( xm + rot ) AND (2*rho-1)
        col = [ ym2 + xm ]
        [scr] = col
        scr += 1



   5.5.2.

        An alternative approach can be used on CPUs which allow  their  16-
   or  32-bit registers to be split into component 8-bit registers. Suppose
   one complete register is called yyxx and that its lowest 8 bits  can  be
   operated  upon  separately  as the register xx.  Since xx is only 8 bits
   long, the equivalent of an AND 255 operation is automatically  performed
   on it whenever it is used.  Thus if rho is set equal to 128 and the Gush
   holds the combined value 2*rho*ym+xm instead of both xm and ym, then the
   core of the routine is simply:

        yyxx = [gush++]
        xx += rot
        col = [ xion + yyxx ]
        [scr] = col
        scr += 1


   5.5.3.

        There is yet a  third  way  to  simplify  the  routine,  though  it
   requires  twice  as  much  memory  for the Complexion data.  An expanded
   Complexion is created in which each line of the map appears twice:  Line
   1 is followed in memory by another copy of Line 1 before getting to Line
   2, such that [xion] is the same as [xion+2*rho], [xion+1] is the same as
   [xion+2*rho+1],  and  so  on up until [xion+4*rho] which is the start of
   the second line.

        An expanded Complexion removes  the  need  for  the  modulus  ('%')
   calculation when rot is added on to xm.

        Instead of the two values xm and ym being used for  each  point  in
   the  Line  Gush, the single combined value xion+4*rho*ym+xm (or possibly
   just 4*rho*ym+xm) is used. The single  variable/register  ymxm  replaces
   both xm and ym and the core of the routine now becomes:

        ymxm = [gush++]
        col = [ ymxm + rot ]    ( or [ xion + ymxm + rot ]  )
        [scr] = col
        scr += 1


        (Note that 4*rho now replaces 2*rho as the width of the  Complexion
   array.)


   5.6.  Scattered Jumps

        There is another aspect of the display routine which can  still  be
   made  more  efficient:  the  looping  mechanism.  For every point of the
   planet that is plotted, additional instructions must be executed to loop
   back to plot the next point. These time wasting loop instructions can be
   missed out by repeating the plotting code in memory once for each of the
   points to be plotted.

        The problem with this is that the number of points to be plotted on
   each  line varies. The solution is to miss out some of the plot routines
   by jumping past them - this needs an 'indirect'  jump  instruction,  the
   address to jump to being stored in a register or in memory.

        The following code shows how this method could be implemented  into
   a  display  routine.  It  is  based  around  an  expanded Complexion, as
   described in the previous sub-section. The value in the Gush which  used
   to  be  the  number  of points to plot on this line is now an address to
   jump to in order to plot that number of points.

        ycount = [gush++]

        L1:
        scr += [gush++]
        jump to [gush++]

        [...]

        * ymxm = [gush++]
        * col = [ ymxm + rot ]
        * [scr] = col
        * scr += 1

        ycount -= 1
        if ( ycount != 0 ) jump to L1


        The code marked by the asterisks plots  one  point.  Assuming  that
   MAX_R  is  the  largest  value  of  RADIUS ever likely to be used in the
   program, then the asterisked code needs to be  repeated  2*MAX_R  times.
   The complete routine should take up no more than a few kilobytes.


   5.7.  The Compiled Gush

        This is a way by which the animation can be dramatically  increased
   in  speed (and thanks to Jamie Lokier for pointing this out): instead of
   the Gush containing numerical data to be read by an  animation  routine,
   it can itself be an executable routine with the data hard-wired into it.

        The Square Gush is translated directly into machine  code,  with  a
   few bytes of code for each of the points to be plotted. A section of the
   Compiled Gush (working with an  expanded  Complexion)  would  then  look
   something like:

        col = [ m1 + rot ]
        [scr] = col
        scr += 1

        col = [ m2 + rot ]
        [scr] = col
        scr += 1

        col = [ m3 + rot ]
        [scr] = col
        scr += 1

        [...]


   where m1, m2 and m3 are immediate (constant) values.

        The Compiled Gush also contains machine instructions  to  move  scr
   from  the  end  of  one line to the start of the next, and, of course, a
   'return'  instruction  at  the  end.  (Furthermore,  quite  a   bit   of
   optimization  can  often  be  done  on the code as it is generated.) The
   planet is then animated simply by choosing values for scr  and  rot  and
   calling the Compiled Gush code.

        But there is one big drawback to this: a Compiled Gush takes  up  a
   lot  of memory, several times more than a normal Gush. Fast animation is
   what this method delivers, but it does so at the cost of a  large  chunk
   of memory.


   6.  Finishing Off


   6.1.  Putting Together the Pieces

        Way back at the beginning of all this  I  described  the  animation
   program  as  consisting of three basic parts. The code for creating Gush
   data is detailed in Section 2, the code for creating Complexion data  is
   detailed  in  Section 3, and Section 5 constructs a routine that draws a
   planet using these two data arrays.

        In Section 1.4, constant  values  (rho,  RADIUS,  DIST,  etc.)  are
   introduced.  Whatever  values  these constants are given it is important
   that they remain the same in all parts of the program.

        With all segments of the program complete an animation can  finally
   be  put  together.  The simplest animation, that of a planet spinning in
   the centre of the screen, is accomplished in the following way.

        Create a Gush and a Complexion, storing  both  in  files  on  disk.
   Write  a  simple  piece  of code that first loads in both data files and
   then, with an appropriate value for scr (the point on the  screen  where
   the  planet  is  to  appear),  repeatedly calls the display routine. The
   additional parameter rot is increased (or decreased) by a  small,  fixed
   amount  on  each call with a modulus operation keeping it in the range 0
   to 2*rho-1.

        There is  not  much  more  to  say  about  the  animation;  further
   elaborations  are  left  to  the  programmer's  imagination.  As  a  few
   suggestions:

   *   Store several different Gushes on disk and  select  one  to  use  at
       random  when  the animation is run. The Gushes are all created using
       different values for phi and theta (and possibly RADIUS).

   *   Use different sets of colours for different planets; choose the  set
       to be used at random when converting a Skin to a Complexion.

   *   Make more interesting planet surfaces; add random cloud and mountain
       generation,  or  edit a Complexion using a standard bitmap editor to
       give it a smiling face.

   *   Draw a star field behind the planet (to  make  the  fact  that  it's
       supposed to be a planet more obvious!). Animate the star field.

   *   Shade the planet (as described in the next sub-section).

   *   Before displaying the next frame of the planet, delete the old frame
       and  change  the  value  of  scr  so  that  the  planet appears in a
       different place. Make the planet move. Make it bounce!


   6.2.  Illumination

        In Section 2.3, when calculating  values  for  the  Gush  a  vector
   P=(xp,yp,zp)  is  generated  for  each point (xs,ys). This vector can be
   used to give a basic illumination effect, that of light shining onto the
   planet from a fixed direction.

        For this effect to work, some method for selectively  altering  the
   brightness  of  the  pixels  making  up  the  planet's  image is needed.
   Ideally, each pixel should have a collection  of  bits  controlling  its
   brightness which are independent of the bits controlling its colour. The
   planet can then be shaded by adjusting just these brightness bits.

        Define a vector I=(xi,yi,zi) as  the  direction  from  which  light
   falls  onto the planet. Then, for each (xs,ys), calculate a value lambda
   as follows:

                      q = sqrt( xi*xi + yi*yi + zi*zi )

                     lambda =  ( I . P ) / ( |I| * |P| )
                   = ( xi*xp + yi*yp + zi*zp ) / ( q * g )

       (The calculation of the constant g is described in Section 2.3)


        lambda is a measure of the illumination of the  point  (xs,ys)  and
   takes  values  from  -1 (minimum brightness) to +1 (maximum brightness).
   (Strictly, if lambda's value is below zero, then no light at all reaches
   (xs,ys) - it is on the dark side of the planet.)

        Transform each lambda into a number kappa, the value to be  put  in
   the  brightness  bits  of the pixel at (xs,ys).  To avoid the shading of
   the planet being too regular, add  a  slight  random  influence  to  the
   transformation  such  as changing the value of lambda by a small, random
   amount. This blurs the border between 'light' and 'dark' regions of  the
   planet.

        Illuminating the planet is now a matter of keeping  the  brightness
   of  each  pixel  fixed  (at  the value kappa) whilst allowing the planet
   display routine to fill in the pixel's colour.

        One way of doing this would be to  set  each  pixel  to  brightness
   kappa  and  colour  zero,  and  then  use  a logical OR operation in the
   display routine to overlay colours onto the pixels. Before  drawing  the
   next  frame,  the  colour  of each pixel would need to be reset to zero,
   either by rewriting all brightness and colour  values  together,  or  by
   resetting just the colour bits using multiple logical AND operations.


   6.3.  Whys and Wherefores

        That's  about  it  really.  The  one  thing  left  to  add  is   an
   explanation.  I've  written  some seven thousand words describing how to
   put together a pretty but pointless animation. Why did I bother?

        Well, as I said earlier, nearly all of the mildly clever  animation
   techniques  used  here  I've had to work out for myself. They aren't the
   sort of thing that you commonly find  in  text  books.  In  a  way  this
   document  is a small attempt to change that - it's sort of a "Beginner's
   Guide to Real-time Animation (Volume 1: Things That Bounce)".

        Also, there's a limit to the number of things that I can  think  of
   to do to spinning planets. I want to see what ideas other people come up
   with using this as a starting point.

        So, having read this document, if  you're  interested  in  bringing
   this animation to life, go to it, and good luck to you.


   6.4.  Acknowledgements

        Thanks must go to Matt Spinner  for  helping  create  the  original
   planet demo and to everyone else who was around at the time (Dave, Nick,
   Simon, Phill, JT, etc).

        And thanks also to Jay Dresser and Jamie  Lokier  from  Usenet  for
   comments on the original posting.



