


AVRIL Programmers Guide
Version 0.9b
April 27, 1994

Bernie Roehl



Part I -- An Introduction

This document is divided into two parts: an introduction to AVRIL and a technical reference.

What is AVRIL?

      AVRIL is A Virtual Reality Interface Library, a software package that allows you to
create and interact with virtual worlds.  It consists of a fast polygon-based rendering engine
and a set of support routines that make virtual world creation a reasonably straightforward
process.

      AVRIL is designed to be fast, portable and easy to use.  It's written entirely in ANSI
C, except for the very low-level routines that actually write to the display.  The 386 version
also has a few short assembly-language routines to handle some of the fixed point math.  The
API (Applications Program Interface) is simple and well-documented, which should make
applications easy to develop.

      Most important, AVRIL is free for non-commercial use.  The problem with most
current VR libraries is that they're very, very expensive; most of them cost more than the
computers they run on!  AVRIL is intended to give everyone with an interest in VR an
opportunity to develop some simple applications without having to invest huge sums of
money.

What does "free for non-commercial use" mean?

      It means that if you're only writing programs for your own use, or to give away free
to others, you can use AVRIL without paying anything.

Who developed AVRIL?

      AVRIL was developed by Bernie Roehl, between November of 1993 and April of
1994.  It's designed to be somewhat backward-compatible with an earlier rendering engine
called REND386 that was developed by Bernie Roehl and Dave Stampe.

So what makes AVRIL different from REND386?

      From the beginning, we knew that REND386 would never run on anything but 386-
based computers; that's why we called it "REND386" in the first place.  REND386 was fast,
but it achieved its speed at the price of portability; large parts of the code were hand-
translated to 386 assembly language.  This obviously reduced the portability of the software,
as well as making it more difficult to maintain.

      AVRIL, by contrast, is written entirely in C.  It's fast because the algorithms are well-
chosen and carefully written.  While it's not quite as fast overall as REND386, there are
actually some situations where it's faster; once it's been optimized a bit, the speed should be
comparable.  Since it's written in C, AVRIL is also much easier to maintain than REND386
was.

Using AVRIL

      AVRIL is very easy to use.  Rather than spend a lot of time discussing the details of
how it works, let's start by creating a simple AVRIL program:

/* EXAMPLE1 -- a cube */

/* Written by Bernie Roehl, April 1994 */

#include <stdio.h>

#include "avril.h"

void main()
      {
      vrl_Object *cube;
      vrl_Light *light;
      vrl_Camera *camera;

      vrl_SystemStartup();

      cube = vrl_NewObject(vrl_PrimitiveBox(100, 100, 100, NULL));
      vrl_ObjectRotY(cube, float2angle(45));

      light = vrl_NewLight();
      vrl_LightRotY(light, float2angle(45));
      vrl_LightRotX(light, float2angle(45));

      camera = vrl_NewCamera();
      vrl_CameraRotX(camera, float2angle(45));
      vrl_CameraMove(camera, 0, 500, -500);

      vrl_SystemRun();
      }


      Notice that the only file you have to #include is "avril.h"; that file contains prototypes
for all the AVRIL functions along with a number of useful macros.  You should always
#include <stdio.h> before avril.h, since avril.h defines routines that take FILE * parameters.

      The program shown above simply creates a cube, a light source and a virtual camera. 
All the AVRIL routines and data types have names beginning with "vrl_"; this ensures that
they won't conflict with any routines you write.  The vrl_SystemStartup() routine does all the
system initialization; the source code for all the vrl_System functions is found in system.c, in
case you're curious as to how they work.  We'll be looking at them in detail later.

      One the initialization is done, the program creates the cube by calling a routine that
generates a primitive box shape; the sides are all 100 units in length.  Once created, the cube
is rotated 45 degrees around the vertical (Y) axis.  The float2angle() routine converts a
floating-point number into an internal format used for storing angles.

      A directional light source is then created, and rotated 45 degrees in each of X and Y. 
Next, a virtual camera is created, rotated and moved into position.  Finally, vrl_SystemRun()
is called; vrl_SystemRun() sits in a loop, checking for keyboard or mouse activity and doing
the rendering as needed.

      To compile and link the program using Borland C++, you would give the following
command:

      bcc -ml example1.c input.c avril.lib disprvd.lib

This compiles example1.c and input.c and links them with the AVRIL library and the display
routines in disprvd.lib; that display library uses the old REND386 "vd256.rvd" driver, which
must be in the current directory when the compiled program is run.  There will soon be other
display libraries that you can use with AVRIL simply by re-linking your application.

The routines in input.c are discussed in a later section.

Sharing Shapes

      Our first example was pretty straightforward; let's try something more complex.

/* EXAMPLE2 -- several asteroids, sharing the same geometry */

/* Written by Bernie Roehl, April 1994 */

#include <stdio.h>
#include <stdlib.h>

#include "avril.h"

void main()
      {
      FILE *infile;
      vrl_Light *light;
      vrl_Camera *camera;
      vrl_Shape *asteroidshape = NULL;
      int i;

      vrl_SystemStartup();
      
      vrl_WorldSetHorizon(0);    /* turn off horizon */
      vrl_WorldSetSkyColor(0);   /* black sky */
      vrl_WorldSetAmbient(float2factor(0.1));  /* low ambient light */

      infile = fopen("asteroid.plg", "r");
      if (infile)
           {
           asteroidshape = vrl_ReadPLG(infile);
           fclose(infile);
           }

      light = vrl_NewLight();
      vrl_LightRotY(light, float2angle(45));
      vrl_LightRotX(light, float2angle(45));
      vrl_LightSetIntensity(light, float2factor(0.9));

      camera = vrl_NewCamera();
      vrl_CameraMove(camera, 0, 100, -50);

      for (i = 0; i < 10; ++i)
           {
           vrl_Object *obj = vrl_NewObject(asteroidshape);
           vrl_ObjectMove(obj, rand() % 1000, rand() % 1000, rand() % 1000);
           }

      vrl_SystemRun();
      }

      This program illustrates a useful memory-saving feature of AVRIL.  The shape of an
object (i.e., its geometric description) is separate from the information about its location and
orientation.  Any number of objects can share the same geometric description, saving
substantial amounts of memory.  A geometric description is called a vrl_Shape, and consists
of a set of vertices, facets and other information.

      The program shown above begins by turning off the horizon (it's on by default) and
setting the sky color to 0 (black).  The sky color is used as the screen clear color if there's no
horizon.  Next, the file "asteroid.plg" is loaded; AVRIL supports the PLG file format.  The
vrl_ReadPLG() function returns a pointer to a vrl_Shape (the same data type that was
returned by the vrl_PrimitiveBox() function in our first example).

      A light source and camera are again set up, and ten virtual objects are created using
the shape that was loaded by vrl_ReadPLG().  Notice that the file only had to be read once,
and that the vertices and facets making up an asteroid are only stored once in memory.  Each
of the asteroids is moved to a random location in an imaginary box 1000 units on a side.

      As you move around, you'll notice that the appearance of an asteroid changes
depending on how far away you are from it; if you get close enough, it's a rough, craggy
surface.  The "asteroid.plg" file stores multiple representations of the object, and AVRIL
automatically selects one of those representations based on distance.  This can speed up the
rendering process by allowing fewer vertices and facets to be used when an object is far
away.

Making Maps

      AVRIL not only separates geometry from location/orientation information, it also
stores surface descriptions separately.  Each object has a "surface map" associated with it,
which stores pointers to actual vrl_Surface descriptors.  Each surface has a type, a hue and a
brightness; in our examples, the surface type is always SURFACE_FLAT (meaning that flat
shading is used).  The hue is what most people think of as the "color", and the brightness is
how much light the surface reflects back to the eye.  The higher the brightness value and the
more directly that light is striking the surface, the more intense the color.

      You can assign surface maps to objects, and change them whenever you like.  Our
third example program uses two different surface maps, called map1 and map2:

/* EXAMPLE3 -- surface maps */

/* Written by Bernie Roehl, April 1994 */

#include <stdio.h>
#include <stdlib.h>

#include "avril.h"

void main()
      {
      FILE *infile;
      vrl_Light *light;
      vrl_Camera *camera;
      vrl_Shape *colorthing = NULL;
      vrl_Surface **map1, **map2;
      int i;

      vrl_SystemStartup();
      
      map1 = vrl_NewSurfacemap(6);
      map2 = vrl_NewSurfacemap(6);
      for (i = 0; i < 6; ++i)
           {
           map1[i] = vrl_NewSurface(i + 1);
           map2[i] = vrl_NewSurface(7 + i);
           }

      infile = fopen("colorful.plg", "r");
      if (infile)
           {
           colorthing = vrl_ReadPLG(infile);
           fclose(infile);
           }

      light = vrl_NewLight();
      vrl_LightRotY(light, float2angle(45));
      vrl_LightRotX(light, float2angle(45));

      camera = vrl_NewCamera();
      vrl_CameraMove(camera, 0, 100, -50);

      for (i = 0; i < 10; ++i)
           {
           vrl_Object *obj = vrl_NewObject(colorthing);
           if (i & 1)
                 vrl_ObjectSetSurfacemap(obj, map1);
           else
                 vrl_ObjectSetSurfacemap(obj, map2);
           vrl_ObjectMove(obj, rand() % 1000, rand() % 1000, rand() % 1000);
           }

      vrl_SystemRun();
      }

The program creates the two maps using the vrl_NewSurfacemap() function; the parameter is
the number of entries the map should have.  Six entries are then created in each map by
calling vrl_NewSurface(); the parameter to that function is the hue.  The first map will use
hues 1 through 6 inclusive, the second will use hues 7 through 12.  A shape is then loaded
from the file "colorful.plg"; that file uses indexed surface descriptors (0x8000, 0x8001 etc)
that refer to entries in the surface map.

      The light source and camera are again set up, and ten objects are created.  Half of
them (the odd-numbered ones) are assigned map1 and the others are assigned map2.  The
objects are again positioned randomly.

      Notice how half the cubes are a different color from the other half; each set of surface
descriptions is only stored once, and each surface map is shared by five of the ten cubes.  All
the cubes share the same vrl_Shape information, which is only stored once.

A Real Taskmaster

      AVRIL has a pseudo-tasking facility, which allows you to add routines to a list that
gets processed continuously while the system runs.  Each task has a function and possibly
some data, as well as an indication of how often it should be run.

      Our fourth example is more complex that the first three; it creates several primitive
shapes, sets up surface maps, and creates tasks to make the objects move by themselves. 
We'll have spinning cubes, bouncing spheres and pulsating cylinders.  Let's look at the
mainline first:

static Angle spinrate;
static Scalar pulse_period;
static Scalar bounce_period;
static Scalar maxheight;


void main()
      {
      vrl_Light *light;
      vrl_Camera *camera;
      vrl_Shape *cube, *sphere, *cylinder;
      vrl_Surface *cubesurf, *pulsesurf;
      int i;

      vrl_SystemStartup();
      
      cube = vrl_PrimitiveBox(100, 100, 100, NULL);
      sphere = vrl_PrimitiveSphere(100, 6, NULL);
      cylinder = vrl_PrimitiveCylinder(100, 50, 100, 8, NULL);

      cubesurf = vrl_NewSurface(5);
      pulsesurf = vrl_NewSurface(14);

      spinrate = float2angle(72.0 / vrl_TimerGetTickRate());  /* deg per tick */
      bounce_period = 4 * vrl_TimerGetTickRate();  /* four-second period */
      maxheight = float2scalar(400);    /* maximum height in units */
      pulse_period =  2 * vrl_TimerGetTickRate();  /* two-second period */

      light = vrl_NewLight();
      vrl_LightRotY(light, float2angle(45));
      vrl_LightRotX(light, float2angle(45));

      camera = vrl_NewCamera();
      vrl_CameraRotY(camera, float2angle(5));
      vrl_CameraMove(camera, 0, 200, -4400);

      for (i = 0; i < 10; ++i)
           {
           vrl_Object *obj = vrl_NewObject(NULL);
           vrl_ObjectMove(obj, rand() % 1000, rand() % 1000, rand() % 1000);
           switch (i & 3)
                 {
                 case 0:
                      vrl_ObjectSetShape(obj, cube);
                      break;
                 case 1:
                      vrl_ObjectSetShape(obj, cube);
                      vrl_ObjectSetSurfacemap(obj, &cubesurf);
                      vrl_TaskCreate(spin, obj, 10);
                      break;
                 case 2:
                      vrl_ObjectSetShape(obj, sphere);
                      vrl_TaskCreate(bounce, obj, 10);
                      break;
                 case 3:
                      vrl_ObjectSetShape(obj, cylinder);
                      vrl_ObjectSetSurfacemap(obj, &pulsesurf);
                      break;
                 }
           }
      vrl_TaskCreate(pulsate, pulsesurf, 10);

      vrl_SystemRun();
      }

Three primitive shapes are created -- a box (100 units on a side), a sphere (100 units in
radius, with 6 facets around) and a tapered cylinder (base radius 100, top radius 50, height
100 units with 8 sides).  Two surfaces are created; one called cubesurf using hue 5 and one
called pulsesurf using hue 14.

      Some global variables are then set; spinrate is the rate that the cubes should spin, in
degrees per "tick".  A tick is a small unit of time; in the DOS versions of AVRIL, the timer
runs at 1024 ticks per second.

      We do the float2angle() conversion here rather than in the spin() task itself; by storing
the Angle value, we avoid having to do the conversion each time through the simulation loop. 
Also notice that we divide by the rate at which the system timer runs, in ticks per second; the
rotation rate is 72 degrees per second, so we divide by ticks per second to get the rotation
rate in degrees per tick.

      Using the value of vrl_TimerGetTickRate() ensures that the code we write will be
portable across platforms.  If you're interested in squeezing every last bit out of your
system's performance, you can take advantage of the fact that the rate is 1024 ticks/second on
a DOS machine and thereby avoid the division (just right-shift ten bits instead).  It's a
portability-vs-performance tradeoff; in this case we opt for portability, since this particular
division gets done only once at startup time.

      The bounce_period is 4 seconds, converted to ticks; this is the time it takes a bouncing
ball to go through one complete up-down cycle.  The maximum height a ball will rise to is
maxheight, arbitrarily set to be 400 units.  Note the conversion from floating-point to the
internal "Scalar" format.  The pulse_period is set to two seconds.

      Again, a light and camera are set up so we can view the scene, and ten objects are
created and randomly positioned.  Some of them are simple cubes (using the default color
assigned by vrl_PrimitiveBox()).  Some of them are spinning cubes, with a single-entry
surfacemap; rather than create an actual surfacemap, we pass the address of the surface
pointer (&cubesurf).  In effect, it's a one-entry array.

      A task is created to make the cube spin.  Each task has a function, some data, and a
"period" which indicates how often the task should be run.  In this case, the function is spin(),
the data is a pointer to the object to be spun, and the period is 10 ticks.  The period doesn't
affect the speed at which the cube will spin; it only determines how often the spin() function
should be called.  The smaller the number, the more often the routine will run and the
"smoother" the motion will be; of course, running the tasks more often takes CPU cycles
away from rendering.

      The bouncing balls are handled the same way as the spinning cubes.  The cylinders
don't have a task associated with them; instead a separate task is set up that will cause the
pulsing to happen.  The data for that task is not an object pointer, but rather a pointer to a
surface descriptor.

      The tasks themselves are quite straightforward.  The simplest is the spin() task, which
is only two lines long:

static void spin(void)
      {
      vrl_ObjectRotY(vrl_TaskGetData(), vrl_TaskGetElapsed() * spinrate);
      vrl_SystemRequestRefresh();
      }

This task gets a pointer to its data using vrl_TaskGetData(); this is a pointer to the object
associated with this task.  The task also gets the elapsed time (in ticks) since it last ran,
multiplies that value by spinrate, and rotates the object by that amount around the vertical (Y)
axis.  The spin() function then calls vrl_SystemRequestRefresh(), which tells the system that
the screen should be refreshed (since an object has moved).

      The bounce() task is only slightly more complex; it uses the sine function to determine
the height at which the object should be positioned:

static void bounce(void)
      {
      vrl_Object *obj = vrl_TaskGetData();
      unsigned long off;
      Scalar height;
      off = (360 * (vrl_TaskGetTimeNow() % bounce_period)) / bounce_period;
      height = vrl_FactorMultiply(maxheight, vrl_Sine(float2angle(off)));
      vrl_ObjectMove(obj, vrl_ObjectGetX(obj), height, vrl_ObjectGetZ(obj));
      vrl_SystemRequestRefresh();
      }

The current time is obtained from vrl_TaskGetTimeNow(), and the % operator is used to find
the modulus of the current time relative to the bounce period.  That value, divided by the
bounce period, is the fraction of the bounce period that has elapsed.  We multiply that by 360
(the number of degrees in a circle) to get an offset value; we take the sine of that value
(using the fast vrl_Sine() routine) and multiply by the maximum height value.  The
vrl_FactorMultiply() routine takes a fractional number (of the type returned by vrl_Sine()) and
multiplies it by a Scalar value to get a (smaller) Scalar value.

      We use vrl_ObjectMove() to actually position the object.  Notice the use of
vrl_ObjectGetX() and vrl_ObjectGetZ() to find the current X and Z values of the object's
location; we don't want to alter those values, only the height.  A call to the function
vrl_SystemRequestRefresh() ensures that the screen will be redrawn with the object at its new
height.

      The pulsate() task is similar to the bounce() task, but instead of computing a height it
computes a brightness and sets it as the new brightness value of pulsesurf.  Brightness values
are in the range of 0 to 255.

A Tiny Program

      AVRIL provides a number of useful utility routines that reduce the amount of actual
programming you have to do in order to create a virtual world.  A minimal AVRIL program
looks like this:

/* Simple demo of AVRIL */

/* Written by Bernie Roehl, April 1994 */

#include <stdio.h>
#include "avril.h"

void main(int argc, char *argv[])
      {
      vrl_SystemStartup();
      vrl_SystemCommandLine(argc, argv);
      vrl_SystemRun();
      }

Yes, that's the entire program.  The vrl_SystemCommandLine() routine examines the
command line parameters, loads any PLG, WLD or FIG files that are specified, and makes
sure that a camera is set up.  This AVRIL Viewer program ("av") is what you typically use to
explore simple virtual worlds.

Of Mice and Menus

      By now, you've probably noticed that something is missing; how have our programs
been able to respond to our keystrokes and mouse presses?  Well, AVRIL does some of this
for you automatically.  When you call vrl_SystemRun(), you're essentially turning control of
the application over to the system.  From time to time, the system will make calls back to
your application to give you control if you need it.  (If you don't like this approach, you're
not stuck with it; the source for the vrl_System functions is provided, so you can do things
however you like).

      There are currently four places that the system calls your application.  Just after it
clears the screen (or draws the horizon, as the case may be) but before it does the actual
rendering of the scene, it calls vrl_ApplicationDrawUnder().  You can use that routine to
"underlay" information on the screen that appears behind any objects that are drawn.  If you
want to use your own background, just turn off screen clearing using
vrl_WorldSetScreenClear(0) and do your background drawing in vrl_ApplicationDrawUnder().

      After the system has rendered the entire scene, it calls vrl_ApplicationDrawOver(); this
allows you to "overlay" information on the screen.  The vrl_ApplicationDrawOver() routine is
where you would put any "heads-up display" type information, such as frame rate or
orientation information.

      When the user presses a key, the system calls vrl_ApplicationKey(), passing the value
of the key that was pressed.  When the user mouse-clicks, the system calls
vrl_ApplicationMouseUp(), passing the location of the mouse on the screen at the time the
buttons were released, and information about which buttons had been down.

      All of these routines have default versions in the AVRIL library, so you don't have to
write all of them.  The default versions of the functions vrl_ApplicationDrawUnder(),
vrl_ApplicationDrawOver(), and vrl_ApplicationMouseUp() are all empty (i.e., they don't do
anything).  The default vrl_ApplicationKey() routine just checks to see if the key was ESC,
and if it was, calls vrl_SystemStopRunning() to shut things down.

      There's also a simple menu system built into this version of AVRIL; it will be
described later.

Lots of Input

      The file input.c contains simple versions of vrl_ApplicationDrawOver(),
vrl_ApplicationMouseUp() and vrl_ApplicationKey() that are shared by all our example
programs.  The vrl_ApplicationMouseUp() routine looks like this:

void vrl_ApplicationMouseUp(int x, int y, unsigned int buttons)
      {
      if (buttons & 0x03)
           {
           vrl_RenderMonitorInit(x, y);
           vrl_SystemRender(NULL);  /* redraw screen */
           if (vrl_RenderMonitorRead(&active_object, NULL, NULL))
                 if (active_object)
                      {
                      vrl_ObjectToggleHighlight(active_object);
                      vrl_SystemRequestRefresh();
                      }
           }
      }

This routine uses the "Monitor" facility of AVRIL to allow the user to select objects.  The
mouse location is passed to vrl_RenderMonitor(); this tells the system to keep an eye on that
point on the screen.  The screen is then re-drawn using vrl_SystemRender(), and the monitor
is read using vrl_RenderMonitorRead().  If that function returns a non-zero value, then the
mouse cursor was on top of an object; since we passed &active_object to the
vrl_RenderMonitorRead() function, active_object now points to the object that the mouse
cursor was on top of.  We toggle the highlight bit of that object (highlighting or un-
highlighting it) and tell the system the screen needs to be refreshed (since the highlighting of
an object has changed).

      The vrl_ApplicationKey() routine is very simple; the only complicated part is that it
handles auto-repeat of keystrokes:

void vrl_ApplicationKey(unsigned int c)
      {
      static int lastkey = 0;
      if (c == INS)
           {
           int i;
           for (i = 0; i < 100; ++i)
                 {
                 process_key(lastkey);
                 vrl_SystemRender(vrl_ObjectUpdate(vrl_WorldGetObjectTree()));
                 }
           }
      else
           process_key(lastkey = c);
      }


If the key is INS (defined in avrilkey.h), the last key is re-processed 100 times; all other keys
are processed once, and the lastkey variable is updated.  Notice the call to
vrl_SystemRender(); it looks pretty complicated, but after you read some of the later sections
it will make more sense.  We need to update the world and re-render the scene after every
keystroke, so the user will see the ongoing changes.

      The process_key() function is fairly long, and will probably change from version to
version of AVRIL.  Most of it should be pretty easy to understand, so you may want to take
a few minutes to look through the source code in input.c (where you'll also find the source
for the vrl_ApplicationMouseUp() and vrl_ApplicationDrawOver() routines).

      The vrl_ApplicationDrawOver() routine provides the position, frame rate, compass and
"heads-up display" support for the AVRIL demos.  It looks like this:

static int showlocation = 0, showhud = 0, showcompass = 0, showframerate = 0;

static int turnmove = 0;       /* 0 for translation, 1 for rotation */

void vrl_ApplicationDrawOver(vrl_Status *stat)
      {
      vrl_Camera *cam = vrl_WorldGetCamera();
      char buff[100];
      if (showlocation)
           {
           sprintf(buff, "Position: %ld,%ld", vrl_CameraGetX(cam), vrl_CameraGetZ(cam));
           vrl_DropText(10, 10, 15, buff);
           }
      if (showcompass)
           vrl_DrawCompass(cam, 250, 40, 35);
      if (showhud)
           {
           sprintf(buff, "%c%c%c",
                 stat->memory ?  'M' : ' ',
                 stat->objects ? 'O' : ' ',
                 stat->facets ?  'F' : ' ');
           vrl_DropText(10, 20, 15, buff);
           sprintf(buff, "Stepsize = %f",
                 scalar2float(vrl_WorldGetMoveStep()));
           vrl_DropText(10, 30, 15, buff);
           sprintf(buff,"Anglesize = %f",
                 angle2float(vrl_WorldGetTurnStep()));
           vrl_DropText(10, 40, 15, buff);
           sprintf(buff, "Zoom = %f, hither = %f",
                 vrl_CameraGetZoom(cam), scalar2float(vrl_CameraGetHither(cam)));
           vrl_DropText(10, 50, 15, buff);
           sprintf(buff, "%ld", coreleft());
           vrl_DropText(10, 60, 15, buff);
           }
      if (showframerate)
           {
           unsigned long rtime = vrl_SystemGetRenderTime();
           if (rtime)  /* make sure we don't divide by zero */
                 {
                 sprintf(buff, "Frames/sec: %ld", vrl_TimerGetTickRate() / rtime);
                 vrl_DropText(5, 170, 15, buff);
                 }
           }
      }

The "show" variables control which parts of the overlaid display are active and which aren't;
these variables are all toggled by code in process_key().  The call to vrl_WorldGetCamera()
returns a pointer to the currently-active virtual camera.  The buffer buff[] will be used to
construct strings that we want to display on the screen.

      If the showlocation variable is set, a text string containing the camera's current X and
Z values is constructed and displayed at location (10, 10) on the display.  The first value is
the horizontal distance in pixels from the left of the screen, and the second value is the
vertical distance in pixels from the top of the screen.  The color used is 15, which is white. 
The vrl_DropText() function automatically produces a "drop shadow" behind the text,
ensuring it's visible even if it's overlaid on top of a white background.

      If the showcompass variable is set, the vrl_DrawCompass() routine is called.  The
compass is displayed at location (250, 40) on the screen, and each "arm" of the compass is 35
pixels long.

      If the showhud variable is set, a variety of information is displayed.  Most of it should
be pretty self-explanatory, but the first item is worth a closer look.  When the renderer draws
a scene, it may run out of internal memory, or it may find there are too many objects or
facets for it to process.  If this happens, it sets bits in a special structure; a pointer to this
structure is passed to vrl_ApplicationDrawOver(), so that it can alert the user to the problem. 
In this case, an 'M' is displayed if the renderer ran out of memory, an 'O' is displayed if
there were too many objects, and an 'F' is displayed if there were too many facets.

      If the showframerate variable is set, the routine vrl_SystemGetRenderTime() is called
to find out how long it took (in ticks) for the screen to be completely re-drawn; if there's
something wrong with the timer routines, or if the timer just runs too slowly on the machine
we're on, it's possible that a value of zero will be returned.  We check for this before
dividing, to avoid a divide-by-zero error.

Into the System

      We've talked a lot so far about the vrl_System routines; now let's take a closer look at
how they work.

int vrl_SystemStartup(void)
      {
      vrl_FileSetLoadpath(getenv("AVRIL"));
      vrl_Mathinit();
      if (vrl_RenderInit(800, 800, 500, 5, 52000))
           {
           printf("Could not initialize rendering engine\n");
           exit(1);
           }
      atexit(vrl_RenderQuit);
      if (vrl_TimerInit()) return -2;
      atexit(vrl_TimerQuit);
      if (vrl_DisplayInit(0)) return -3;
      atexit(vrl_DisplayQuit);
      vrl_MouseInit();
      vrl_SystemStartRunning();
      vrl_SystemRequestRefresh();
      return 0;
      }

The vrl_SystemStartup() routine does the initialization of all the various AVRIL subsystems. 
It first checks for an AVRIL environment variable; if one is found, it sets the loadpath (the
directory from which AVRIL will load all its files) to the value of that environment variable.

      Next, it calls vrl_MathInit(), which sets up various trig tables used internally by
AVRIL (for example, a table of sines that's used by the vrl_Sine() function described earlier). 
After that, the rendering engine itself is initialized; the parameters to the vrl_RenderInit()
function may change with a future release of the software, but for now just use the values
that are shown above.  The value 52000 is the amount of memory the renderer should allocate
for its internal use; if the renderer needs more than this amount of memory when rendering a
scene, it will set the "memory" value in the status struct described earlier (which was passed
to vrl_ApplicationDrawOver()).  If the renderer is unable to initialize itself (for example, if it
couldn't allocate the specified amount of memory) then vrl_RenderInit() returns a non-zero
value.  The atexit() function is used to ensure that the renderer is de-initialized using
vrl_RenderQuit() when the program exits.

      Next, the timer and display routines are initialized, and atexit() functions are set for
each.  The mouse is initialized, and vrl_SystemStartRunning() is called.  Finally, an initial
display refresh is requested.

      The vrl_SystemStartRunning(), vrl_SystemStopRunning(), and vrl_SystemIsRunning()
routines are used to control whether the system is currently "running" or not.  They just set
and check the value of the variable system_is_running; however, using the routines makes
your code a bit more readable.  It's also possible to redefine those routines to do something
in addition to just setting or clearing a flag.

      The vrl_SystemRun() routine is the main loop of every AVRIL application.  It looks
like this:

void vrl_SystemRun(void)
      {
      while (vrl_SystemIsRunning())
           {
           vrl_Object *list;
           if (vrl_MouseRead(NULL, NULL, NULL))  /* mouse changed */
                 {
                 unsigned int mouse_buttons;
                 vrl_MouseRead(NULL, NULL, &mouse_buttons);
                 if (mouse_buttons)  /* button down */
                      {
                      int mouse_x, mouse_y, win_x, win_y;
                      unsigned int down_buttons = mouse_buttons;
                      vrl_DisplayGetWindow(&win_x, &win_y, NULL, NULL);
                      while (mouse_buttons)  /* wait for button release */
                           vrl_MouseRead(&mouse_x, &mouse_y, &mouse_buttons);
                      vrl_ApplicationMouseUp(mouse_x - win_x, mouse_y - win_y,
                           down_buttons);
                      }
                 }
           if (vrl_KeyboardCheck())
                 vrl_ApplicationKey(vrl_KeyboardRead());
           vrl_TaskRun();
           list = vrl_ObjectUpdate(vrl_WorldGetObjectTree());
           if (vrl_SystemQueryRefresh())
                 vrl_SystemRender(list);
           }
      }

It shouldn't come as any surprise that this looks like an event loop in a GUI application; on
some systems, that's exactly how vrl_SystemRun() will be implemented.  However, on a DOS
platform it's necessary to explicitly check the mouse and keyboard for activity.  If the mouse
has changed location or buttons status, the call to vrl_MouseRead() will return non-zero; if
the mouse buttons are down, the routine tracks the mouse input until the buttons are released. 
The button status is saved in the variable down_buttons, and then passed to the routine
vrl_ApplicationMouseUp() along with the mouse cursor location in the current window.

      If a key has been pressed, the keyboard is read and the value of the key is passed to
vrl_ApplicationKey().

      The vrl_TaskRun() function is called to run all the tasks that have been created (like
the spin(), bounce() and pulsate() tasks we used in example 4).  Next, the vrl_ObjectUpdate()
routine is called; it walks the hierarchical tree of objects in the world, updating their location
and orientation information and threading them onto a linked list which is returned as the
value of the vrl_ObjectUpdate() function.  If the display needs to be redrawn (i.e. the
vrl_SystemRequestRefresh() routine that we mentioned earlier has been called at least once
since we last re-drew the screen) then the vrl_SystemRender() routine is called, and is given
the linked list of objects to render.

      The vrl_SystemRender() routine does the actual updating of the screen.  Even though
source is provided, you should use this routine as-is; it's likely to change in future releases of
AVRIL, and several additional features will be added.  The code currently looks like this:

vrl_Status *vrl_SystemRender(vrl_Object *list)
      {
      static vrl_Object *lastlist = NULL;
      int pagenum;
      vrl_Status *stat;
      long render_start;
      if (list == NULL)
           {
           list = lastlist;
           if (list == NULL)
                 return NULL;
           }
      else
           lastlist = list;
      pagenum = vrl_DisplayGetDrawPage();
      if (++pagenum >= vrl_DisplayGetNpages())
           pagenum = 0;
      vrl_DisplaySetDrawPage(pagenum);
      render_start = vrl_TimerRead();
      vrl_RenderBegin(vrl_WorldGetCamera(), vrl_WorldGetLights());
      vrl_RenderSetAmbient(vrl_WorldGetAmbient());
      if (vrl_WorldGetScreenClear())
           {
           if (vrl_WorldGetHorizon() && !vrl_DisplayGetDrawMode())
                 vrl_RenderHorizon();
           else
                 vrl_DisplayClear(vrl_WorldGetSkyColor());
           }
      vrl_ApplicationDrawUnder();
      stat = vrl_RenderObjlist(list);
      last_render_ticks = vrl_TimerRead() - render_start;
      vrl_ApplicationDrawOver(stat);
      vrl_MouseCursorHide();
      vrl_DisplayUpdate();
      vrl_DisplaySetViewPage(pagenum);
      vrl_MouseCursorShow();
      need_to_redraw = 0;
      return stat;
      }

If the list that the vrl_SystemRender() routine is given is NULL (as was the case in our
example vrl_ApplicationMouseUp() routine in input.c) then the last list of objects rendered is
used.

      The system uses the concept of a "draw" page (on which drawing takes place) and a
"view" page (which is the one the user is currently viewing).  The vrl_SystemRender() routine
gets the current drawing page number, and increments it (so we start drawing on the next
page).  It wraps back to page zero after it's drawn the last available display page.  It then sets
the render_start variable to the current time (to keep track of how long it takes to draw this
screen), then calls vrl_RenderBegin() to set the current camera and list of light sources.  It
sets the ambient light level to be that for the current world, checks to see if it should clear the
screen, then either clears it or draws a horizon.  The vrl_ApplicationDrawUnder() routine we
looked at earlier is then called, and the vrl_RenderObjectlist() routine is called to do the
actual rendering.  The last_render_ticks variable is set; this is the value returned by
vrl_SystemGetRenderTime(), which we talked about earlier.

      The vrl_ApplicationDrawOver() routine is then called to put any additional
information on the display.  The cursor is hidden, and vrl_DisplayUpdate() is called; this is
necessary, since some display systems don't have multiple page and instead use an off-screen
buffer which the vrl_DisplayUpdate() routine copies to the screen.  For systems that have
multiple-page displays, the current view page is set to the (now finished) drawing page.  The
mouse cursor is then revealed again, and the need_to_redraw variable (which was set by
vrl_SystemRequestRefresh()) is cleared.

      The final vrl_System routine is vrl_SystemCommandLine().  It just goes through the
command-line parameters and calls the appropriate routines to read the various types of files:

void vrl_SystemCommandLine(int argc, char *argv[])
      {
      int i;
      vrl_Camera *cam;
      vrl_World *w = vrl_WorldGetCurrent();
      for (i = 1; i < argc; ++i)  /* i = 1 to skip argv[0] */
           {
           FILE *in = fopen(argv[i], "r");
           if (in == NULL) continue;
           if (strstr(argv[i], ".wld"))
                 vrl_ReadWLD(in);
           else if (strstr(argv[i], ".fig"))
                 vrl_ReadFIG(in, NULL, NULL);
           else if (strstr(argv[i], ".plg"))
                 vrl_ReadObjectPLG(in);
           /* ignore anything else */
           fclose(in);
           }
      if (vrl_WorldGetCamera())   /* have a camera already */
           return;
      cam = vrl_FindOnList(w->named_cameras, "1");
      if (cam)
           vrl_WorldSetCamera(cam);
      else
           vrl_NewCamera();
      }

After all the files on the command line have been processed, the vrl_SystemCommandLine()
routine checks to see if a current camera has been set.  If not, it looks for a camera called "1"
(this is for backwards compatibility with older .WLD files which only supported numeric
camera names; camera "1" was the default).  If a camera with that name is found, it's set as
the current camera; otherwise a new camera is created.

Part II - Technical Reference

      This section describes AVRIL from a technical perspective, and describes the data
types and functions that are available.  Anything that is not documented here should not be
used, since it's subject to change in the next release of AVRIL.  Also keep in mind that some
of the routines described below may be implemented as macros, and that this may change in
future releases; none of your code should assume that any particular routine is either a macro
or a real function.

      There are a number of important concepts that are essential to an understanding of
how AVRIL works.  Let's start by examining them.

Basic Data Types

      AVRIL uses a left-handed coordinate system; if the X axis points to the right, and the
Y axis points up, then Z points straight ahead.  If you're looking at the origin from the
positive end of an axis, a clockwise rotation is a positive rotation angle.

      Distances in AVRIL are represented by Scalars, and rotation angles by Angles. 
AVRIL can be compiled to use either floating point or fixed point, so it's important to use
the Scalar and Angle types for portability.  Scalars should always be treated as (long) integer
values, regardless of whether floating or fixed point is used.  Angles are always measured in
degrees.  A third fundamental data type is a Factor, which is used for things like the return
value of trig functions; a special constant called UNITY is #defined to be a Factor of 1.

      In a floating point implementation, all three types (Scalars, Angles and Factors) are
stored as floats; in a fixed-point implementation, they're all 32-bit integers (which will be
"long" on some systems).  The following macros are provided for converting between floating
point (or regular integers) and the three special types:

      float2scalar(float)
      scalar2float(Scalar)

      float2angle(float)
      angle2float(Angle)

      float2factor(float)
      factor2float(Factor)

Several routines are provided to support portable multiplication and division of the types:

      vrl_Scalar vrl_ScalarDivide(Scalar a, Scalar b)
      vrl_Scalar vrl_ScalarMultDiv(Scalar a, Scalar b, Scalar c)
      vrl_Scalar vrl_FactorMultiply(Scalar a, Factor b)

The first of these simply divides two Scalars and returns a Scalar result.  The second
multiplies two Scalars and divides by a third, using a 64-bit intermediate result; in other
words, it computes (a*b)/c.  The third routine multiplies a Scalar by a Factor and returns a
Scalar result; it can also be used for multiplying two Factors, or an integer or long value by a
Factor.

      On floating-point implementations, there may be occasions where the computed value
of a Scalar has a fractional part; in such cases you should use the following function:

      vrl_Scalar vrl_ScalarRound(Scalar a)

to round to the nearest valid Scalar value.

      There are currently two trig routines, vrl_Sine() and vrl_Cosine(); they both take
Angles as parameters and return Factors:

      Factor vrl_Sine(Angle angle)
      Factor vrl_Cosine(Angle angle)

The routine vrl_Mathinit() should be called before calling any of the trig functions; it pre-
computes the trig tables.

Vectors

      A Vector is a three-element array, which can be indexed by the #defined constants X,
Y and Z; for example, if v is a vector then v[X] is the X-component of the vector.  In
general, Vectors are made up of three Scalars; however, a normal vector (such as a facet
normal, a basis vector, or a vector that's been normalized using the vrl_VectorNormalize()
function) will actually have Factors as elements.  The following functions perform
fundamental operations on Vectors:

      void vrl_VectorCreate(Vector result, Scalar x, Scalar y, Scalar z);
      void vrl_VectorCopy(Vector destination, Vector source);
      void vrl_VectorAdd(Vector result, Vector v1, Vector v2);
      void vrl_VectorSub(Vector result, Vector v1, Vector v2);
      Factor vrl_VectorDotproduct(Vector v1, Vector v2);
      Scalar vrl_VectorCrossproduct(Vector result, Vector v1, Vector v2);
      Scalar vrl_VectorMagnitude(Vector v);
      void vrl_VectorNormalize(Vector v);

The vrl_VectorCreate() function takes three Scalars and assembles them into a Vector.  The
vrl_VectorCopy(), vrl_VectorAdd() and vrl_VectorSub() routines do element-by-element
copies, additions and subtractions of Vectors.  The vrl_VectorDotproduct() routine computes
the dot product (inner product) of two vectors; at least one of the vectors should be
normalized for this to work properly.

      The vrl_VectorCrossproduct() routine computes the vector cross product (outer
product) of two vectors.  This is likely to be slow, since it normalizes the result (which
involves doing a square root operation).  It returns the magnitude of the vector prior to
normalization.  The vrl_Magnitude() routine returns the magnitude of a vector, and the
vrl_VectorNormalize() routine scales a vector so that it has a magnitude of 1.

Matrices

      A Matrix is a 4 by 3 array that stores location and orientation information.  All
AVRIL matrices are homogenous; the upper 3 by 3 submatrix stores rotation information and
the last 3-element row stores a translation vector.  You should never have to deal with the
Matrix type directly.  However, in case you do have a need to deal with actual matrices, the
following routines are provided:

      void vrl_MatrixIdentity(Matrix m);
      void vrl_MatrixCopy(Matrix result, Matrix m);
      void vrl_MatrixMultiply(Matrix result, Matrix m1, Matrix m2);
      void vrl_MatrixInverse(Matrix result, Matrix m);
      void vrl_MatrixRotX(Matrix m, Angle angle);
      void vrl_MatrixRotY(Matrix m, Angle angle);
      void vrl_MatrixRotZ(Matrix m, Angle angle);
      void vrl_MatrixRotVector(Matrix m, Angle angle, Vector vector);
      void vrl_MatrixResetRotations(Matrix m);
      void vrl_MatrixGetBasis(Vector v, Matrix m, int axis);
      void vrl_MatrixTranslate(Matrix result, Scalar x, Scalar y, Scalar z);
      void vrl_MatrixSetTranslation(Matrix result, Scalar x, Scalar y, Scalar z);

      void vrl_MatrixGetTranslation(Vector v, Matrix m);

The vrl_MatrixIdentity() function sets the matrix to zeroes, except for the diagonal elements
which are set to UNITY.  The vrl_MatrixCopy() and vrl_MatrixMultiply() routines are used
to copy and multiply matrices, and the vrl_MatrixInverse() routine computes the matrix
inverse.  The various rotation functions apply a rotation around X, Y, Z or a specified vector
by a given angle; the vrl_MatrixResetRotations() routine sets all the rotations to zero.

      The function vrl_MatrixGetBasis() gets one of the basis vectors of the rotation part of
the matrix; this is equivalent to (but faster than) transforming an axis-aligned unit vector by
the matrix.  In other words, vrl_MatrixGetBasis(v, m, X) is equivalent to transforming the
vector [1,0,0] by the rotation part of the matrix and storing the result in the vector v.

      The vrl_MatrixTranslate() routine applies a translation to the matrix, and
vrl_MatrixSetTranslation() sets the actual translation part of the matrix.  The
vrl_MatrixGetTranslation() routine fills the given vector with the current translation part of
the matrix.

Transforms

      You should never have to use any of the transform functions directly; this is all
handled for you by AVRIL.  A vector can be transformed by a matrix, or each component of
the transform (X, Y or Z) can be computed separately:

      void vrl_Transform(Vector result, Matrix m, Vector v);
      Scalar vrl_TransformX(Matrix m, Vector v);
      Scalar vrl_TransformY(Matrix m, Vector v);
      Scalar vrl_TransformZ(Matrix m, Vector v);

Worlds

      In AVRIL, a virtual world is a collection of objects, light sources, virtual cameras and
miscellaneous attributes.  You can have any number of worlds within a single AVRIL
application; they're distinct from each other, and you can switch between them whenever you
like.

      When you run an AVRIL program, a default world is created and initialized for you;
if you only plan on having one world in your application, you don't have to do anything
special.  If you want to create additional worlds, you can simply declare variables of type
vrl_World and initialize them by calling vrl_WorldInit(&yourworld); however, it's probably
better to dynamically allocate them using malloc().  In fact, the simplest way to create a
world is with the vrl_NewWorld() function, which allocates the space and initializes the world
for you.  To make a given world current, use the vrl_WorldSetCurrent() function; the
vrl_WorldGetCurrent() function can be used to get a pointer to the current world.

      vrl_World *vrl_WorldInit(vrl_World *world);
      vrl_World *vrl_NewWorld(void);
      void vrl_WorldSetCurrent(vrl_World *world);
      vrl_World *vrl_WorldGetCurrent(void);

You can easily add objects and light sources to the current world, and remove them; you can
also count how many of each the current world contains, and get pointers to the linked list of
lights and the hierarchical tree of objects:

      void vrl_WorldAddLight(vrl_Light *light);
      void vrl_WorldRemoveLight(vrl_Light *light);

      void vrl_WorldAddObject(vrl_Object *obj);
      void vrl_WorldRemoveObject(vrl_Object *obj);

      int vrl_WorldCountObjects(void);
      int vrl_WorldCountLights(void);

      vrl_Light *vrl_WorldGetLights(void);
      vrl_Object *vrl_WorldGetObjectTree(void);

Each world has a "current camera" through which the world is seen; you can set the current
camera, and get a pointer to it using these routines:

      void vrl_WorldSetCamera(vrl_Camera *cam);
      vrl_Camera *vrl_WorldGetCamera(void);

The clearing of the screen prior to each frame, and the use (and colors) of the horizon, are
controlled by the following functions:

      void vrl_WorldSetScreenClear(int n);
      int vrl_WorldGetScreenClear(void);
      void vrl_WorldToggleScreenClear(void);

      void vrl_WorldSetHorizon(int n);
      int vrl_WorldGetHorizon(void);
      void vrl_WorldToggleHorizon(void);

      void vrl_WorldSetGroundColor(int color);
      int vrl_WorldGetGroundColor(void);

      void vrl_WorldSetSkyColor(int color);
      int vrl_WorldGetSkyColor(void);

The rate at which the user moves and turns is controlled by the "turn" step and the "move"
step.  In addition, the movement "mode" can be set to 0 or 1; if it's 1 (the default) then
simple movement can move the user vertically, otherwise they stay on the ground.

      void vrl_WorldSetMovementMode(int n);
      int vrl_WorldGetMovementMode(void);
      void vrl_WorldToggleMovementMode(void);

      void vrl_WorldSetMovestep(Scalar distance);
      Scalar vrl_WorldGetMovestep(void);

      void vrl_WorldSetTurnstep(Angle angle);
      Angle vrl_WorldGetTurnstep(void);

Finally, additional aspects of the virtual world such as the ambient light level and the "scale
factor" (the correspondence between real-world millimeters and distances in the virtual world)
can be set and queried using the following functions:

      void vrl_WorldSetAmbient(Factor ambient);
      Factor vrl_WorldGetAmbient(void);

      void vrl_WorldSetScale(Scalar scale);
      Scalar vrl_WorldGetScale(void);


Objects

      Objects are the most important entities in a virtual world.  All objects have a location
and orientation, and they can be attached to each other in a tree-structured hierarchy.  Each
object can have a shape (i.e. geometric description) and a surface map.  You can create an
object statically (by declaring a variable of type vrl_Object) or dynamically (either by using
malloc() to allocate the space and vrl_ObjectInit() to initialize it, or by simply calling
vrl_NewObject()).  If you use vrl_NewObject(), you can optionally specify a shape for the
object to use; if you don't want to assign a shape, use NULL.

      vrl_Object *vrl_ObjectInit(vrl_Object *obj);
      vrl_Object *vrl_NewObject(vrl_Shape *shape);

Objects can be moved, either to an absolute location or relative to their current location:

      void vrl_ObjectMove(vrl_Object *obj, Scalar x, Scalar y, Scalar z);
      void vrl_ObjectRelMove(vrl_Object *obj, Scalar x, Scalar y, Scalar z);

An object's current location can be obtained in two ways, either component-by-component for
each of X, Y and Z, or copied into a vector:

      Scalar vrl_ObjectGetX(vrl_Object *object);
      Scalar vrl_ObjectGetY(vrl_Object *object);
      Scalar vrl_ObjectGetZ(vrl_Object *object);

      void vrl_ObjectGetLocation(vrl_Object *object, vrl_Vector v);

An object can also be rotated around X, Y or Z, or around an arbitrary vector; in addition, its
rotations can be reset to zero:

      void vrl_ObjectRotX(vrl_Object *obj, Angle angle);
      void vrl_ObjectRotY(vrl_Object *obj, Angle angle);
      void vrl_ObjectRotZ(vrl_Object *obj, Angle angle);
      void vrl_ObjectRotVector(vrl_Object *obj, Angle angle, Vector vector);
      void vrl_ObjectRotReset(vrl_Object *obj);

The current world-space orientation of an object's "forward", "up" and "right" vectors can be
obtained using the following routines:

      void vrl_ObjectGetForwardVector(vrl_Object *object, Vector v);
      void vrl_ObjectGetRightVector(vrl_Object *object, Vector v);
      void vrl_ObjectGetUpVector(vrl_Object *object, Vector v);

The vectors filled in by these routines will all be normalized.

An object can be attached to another object, or detached from whatever object it is currently
attached to:

      vrl_Object *vrl_ObjectAttach(vrl_Object *obj, vrl_Object *newparent);
      vrl_Object *vrl_ObjectDetach(vrl_Object *obj);

Note that all movement and rotation is carried out in the object's coordinate system.  If the
object is attached to another object, its location and orientation will depend on that of its
parent.

      You can walk an entire object tree, executing a function on each node of the tree,
using the following routine:

      void vrl_ObjectTraverse(vrl_Object *object, int (*function)(vrl_Object *obj));

The function is called once for each object in the hierarchy, and is given a pointer to the
object it's being called on; if the function returns a non-zero value at any point, the tree is not
processed any further.

      The shape and surface map of an object can be altered at any time, and as often as
needed, using the following routines:

      void vrl_ObjectSetShape(vrl_Object *object, vrl_Shape *shape);
      vrl_Shape *vrl_ObjectGetShape(vrl_Object *object);

      void vrl_ObjectSetSurfacemap(vrl_Object *object, vrl_Surface **map);
      vrl_Surface **vrl_ObjectGetSurfacemap(vrl_Object *object);

Objects can be flagged as invisible (in which case they're not drawn) or highlighted (in which
case they're drawn with a bright outline).  They can also have a "layer" property, and
individual layers can be made visible or invisible.  The following routines are used to set,
query and toggle those values:


      void vrl_ObjectSetVisibility(vrl_Object *object, int vis);
      int vrl_ObjectGetVisibility(vrl_Object *object);
      void vrl_ObjectToggleVisibility(vrl_Object *object);

      void vrl_ObjectSetHighlight(vrl_Object *object, highlight);
      int vrl_ObjectGetHighlight(vrl_Object *object);
      void vrl_ObjectToggleHighlight(vrl_Object *object);

      void vrl_ObjectSetLayer(vrl_Object *object, int layer);
      int vrl_ObjectGetLayer(vrl_Object *object);

AVRIL will normally select a level of detail for an object automatically; however, you can
override this mechanism on an object-by-object basis using two routines to set and get the
current "forced" rep for an object:

      void vrl_ObjectSetRep(vrl_Object *object, vrl_Rep *rep);
      vrl_Rep *vrl_ObjectGetRep(vrl_Object *object);

Whenever an object moves, all the objects "descended" from that object must be updated. 
The following function will update the object and all its descendants; you should generally
only call this once per frame, on the object tree for the current world:

      vrl_Object *vrl_ObjectUpdate(vrl_Object *object);

Shapes

      As described earlier, AVRIL keeps shape information separate from object
descriptions, so that shapes can be re-used by multiple objects.  Shapes (entities of type
vrl_Shape) are generally read from PLG files using the vrl_ReadPLG() function described
later.  You can also create them using the vrl_Primitive family of functions, also described
later in this document.

      You can modify a shape after it's been loaded; bear in mind that any changes you
make to a shape will affect all objects using that shape.  After making changes to a shape (or
any representation within a shape), you should call vrl_ShapeUpdate() to recompute the
shape's bounds.

      void vrl_ShapeUpdate(vrl_Shape *shape);

Shapes can have a default surface map, which is used for objects that don't set one of their
own.  A pointer to a shape's default surface map can be obtained by calling the function

      vrl_Surface **vrl_ShapeGetSurfacemap(vrl_Shape *shape);

Future releases of this documentation will go into more detail about shapes.

Representations

      A shape can have any number of representations, at various levels of detail.  Each
representation (vrl_Rep) has a set of vertices (each of type Vector) and a set of facets (each
of type vrl_Facet).  A representation can also have a "sorting type" field; this will be
explained in more detail in future releases of this documentation.

      You can traverse the list of representations for a shape, calling a function on each
representation, by using the following routine:

      void vrl_ShapeTraverseReps(vrl_Shape *shape, int (*function(vrl_Rep *rep)));

The function is once called for every representation, and is given the representation as a
parameter.  If the function returns a non-zero value, the processing of the representation list
stops at that rep.

      To obtain a pointer to the rep which will be active at a given on-screen size, use the
following function:

      vrl_Facet *vrl_RepGetFacet(vrl_Rep *rep, int n);

You can set and get the sorting type, find out the approximate size (in pixels) at which a rep
becomes effective, as well as count the number of vertices and facets in a rep using the
following functions:

      void vrl_RepSetSorting(vrl_Rep *rep, int type);
      int vrl_RepGetSorting(vrl_Rep *rep);

      int vrl_RepGetSize(vrl_Rep *rep);

      int vrl_RepCountVertices(vrl_Rep *rep);
      int vrl_RepCountFacets(vrl_Rep *rep);

There are also "traversal" functions for vertices and facets:

      void vrl_RepTraverseVertices(vrl_Rep *rep, int (*function)(Vector *vertex));
      void vrl_RepTraverseFacets(vrl_Rep *rep, int (*function)(vrl_Facet *facet));

Facets
      AVRIL's terminology is slightly different from some other VR systems; a "facet" is a
flat three-dimensional entity, whereas a "polygon" is a two dimensional area on the screen. 
The graphics pipeline turns facets into polygons.

      Facets in AVRIL have an array of integers that specify which vertices in the
representation should be connected (and in what sequence) to form the outline of the facet. 
Facets also have an index into the surface map for an object, to determine what the surface
properties of the facet should be.  They also have a normal vector, for lighting and backface
removal, and a bit that indicates whether or not the facet should be highlighted.

      The surface index of any facet can be set or queried at any time using the following
two routines:

      void vrl_FacetSetSurfnum(vrl_Facet *facet, int n);
      int void vrl_FacetGetSurfnum(vrl_Facet *facet);

The highlighting of the facet can be set, queried or toggled using the following routines:

      void vrl_FacetSetHighlight(vrl_Facet *facet, int high);
      int vrl_FacetGetHighlight(vrl_Facet *facet);
      void vrl_FacetToggleHighlight(vrl_Facet *facet);

The number of points in the facet, the index of any particular point, or a pointer to the vertex
for a particular point, can all be obtained using these routines:

      int vrl_FacetCountPoints(vrl_Facet *facet);
      int vrl_FacetGetPoint(vrl_Facet *facet, int n);
      Vector *vrl_FacetGetVertex(vrl_Rep *rep, vrl_Facet *facet, int n); 


Surfaces

      AVRIL surfaces are designed for expandability.  At the moment, each vrl_Surface
consists of a type, a hue and a brightness.  The types are SURF_FLAT (for flat shading),
SURF_METAL (for a pseudo-metallic effect) and SURF_GLASS (for a partially transparent
effect).  Surfaces are initialized and modified using the following routines:

      vrl_Surface *vrl_SurfaceInit(vrl_Surface *surf, unsigned char hue);
      vrl_Surface *vrl_NewSurface(unsigned char hue);

      void vrl_SurfaceSetType(vrl_Surface *surf, int type);
      int vrl_SurfaceGetType(vrl_Surface *surf);

      void vrl_SurfaceSetHue(vrl_Surface *surf, unsigned char h);
      unsigned char vrl_SurfaceGetHue(vrl_Surface *surf);

      void vrl_SurfaceSetBrightness(vrl_Surface *surf, unsigned char b);
      unsigned char vrl_SurfaceGetBrightness(vrl_Surface *surf);

The hue and brightness values are 8-bit unsigned quantities; the maximum brightness value is
therefore 255.

      For backwards compatibility with REND386, AVRIL includes a function to convert a
16-bit REND386 surface descriptor into a vrl_Surface:

      vrl_Surface *vrl_SurfaceDesc(unsigned int desc, vrl_Surface *surf);

Surface maps are just arrays of pointers to surfaces; you can create a surface map for a
particular number of entries, and access entries within a map, using the following routines:

      vrl_Surface **vrl_NewSurfacemap(int n);
      vrl_Surface *vrl_SurfacemapGetSurface(vrl_Surface **map, int surfnum);
      vrl_Surface *vrl_SurfacemapSetSurface(vrl_Surface **map, int surfnum,
           vrl_Surface *surface);

Lights

      Lights in AVRIL have a number of properties; they can be on or off, they can have an
intensity, and they can be associated with an object.  The on/off and intensity properties are
similar to a household dimmer; rotating the knob on a dimmer alters the intensity, and
pushing it in toggles the light on and off.

      The current version of AVRIL only supports ambient lights and directional lights;
point sources will be supported soon.  Any light that is not associated with an object is
considered ambient; this is in addition to the overall ambient light level for the world.  A
directional light uses the orientation of the object it's associated with to determine which
direction the light should come from.  A point source light (once implemented) will use the
location of the object it's associated with to determine where the light comes from.

      As with worlds and objects, lights can be statically or dynamically created using the
following functions:

      vrl_Light *vrl_LightInit(vrl_Light *light);
      vrl_Light *vrl_NewLight(void);

The light's type value can be one of LIGHT_AMBIENT, LIGHT_DIRECTIONAL or
LIGHT_POINTSOURCE, and is set and queried using the following two functions:

      void vrl_LightSetType(vrl_Light *light, int type);
      int vrl_LightGetType(vrl_Light *light);

The light's on/off status can be checked and altered, and the intensity set and queried, using
these functions:

      void vrl_LightOn(vrl_Light *light);
      void vrl_LightOff(vrl_Light *light);
      void vrl_LightToggle(vrl_Light *light);
      int vrl_LightIsOn(vrl_Light *light);

      void vrl_LightSetIntensity(vrl_Light *light, Factor inten);
      Factor vrl_LightGetIntensity(vrl_Light *light);

Notice that the intensity values are Factors; they should never be greater than UNITY or less
than zero.

      You can make and break associations between a light source and an object, and
determine what object a light source is currently associated with, using the following routines:

      void vrl_LightAssociate(vrl_Light *light, vrl_Object *object);
      void vrl_LightDisAssociate(vrl_Light *light);
      vrl_Object *vrl_LightGetObject(vrl_Light *light);

All the movement and rotation routines that were used for objects earlier have counterparts
that are used for moving and rotating light sources; they're implemented as macros that just
perform the move or rotation on the object with which the light source is associated.

      vrl_LightMove(light, x, y, z)
      vrl_LightRelMove(light, x, y, z)
      vrl_LightRotX(light, angle)
      vrl_LightRotY(light, angle)
      vrl_LightRotZ(light, angle)
      vrl_LightRotVector(light, angle, vector)
      vrl_LightRotReset(light)
      vrl_LightAttach(obj, newparent)
      vrl_LightDetach(obj)
      vrl_LightGetLocation(light, v)
      vrl_LightGetX(light)
      vrl_LightGetY(light)
      vrl_LightGetZ(light)

It's important to note the difference between attaching and associating light sources.  A light
source can be associated with an object, which means it will use that object's location and
orientation as its own.  The object with which the light is associated can be attached to
another object, and "inherit" location and orientation information from it.  The
vrl_LightAttach() and vrl_LightDetach() routines are provided only as a convenience; what
you're really attaching and detaching with those routines is the object that the light is
associated with.  You generally associate a light source with an object once, and leave it that
way; you can attach or detach the light however you want after that.

Cameras

      AVRIL allows you to have any number of virtual cameras.  Each camera is associated
with an object, much as lights are.  However, unlike lights, cameras must be attached to an
object; there's no such thing as an "ambient" camera.  Cameras are initialized just like objects
or light sources:

      vrl_Camera *vrl_CameraInit(vrl_Camera *camera);
      vrl_Camera *vrl_NewCamera(void);

Cameras have only a couple of properties that are important; in particular, a zoom factor, an
aspect ratio, and a hither clipping plane distance.  These are all set and queried using the
following routines:

      void vrl_CameraSetZoom(vrl_Camera *camera, float zoom);
      float vrl_CameraGetZoom(vrl_Camera *camera);

      void vrl_CameraSetAspect(vrl_Camera *camera, float asp);
      float vrl_CameraGetAspect(vrl_Camera *camera);

      void vrl_CameraSetHither(vrl_Camera *camera, Scalar hither);
      Scalar vrl_CameraGetHither(vrl_Camera *camera);

Notice that the zoom factor and aspect ratio are floats; this may change in a future release of
AVRIL.  The zoom factor works like the zoom on a camera; the higher the zoom, the more
the image is magnified.  The zoom is the tangent of half the field of view (viewing angle). 
The aspect ratio is the ratio between the horizontal and vertical zoom factors.  The hither
clipping distance is the distance in virtual space from the camera to the invisible plane at
which objects will be "clipped".

      The routines for associating a camera with an object and for determining what object a
camera is currently associated with are as follows:

      void vrl_CameraAssociate(vrl_Camera *camera, vrl_Object *object);
      vrl_Object *vrl_CameraGetObject(vrl_Camera *camera);

There's no vrl_CameraDisAssociate() function, as there was for lights; cameras must be
associated with an object in order to have any meaning.

      Again, routines are provided for manipulating and querying the location and
orientation of a virtual camera; these are macros, just as they were for lights:

      vrl_CameraMove(camera, x, y, z)
      vrl_CameraRelMove(camera, x, y, z)
      vrl_CameraRotX(camera, angle)
      vrl_CameraRotY(camera, angle)
      vrl_CameraRotZ(camera, angle)
      vrl_CameraRotVector(camera, angle, vector)
      vrl_CameraRotReset(camera)
      vrl_CameraAttach(obj, newparent)
      vrl_CameraDetach(obj)
      vrl_CameraGetX(camera)
      vrl_CameraGetY(camera)
      vrl_CameraGetZ(camera)
      vrl_CameraGetLocation(camera, v)

In addition, there are three routines that obtain the current "forward", "right" and "up" vectors
for a camera:

      vrl_CameraGetForwardVector(camera, v)
      vrl_CameraGetRightVector(camera, v)
      vrl_CameraGetUpVector(camera, v)

These are used in input.c to let the user move forwards, backwards, up and down relative to
their current orientation.

Layers

      Layers were described earlier, in the section on objects.  The routines for dealing with
layers are as follows:

      void vrl_LayerOn(int n);
      void vrl_LayerOff(int n);
      void vrl_LayerToggle(int n);
      int vrl_LayerIsOn(int n);
      void vrl_LayerAllOn(void);

The last routine, vrl_LayerAllOn(), turns all the layers on at once; this is the default
condition.

File I/O Routines

      AVRIL supports the PLG file format, the FIG file format, and most of the WLD file
format.  The library contains routines for reading each of those formats:

      vrl_Shape *vrl_ReadPLG(FILE *in);
      vrl_Object *vrl_ReadObjectPLG(FILE *in);
      int vrl_ReadWLD(FILE *in);
      vrl_Object *vrl_ReadFIG(FILE *in, vrl_Object *parent, char *rootname);

The vrl_ReadPLG() routine reads a shape from the specified file and returns a pointer to it. 
The vrl_ReadObjectPLG() routine is similar, but it also allocates an object and assigns the
shape to it.

      The vrl_ReadWLD() file reads a world description from the file into the current world;
you can make as many calls to this routine as you like, combining a number of WLD files. 
The vrl_ReadFIG() routine lets you specify a "parent" object to which the newly-read object
tree should be attached, as well as the name of the root object.  Any segnames that are
assigned with the FIG file will be added to the current world's list of named objects as
rootname.segname (named lists will be explained later, in the section on Lists).

      There are several other routines that support file operations.  Two routines maintain a
kind of "current directory" for file loading; they are

      void vrl_FileSetLoadpath(char *path);
      char *vrl_FileFixupFilename(char *fname);

The first sets the given path (if not NULL) to be the directory that subsequent file operations
should apply to.  The second routine is used to generate a full filename, with the current
loadpath prepended.  Note that filenames beginning with '/' or '\' are not modified.

      While loading a PLG file, a scale factor and offset can be applied.  The vertices read
from the file are multiplied by the scaling factors, and then the offsets are added to them. 
The scale factors and offsets are set using:

      void vrl_SetReadPLGscale(float x, float y, float z);
      void vrl_SetReadPLGoffset(float x, float y, float z);

FIG files can also have a scale factor applied to them.  In addition, parts of a figure that have
a "segnum" value set can have pointers to their objects placed into a parts array specified by
the user:

      void vrl_SetReadFIGscale(float x, float y, float z);
      void vrl_SetFigurePartArray(vrl_Object **ptr, int maxparts);

If the ptr is not NULL, then any parts in the FIG file that have a segnum will create an entry
in the array, indexed by the segnum value.  The maxparts value is the number of elements the
caller has provided space for in the array.

Lists

      AVRIL has a couple of simple routines that allow you to keep singly-linked lists of
named pointers.  To create a list, declare it as

      vrl_List *lst;

To add or locate entries, use the routines

      void *vrl_AddToList(vrl_List **list, char *name, void *value);
      void *vrl_FindOnList(vrl_List *list, char *name);

Notice that the vrl_AddToList() function takes a pointer to the pointer; i.e. you would call it
as vrl_AddToList(&lst, "name", objptr).

System and Application routines

      These were described in detail in the Introduction section, and source code is provided
in the files system.c and input.c for you to examine.

User Interface

      The current version of AVRIL has a few primitive user interface routines for you to
use.  A better user interface needs to be designed; in the meantime, here are the routines:

      void vrl_UserInterfaceBox(int width, int height, int *x, int *y);
      void vrl_UserInterfacePopMsg(char *msg);
      void vrl_UserInterfacePopText(char *text[]);
      int vrl_UserInterfaceDismiss(void);
      int vrl_UserInterfacePopMenu(char *text[]);
      unsigned vrl_UserInterfacePopPrompt(char *prompt, char *buff, int n);

The vrl_UserInterfaceBox() routine puts up a nice bordered box, centered on the screen.  The
width and height determine the size of the box.  When the routine returns, x and y will
contain the screen coordinates of the top-left corner of the box.

      The vrl_UserInterfacePopMsg() routine displays a one-line text message.  The
vrl_UserInterfacePopText() routine puts up a multi-line message; the array of string pointers
has to have a NULL entry at the end.

      The vrl_UserInterfaceDismiss() routine is useful after you've called either
vrl_UserInteracePopMsg() or vrl_UserInterfacePopText(); it waits for the user to press a key
or click the mouse.

      The vrl_UserInterfacePopMenu() routine displays a menu and waits for the user to
select an item.  If the user clicks on an item with the mouse, the index of that item will be
returned as the value of the function.  If the user presses a key, the menu is searched item by
item until one is found that has an uppercase letter matching the key the user entered; the
index of that entry is returned.  If the user clicks outside the menu, or presses ESC, the value
-1 is returned.

      The vrl_UserInterfacePopPrompt() box displays a prompt to the user and lets them
enter a text response.  The backspace key is supported.  The user can end their input using
either ENTER or ESC; the key they press to end their input is returned as the value of the
function.

      There are two other routines which are not really part of the user interface; they're
used to overlay text on the screen or display the compass.  They're typically called from
vrl_ApplicationDrawOver().

      void vrl_DrawCompass(vrl_Camera *camera, int x, int y, int armlen);
      void vrl_DropText(int x, int y, int color, char *text);

In vrl_DrawCompass(), the x and y values are the location of the "origin" of the compass and
armlen is the length of each arm.  (The arms will of course seem shorter because of
perspective).  The x, y and armlen values are in screen coordinates (i.e., pixels).  The camera
is used to obtain orientation information about the user.

      The vrl_DropText() routine displays the text message at the given screen coordinates
in the given color, with a black (i.e., color 0) drop shadow.

Tasks

      The pseudo-tasking mechanism was described back in the Introduction section.  Tasks
are added using vrl_TaskCreate(), which takes a pointer to the function, a pointer to the data,
and the period.  The tasks should be run periodically by calling vrl_TaskRun().  The tasks can
obtain a pointer to their data by calling vrl_TaskGetData(), the elapsed time since they last
ran by calling vrl_TaskGetElapsed(), and the current time by calling vrl_TaskGetTimeNow(). 
Note that for any given call to vrl_TaskRun(), all the tasks will receive the same value from
vrl_TaskGetTimeNow(); this is different from vrl_TimerRead(), since the timer runs
independently of the tasks.  You may want to use one or the other of those two functions
depending on the circumstances.

      int vrl_TaskCreate(void (*function)(void), void *data, unsigned long period);
      void vrl_TaskRun(void);
      void *vrl_TaskGetData(void);
      unsigned long vrl_TaskGetElapsed(void);
      unsigned long vrl_TaskGetTimeNow(void);

Primitives

      AVRIL currently provides five utility routines for creating simple geometric
primitives.  Each takes a surface map pointer; if the value is NULL, the default color for
geometric primitives is used.

      vrl_Shape *vrl_PrimitiveBox(Scalar width, Scalar height, Scalar depth, vrl_Surface **map);
      vrl_Shape *vrl_PrimitiveCone(Scalar radius, Scalar height, int nsides, vrl_Surface **map);
      vrl_Shape *vrl_PrimitiveCylinder(Scalar bottom_radius, Scalar top_radius, Scalar height,
           int nsides, vrl_Surface **map);
      vrl_Shape *vrl_PrimitivePrism(Scalar width, Scalar height, Scalar depth,
           vrl_Surface **map);
      vrl_Shape *vrl_PrimitiveSphere(Scalar radius, int nsides, vrl_Surface **map);

The box and sphere have their origin at their geometric centers, the cone and the cylinder
have their origin at the center of their bases, and the prism has its origin at one corner.

Rendering

      The rendering "engine" needs to be initialized before any actual rendering is done. 
The renderer needs to know how much memory to allocate for itself, as well as the maximum
number of objects, facets, vertices and lights it will have to contend with.  When the program
is ready to exit, vrl_RenderQuit() should be called to cleanly shut down the engine.

      int vrl_RenderInit(int maxvert, int maxf, int maxobjs, int maxlights,
           unsigned int mempoolsize);
      void vrl_RenderQuit(void);

Two functions are used to give the renderer a pointer to the current camera and list of lights,
and to set the current ambient lighting level (usually that for the current world):

      void vrl_RenderBegin(vrl_Camera *camera, vrl_Light *lights);
      void vrl_RenderSetAmbient(Factor amb);

Finally, two functions do the actual drawing; one draws a horizon, the other renders a list of
objects (such as that returned by vrl_ObjectUpdate()).

      void vrl_RenderHorizon(void);
      vrl_Status *vrl_RenderObjlist(vrl_Object *objects);

The vrl_RenderObjlist() function returns a pointer to a status struct, which was described in
the Introduction section.

The Keyboard, the Mouse and the Timer

      AVRIL has a set of routines which deal with the keyboard, mouse and timer; these
will vary from one platform to another, but they should all provide the same high-level
interface to application software.

      int vrl_TimerInit(void);
      void vrl_TimerQuit(void);
      unsigned long vrl_TimerRead(void);
      unsigned long vrl_TimerGetTickRate(void);
      int vrl_TimerAddHandler(void (*func)(unsigned long));

These routines let you initialize, de-initialize and read the timer.  The vrl_TimerGetTickRate()
routine returns the number of ticks per second that the timer runs at.  The higher this number
is, the more accurate the frames/second calculations will be (among other things).  You can
add handler functions that get called on every tick, and which are passed the current time as a
parameter.

      int vrl_MouseInit(void);
      int vrl_MouseRead(int *x, int *y, unsigned int *buttons);
      void vrl_MouseCursorHide(void);
      void vrl_MouseCursorShow(void);

These routines let you initialize and read the mouse.  Whenever you write to the currently
visible page (the viewpage) you should first call vrl_MouseCursorHide() to prevent the mouse
from being "squashed" under a falling polygon.  When you're finished updating the display,
call vrl_MouseCursorShow() to let the rodent run free again.  The vrl_SystemRender()
routine, found in system.c, shows how these routines are used.  The user interface routines do
the calls to vrl_MouseCursorHide() and vrl_MouseCursorShow(), so you don't need to worry
about them in that case.

      int vrl_KeyboardCheck(void);
      unsigned int vrl_KeyboardRead(void);

The vrl_KeyboardCheck() routine returns non-zero if a key has been pressed, and
vrl_KeyboardRead() returns the actual key.  Most keys just return their ASCII values; see the
file avrilkey.h for definitions of special keys (like arrows, function keys, etc).

Display routines

      The interface to the display routines is in a state of flux; the 1.00 release of this
document will provide the details, and a future appendix will explain how to write your own
display routines.

Some Final Notes

      Well, that's it.  This is the first release of AVRIL, and it's version is 0.9b -- that
means it's effectively a pre-release, so there may be some things that aren't fully
implemented yet.  If you run into any problems, my email address is broehl@uwaterloo.ca; be
sure to put AVRIL in the subject line so I know what it's about, otherwise it might take me
days to get back to you.  (It might anyway...)

      Features that will be added in future releases include routines for handling 6 DOF
input devices, stereoscopic viewing, support for sound, and an application language of some
sort.

      The latest release of AVRIL can always be found on sunee.uwaterloo.ca, in the
pub/avril directory.  I will also try to put the latest version on major sites such as
wuarchive.wustl.edu and oak.oakland.edu.

      If you'd like to help with AVRIL, here are some things you'll be able to do soon,
once the first full release is ready:

           - write more and better display routines
           - write better user interface libraries
           - write device handlers
           - implement application languages
           - improve the math routines

      In the meantime, I hope you enjoy using AVRIL.  Once version 1.00 is ready, I'll set
up a mailing list for questions (and answers!) about AVRIL.
