home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Virtual Reality Homebrewer's Handbook
/
vr.iso
/
avril
/
avrilref.txt
< prev
next >
Wrap
Text File
|
1996-03-19
|
135KB
|
2,833 lines
AVRIL Technical Reference
Version 2.0
March 28, 1995
Bernie Roehl
Note: This is the technical reference manual for version 2.0 of
AVRIL. The tutorial is a separate document. The appendices for
this technical reference manual are also stored separately.
This document describes AVRIL from a technical perspective,
and explains 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 future releases of AVRIL. Also keep in
mind that some of the routines described below may be implemented
as macros, and that this may also 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 vrl_Scalars, and
rotation angles by vrl_Angles. AVRIL can be compiled to use
either floating point or fixed point, so it's important to use
the vrl_Scalar and vrl_Angle types for portability. vrl_Scalars
should always be treated as (long) integer values, regardless of
whether floating or fixed point is used. vrl_Angles are always
measured in degrees (converted to the internal vrl_Angle format,
of course). A third fundamental data type is vrl_Factor, which
is used for things like the return value of trig functions; a
special constant called VRL_UNITY is #defined to be a vrl_Factor
of 1.
In a floating point implementation, all three types
(vrl_Scalars, vrl_Angles and vrl_Factors) are stored as floats;
in a fixed-point implementation, they're all 32-bit integers
(which will be "long" on many systems). The following macros are
AVRIL Technical Reference Manual 1
provided for converting between floating point (or regular
integers) and the three special types:
float2scalar(float);
scalar2float(vrl_Scalar);
float2angle(float);
angle2float(vrl_Angle);
float2factor(float);
factor2float(vrl_Factor);
Several routines are provided to support portable multiplication
and division of the types:
vrl_Factor vrl_ScalarDivide(vrl_Scalar a, vrl_Scalar b);
vrl_Scalar vrl_ScalarMultDiv(vrl_Scalar a, vrl_Scalar b, vrl_Scalar c);
vrl_Scalar vrl_FactorMultiply(vrl_Factor a, vrl_Scalar b);
The first of these routines simply divides two vrl_Scalars and
returns a vrl_Factor result; the absolute value of a should be
less than or equal to the absolute value of b. The second
routine multiplies two vrl_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 vrl_Factor by a
vrl_Scalar and returns a vrl_Scalar result; it can also be used
for multiplying two vrl_Factors, or an integer or long value by a
vrl_Factor. The order of the operands is significant, because C
automatically promotes ints to longs.
In floating-point implementations of AVRIL, there may be
occasions where the computed value of a vrl_Scalar has a
fractional part; in such cases you should use the following
function:
vrl_Scalar vrl_ScalarRound(vrl_Scalar value);
to round to the nearest valid vrl_Scalar value. To take the
absolute value of a vrl_Scalar, use the function
vrl_Scalar vrl_ScalarAbs(vrl_scale value);
There are currently two trig routines, vrl_Sine() and
vrl_Cosine(); they both take vrl_Angles as parameters and return
vrl_Factors:
vrl_Factor vrl_Sine(vrl_Angle angle);
vrl_Factor vrl_Cosine(vrl_Angle angle) ;
The routine vrl_MathInit() should be called before calling any of
the trig functions; it pre-computes the trig tables. This is
done in the vrl_SystemStartup() routine (found in system.c).
AVRIL Technical Reference Manual 2
Positions on the screen (i.e., pixel coordinates) are
represented using the vrl_ScreenPos type, for future portability.
Fractional screen positions (used in scan-converting polygons)
are represented using the vrl_ScreenCoord type. This type is
currently used only by the vrl_Display family of routines.
There are several other basic types used in AVRIL: vrl_Time
is a measure of elapsed time in ticks, vrl_Color is used to
represent colors (both 8-bit and 24-bit) and vrl_Boolean is a
true/false type value (non-zero being true). The types vrl_32bit
and vrl_unsigned32bit are used for signed and unsigned 32-bit
numbers, and vrl_16bit and vrl_unsigned16bit are used for signed
and unsigned 16-bit numbers. The function
vrl_32bit abs32(vrl_32bit value);
will return the absolute value of a 32-bit number, independent of
whether 32-bit values are of type int or type long.
Vectors
A vrl_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,
vrl_Vectors are made up of three vrl_Scalars; however, a
normalized vector (such as a facet normal, a basis vector, or a
vector that's been normalized using the vrl_VectorNormalize()
function) will actually have vrl_Factors as elements. The
following functions perform fundamental operations on
vrl_Vectors:
void vrl_VectorCreate(vrl_Vector result, vrl_Scalar x, vrl_Scalar y, vrl_Scalar z);
void vrl_VectorCopy(vrl_Vector destination, vrl_Vector source);
void vrl_VectorAdd(vrl_Vector result, vrl_Vector v1, vrl_Vector v2);
void vrl_VectorSub(vrl_Vector result, vrl_Vector v1, vrl_Vector v2);
void vrl_VectorNegate(vrl_Vector v);
vrl_Factor vrl_VectorDotproduct(vrl_Vector v1, vrl_Vector v2);
vrl_Scalar vrl_VectorCrossproduct(vrl_Vector result, vrl_Vector v1, vrl_Vector v2);
vrl_Scalar vrl_VectorMagnitude(vrl_Vector v);
void vrl_VectorNormalize(vrl_Vector v);
vrl_Scalar vrl_VectorDistance(vrl_Vector v1, vrl_Vector v2);
void vrl_VectorScale(vrl_Vector v, vrl_Scalar newmag);
void vrl_VectorRescale(vrl_Vector v, vrl_Scalar newmag);
void vrl_VectorPrint(FILE *out, char *str, vrl_Vector v);
vrl_Boolean vrl_VectorEqual(vrl_Vector v1, vrl_Vector v2);
void vrl_VectorZero(vrl_Vector v);
The vrl_VectorCreate() function takes three vrl_Scalars and
assembles them into a vrl_Vector. The vrl_VectorCopy(),
vrl_VectorAdd() and vrl_VectorSub() routines do element-by-
element copies, additions and subtractions of vrl_Vectors. The
vrl_VectorNegate() function reverses the direction of a
AVRIL Technical Reference Manual 3
vrl_Vector by flipping the sign of each of its components. 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.
The vrl_VectorDistance() routine takes two vrl_Vectors (each
representing a point in space) and computes the distance between
those two points. The vrl_Scale() function takes a normalized
vrl_Vector and scales all its components by the given amount; the
vrl_Rescale() function takes a non-normalized vector and re-
scales it to have the specified magnitude.
The vrl_VectorPrint() routine prints out a message followed
by the values of each of the components of the vrl_Vector,
enclosed in square brackets. Do not attempt to write to the
screen with this routine, since it will not work well in graphics
mode.
The vrl_VectorEqual() routine returns a non-zero value if
the two vrl_Vectors are identical, and vrl_VectorZero() sets the
components of a vrl_Vector to zero. The [0,0,0] vector is
sometimes needed, so a global vrl_Vector variable called
vrl_VectorNULL is defined.
Matrices
A vrl_Matrix is a 4 by 3 array that stores location and
orientation information. All AVRIL matrices are homogeneous; 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 vrl_Matrix type directly. However, in case you
do have a need to deal with actual matrices, the following
routines are provided:
void vrl_MatrixIdentity(vrl_Matrix m);
void vrl_MatrixCopy(vrl_Matrix result, vrl_Matrix m);
void vrl_MatrixMultiply(vrl_Matrix result, vrl_Matrix m1, vrl_Matrix m2);
void vrl_MatrixInverse(vrl_Matrix result, vrl_Matrix m);
void vrl_MatrixRotX(vrl_Matrix m, vrl_Angle angle, vrl_Boolean leftside);
void vrl_MatrixRotY(vrl_Matrix m, vrl_Angle angle, vrl_Boolean leftside);
void vrl_MatrixRotZ(vrl_Matrix m, vrl_Angle angle, vrl_Boolean leftside);
void vrl_MatrixRotVector(vrl_Matrix m, vrl_Angle angle, vrl_Vector vector,
vrl_Boolean leftside);
void vrl_MatrixResetRotations(vrl_Matrix m);
AVRIL Technical Reference Manual 4
void vrl_MatrixGetBasis(vrl_Vector v, vrl_Matrix m, int axis);
void vrl_MatrixSetBasis(vrl_Matrix m, vrl_Vector v, int axis);
void vrl_MatrixTranslate(vrl_Matrix result, vrl_Scalar x, vrl_Scalar y, vrl_Scalar z);
void vrl_MatrixSetTranslation(vrl_Matrix result,
vrl_Scalar x, vrl_Scalar y, vrl_Scalar z);
void vrl_MatrixGetTranslation(vrl_Vector v, vrl_Matrix m);
void vrl_MatrixGetRotations(vrl_Matrix m, vrl_Angle *rx, vrl_Angle *ry, vrl_Angle *rz);
The vrl_MatrixIdentity() function sets the matrix to zeroes,
except for the diagonal elements which are set to VRL_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. Several of the vrl_Matrix routines use a
leftside parameter; a non-zero value for this parameter specifies
that the transformation should be applied as a pre-multiplication
instead of a post-multiplication.
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 m 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, and vrl_MatrixGetRotations() gets the angles
which, when applied in the order Y, X, Z, produces the rotation
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(vrl_Vector result, vrl_Matrix m, vrl_Vector v);
vrl_Scalar vrl_TransformX(vrl_Matrix m, vrl_Vector v);
vrl_Scalar vrl_TransformY(vrl_Matrix m, vrl_Vector v);
vrl_Scalar vrl_TransformZ(vrl_Matrix m, vrl_Vector v);
Coordinate Systems
AVRIL allows objects to be translated or rotated in five
different coordinate systems. This may seem like a lot, but
they're easy to get used to. An object can be moved in its own
AVRIL Technical Reference Manual 5
local coordinate system, the coordinate system of the object it's
attached to, the "world" coordinate system, the viewer's
coordinate system, or the coordinate system of another object.
For example, consider a bicycle on an open train car. The
bicycle is facing sideways, so that if you were sitting on it
you'd be watching the scenery go by on the right side of the
train. The train itself is moving northeast. If we translate
the bicycle along the positive Z axis in its local coordinate
system, it will travel sideways off the train car, in a south-
easterly direction. If we move it in the positive Z direction of
its parent, it will move to the rider's left, towards the front
of the train (northeast). If we move it in the positive Z
direction in the world, it will move due north. If we're looking
at it from directly above, moving the bicycle in the viewer's
positive Z direction would send it through the train car and down
into the ground. If a bird is flying due south, then moving the
bicycle in the positive Z direction relative to the bird would
make the bike move due south.
We represent these various coordinate systems by the
constants VRL_COORD_LOCAL, VRL_COORD_PARENT, VRL_COORD_WORLD, and
VRL_COORD_OBJREL. The view-relative coordinate system is just a
special case of the VRL_COORD_OBJREL coordinate frame, with the
viewer as the object that the movement should be relative to.
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 vrl_malloc(). In fact, the
simplest way to create a world is with the vrl_WorldCreate()
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_WorldCreate(void);
void vrl_WorldSetCurrent(vrl_World *world);
vrl_World *vrl_WorldGetCurrent(void);
AVRIL Technical Reference Manual 6
You can easily add objects, light sources and cameras 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, linked list of cameras and the hierarchical tree
of objects You can also find lights, cameras and objects by
name.
void vrl_WorldAddLight(vrl_Light *light);
void vrl_WorldRemoveLight(vrl_Light *light);
vrl_Light *vrl_WorldFindLight(char *name);
void vrl_WorldAddCamera(vrl_Camera *camera);
void vrl_WorldRemoveCamera(vrl_Camera *camera);
vrl_Camera *vrl_WorldFindCamera(char *name);
void vrl_WorldAddObject(vrl_Object *obj);
void vrl_WorldRemoveObject(vrl_Object *obj);
vrl_Object *vrl_WorldFindObject(char *name);
int vrl_WorldCountObjects(void);
int vrl_WorldCountLights(void);
int vrl_WorldCountCameras(void);
vrl_Light *vrl_WorldGetLights(void);
vrl_Light *vrl_WorldGetCameras(void);
vrl_Object *vrl_WorldGetObjectTree(void);
If you need to iterate through the linked list of lights or
cameras, you can use the functions
vrl_Light *vrl_LightGetNext(vrl_Light *light);
vrl_Camera *vrl_CameraGetNext(vrl_Camera *camera);
You can also obtain information about the total number of facets
in the world, the minimum and maximum bounds of the world, the
center of the world and the radius of the world's bounding sphere
using these functions:
int vrl_WorldCountFacets(void);
void vrl_WorldGetBounds(vrl_Vector v1, vrl_Vector v2);
void vrl_WorldGetCenter(vrl_Vector v);
vrl_Scalar vrl_WorldGetSize(void);
Each world has a "current camera" through which the world is
seen; you can set the current camera, or get a pointer to it
using these routines:
void vrl_WorldSetCamera(vrl_Camera *cam);
vrl_Camera *vrl_WorldGetCamera(void);
AVRIL Technical Reference Manual 7
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. Note that these are really only suggestions, and it's up
to the application to make use of them.
void vrl_WorldSetMovementMode(int n);
int vrl_WorldGetMovementMode(void);
void vrl_WorldToggleMovementMode(void);
void vrl_WorldSetMovestep(vrl_Scalar distance);
vrl_Scalar vrl_WorldGetMovestep(void);
void vrl_WorldSetTurnstep(vrl_Angle angle);
vrl_Angle vrl_WorldGetTurnstep(void);
There's a flag, stored in the world data structure, which
indicates whether or not the world is being rendered
stereoscopically; the following routines access that flag:
void vrl_WorldSetStereo(int n);
int vrl_WorldGetStereo(void);
void vrl_WorldToggleStereo(void);
The world data structure also stores a pointer to the stereo
configuration being used. That pointer can be accessed using the
following routines:
vrl_WorldSetStereoConfiguration(conf);
vrl_StereoConfiguration *vrl_WorldGetStereoConfiguration(void);
AVRIL Technical Reference Manual 8
In addition to the standard, "cyclopean" camera, there are
cameras in the world for the left and right eyes. The functions
to access them are:
void vrl_WorldSetLeftCamera(cam);
vrl_Camera *vrl_WorldGetLeftCamera(void);
void vrl_WorldSetRightCamera(cam);
vrl_Camera *vrl_WorldGetRightCamera(void);
Finally, additional aspects of the virtual world such as the
ambient light level and the "scale factor" (the number of real-
world millimeters per unit of distance in the virtual world) can
be set and queried using the following functions:
void vrl_WorldSetAmbient(vrl_Factor ambient);
vrl_Factor vrl_WorldGetAmbient(void);
void vrl_WorldSetScale(vrl_Scalar scale);
vrl_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
vrl_malloc() to allocate the space and vrl_ObjectInit() to
initialize it, or by simply calling vrl_ObjectCreate()). If you
use vrl_ObjectCreate(), you can optionally specify a shape for
the object to use; if you don't want to assign a shape, use NULL.
You can also destroy objects using vrl_ObjectDestroy().
vrl_Object *vrl_ObjectInit(vrl_Object *obj);
vrl_Object *vrl_ObjectCreate(vrl_Shape *shape);
void vrl_ObjectDestroy(vrl_Object *object);
You can create an exact copy of an object using the function
vrl_Object *vrl_ObjectCopy(vrl_Object *obj);
Note that the newly-created object will share all the same
properties (including the shape and surface map) as the original
and will be in the exact same location as the original; you
should probably move it. The copy will have nothing attached to
it (it doesn't inherit children from the original), and will be a
sibling of the original (sharing the same parent, if any).
Objects can be rotated around any of the axes, in any coordinate
frame, using the following function:
AVRIL Technical Reference Manual 9
void vrl_ObjectRotate(vrl_Object *obj, vrl_Angle angle, int axis,
vrl_CoordFrame frame, vrl_Object *relative_to);
The axis is one of the defined constants X, Y or Z. The frame is
one of the coordinate frames discussed earlier. If the frame is
VRL_COORD_OBJREL, then the relative_to parameter points to the
object that motion should be relative to. For example, to rotate
an object 45 degrees around the viewer's Z axis, you would make
the following call:
vrl_ObjectRotate(obj, float2angle(45), Z,
VRL_COORD_OBJREL, vrl_CameraGetObject(vrl_WorldGetCamera()));
You can also orient an object to "look" in a particular direction
using the function
void vrl_ObjectLookAt(vrl_Object *obj, vrl_Vector forward, vrl_Vector up);
The object will be rotated so that it's Z axis points along the
forward vector, and its Y axis points in the general direction of
the up vector. Note that the actual Y orientation may be
different, unless you make sure that up is perpendicular to
forward. The up and forward vectors are specified in world
coordinates, and should both be unit vectors; you may find the
vrl_VectorNormalize() function handy for this.
Translations of an object are done with the following function:
void vrl_ObjectTranslate(vrl_Object *obj, vrl_Vector v,
vrl_CoordFrame frame, vrl_Object *relative_to);
The object is moved along the vrl_Vector v in the specified
frame. The meaning of the relative_to parameter is the same as
it was for vrl_ObjectRotate().
The vrl_ObjectRotate() and vrl_ObjectTranslate() routines both
apply a rotation to the current state of the object. If you wish
to make the rotations absolute, call vrl_ObjectRotReset(obj). If
you wish to make translations absolute, call
vrl_ObjectVectorMove(obj, vrl_VectorNULL) to set the
translations to zero. These should both be done before applying
the vrl_ObjectRotate() and vrl_ObjectTranslate() functions.
The vrl_ObjectRotate() and vrl_ObjectTranslate() functions should
be used for all object rotation and translation. Some older
functions are also provided for rotating and moving objects
relative to their parent (one of the more common cases); they are
as follows:
void vrl_ObjectMove(vrl_Object *obj, vrl_Scalar x, vrl_Scalar y, vrl_Scalar z);
void vrl_ObjectRelMove(vrl_Object *obj, vrl_Scalar x, vrl_Scalar y, vrl_Scalar z);
void vrl_ObjectRotX(vrl_Object *obj, vrl_Angle angle);
AVRIL Technical Reference Manual 10
void vrl_ObjectRotY(vrl_Object *obj, vrl_Angle angle);
void vrl_ObjectRotZ(vrl_Object *obj, vrl_Angle angle);
void vrl_ObjectRotVector(vrl_Object *obj, vrl_Angle angle, vrl_Vector vector);
void vrl_ObjectRotReset(vrl_Object *obj);
void vrl_ObjectVectorMove(vrl_Object *obj, vrl_Vector v);
void vrl_ObjectVectorRelMove(vrl_Object *obj, vrl_Vector v);
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:
vrl_Scalar vrl_ObjectGetWorldX(vrl_Object *object);
vrl_Scalar vrl_ObjectGetWorldY(vrl_Object *object);
vrl_Scalar vrl_ObjectGetWorldZ(vrl_Object *object);
void vrl_ObjectGetWorldLocation(vrl_Object *object, vrl_Vector v);
vrl_Scalar vrl_ObjectGetRelativeX(vrl_Object *object);
vrl_Scalar vrl_ObjectGetRelativeY(vrl_Object *object);
vrl_Scalar vrl_ObjectGetRelativeZ(vrl_Object *object);
void vrl_ObjectGetRelativeLocation(vrl_Object *object, vrl_Vector v);
The World versions of the functions return the absolute world
coordinates; the Relative versions return the coordinates
relative to the object's parent.
The rotation angles of objects, either relative to the world or
to their parent, can be obtained using the following routines:
void vrl_ObjectGetWorldRotations(vrl_Object *object,
vrl_Angle *rx, vrl_Angle *ry, vrl_Angle *rz);
void vrl_ObjectGetRelativeRotations(vrl_Object *object,
vrl_Angle *rx, vrl_Angle *ry, vrl_Angle *rz);
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, vrl_Vector v);
void vrl_ObjectGetRightVector(vrl_Object *object, vrl_Vector v);
void vrl_ObjectGetUpVector(vrl_Object *object, vrl_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; you can also find
out the "parent" of the object:
vrl_Object *vrl_ObjectAttach(vrl_Object *obj, vrl_Object *newparent);
vrl_Object *vrl_ObjectDetach(vrl_Object *obj);
vrl_Object *vrl_ObjectGetParent(vrl_Object *obj);
AVRIL Technical Reference Manual 11
The vrl_ObjectAttach() and vrl_ObjectDetach() functions return a
pointer to the object's previous parent if any. Note that
movement and rotation in the VRL_COORD_PARENT system (including
that performed using vrl_ObjectRotX() and other similar
functions) is carried out relative to the object's parent. In
other words, if the object is attached to another object, its
location and orientation will depend on that of its parent; if
the parent moves, the child will move with it. However, if the
child moves the parent will stay where it is.
You can find the "root" of an object tree using the following
function:
vrl_Object *vrl_ObjectFindRoot(vrl_Object *obj);
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. All parent objects are processed before
their descendants.
The distance between two objects can be found using
vrl_Scalar vrl_ObjectComputeDistance(vrl_Object *obj1, vrl_Object *obj2);
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_Surfacemap *map);
vrl_Surfacemap *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, as described later in
the section on Layers. Note that layer zero is always visible;
in effect, an object whose layer is zero will appear on all
layers. 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);
AVRIL Technical Reference Manual 12
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 supports the idea of "fixed" objects; you can mark an
object as fixed or movable, and find out its current status,
using the following functions:
void vrl_ObjectMakeFixed(vrl_Object *object);
void vrl_ObjectMakeMovable(vrl_Object *object);
vrl_Boolean vrl_ObjectIsFixed(vrl_Object *object);
You can also find out the boundaries of an object (in world
coordinates) using the functions
void vrl_ObjectGetMinbounds(vrl_Object *object, vrl_Vector v);
void vrl_ObjectGetMaxbounds(vrl_Object *object, vrl_Vector v);
The vectors returned by these two functions can be thought of as
the opposite corners of the object's bounding box.
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);
If you want automatic representation selection to be re-enabled
for the object, just use vrl_ObjectSetRep(obj, NULL). See the
section on Representations for details.
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:
vrl_Object *vrl_ObjectUpdate(vrl_Object *object);
You should generally only call this once per frame, on the object
tree for the current world; the macro vrl_WorldUpdate() can be
used to do this more concisely.
A vrl_Object can have several other properties associated
with it. These include a name, a function, and some application-
specific data. The function associated with an object gets
called whenever the object is processed during the tree-walking
that vrl_ObjectUpdate() performs. The following routines allow
you to get and set these additional properties:
AVRIL Technical Reference Manual 13
void vrl_ObjectSetName(vrl_Object *obj, char *str);
char *vrl_ObjectGetName(vrl_Object *obj);
void vrl_ObjectSetFunction(vrl_Object *obj, vrl_ObjectFunction fn);
vrl_ObjectFunction *vrl_ObjectGetFunction(vrl_Object *obj);
void vrl_ObjectSetApplicationData(vrl_Object *obj, void *data);
void *vrl_ObjectGetApplicationData(vrl_Object *obj);
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. The syntax for PLG files is described in Appendix C.
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! To re-scale a shape, or shift all the vertices
in the shape relative to the shape's origin point, use the
following functions:
void vrl_ShapeRescale(vrl_Shape *shape, float sx, float sy, float sz);
void vrl_ShapeOffset(vrl_Shape *shape, vrl_Scalar tx, vrl_Scalar ty, vrl_Scalar tz);
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);
The vrl_ShapeRescale() and vrl_ShapeOffset() routines call
vrl_ShapeUpdate() automatically, so you don't have to do it
again; it's only when you move individual vertices that you need
to worry about it.
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, or a pointer to a new surfacemap for
a shape set, by calling the functions
vrl_Surfacemap *vrl_ShapeGetSurfacemap(vrl_Shape *shape);
void vrl_ShapeSetSurfacemap(vrl_Shape *shape, vrl_Surfacemap *map);
To get a pointer to the representation of a shape that will be
used at a given on-screen size, use following function:
vrl_Rep *vrl_ShapeGetRep(vrl_Shape *shape, vrl_Scalar size);
To add an additional representation to an existing shape, use the
function
AVRIL Technical Reference Manual 14
void vrl_ShapeAddRep(vrl_Shape *shape, vrl_Rep *rep, vrl_Scalar size);
The size parameter gives the apparent on-screen size in pixels at
which the shape should be used. You can find out how many
representations a shape has using the function
int vrl_ShapeCountReps(vrl_Shape *shape);
A shape's name can be set or obtained using the functions
void vrl_ShapeSetName(vrl_Shape *shape, char *str);
char *vrl_ShapeGetName(vrl_Shape *shape);
Shapes are kept internally in a singly-linked list; if you need
to iterate through the list, the following two functions can be
used
vrl_Shape *vrl_ShapeGetList(void);
vrl_Shape *vrl_ShapeGetNext(vrl_Shape *shape)
You can also locate a shape by name using
vrl_Shape *vrl_ShapeFind(char *name);
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 vrl_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 called once 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.
The other approach is to iterate through the linked list of
representations using the functions
vrl_Rep *vrl_ShapeGetFirstRep(vrl_Shape *shape);
vrl_Rep *vrl_RepGetNext(vrl_Rep *rep);
You can set and get a vrl_Rep's sorting type, find out the
approximate size (in pixels) at which a rep becomes effective, as
AVRIL Technical Reference Manual 15
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)(vrl_Vector *vertex));
void vrl_RepTraverseFacets(vrl_Rep *rep, int (*function)(vrl_Facet *facet));
If you need to get or set the values of a vertex's coordinates,
you can use the functions
void vrl_RepGetVertex(vrl_Rep *rep, int n, vrl_Vector v);
void vrl_RepSetVertex(vrl_Rep *rep, int n, vrl_Vector v);
Be careful when using vrl_RepSetVertex(); it's easy to move a
vertex and create non-planar or non-convex facets, which confuse
the renderer. You can only move vertices safely if you know the
vrl_Rep is composed entirely of triangles, since by their nature
triangles are always planar and convex. In any case, be sure to
call vrl_ShapeUpdate() after moving any vertices.
To support Gouraud shading, functions are provided to compute
vertex normals by averaging the normal vectors of all the polys
which share the vertex in a vrl_Rep or a vrl_Shape:
void vrl_RepComputeVertexNormals(vrl_Rep *rep);
void vrl_ShapeComputeVertexNormals(vrl_Shape *shape);
Make sure that the polygons normals have already been computed
(by the vrl_ShapeUpdate() function) before calling
vrl_RepComputeVertexNormals().
There's also a function for computing edge information for a
representation; currently, this information is not used:
void vrl_RepBuildEdges(vrl_Rep *rep);
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 job of
the graphics pipeline is to turn facets into polygons.
AVRIL Technical Reference Manual 16
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 flag
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);
vrl_Vector *vrl_FacetGetVertex(vrl_Rep *rep, vrl_Facet *facet, int n);
vrl_Facets can be identified by an ID number. The ID number for
a vrl_Facet can be set and read, and a vrl_Facet with a
particular ID can be found, using the following functions:
void vrl_FacetSetId(vrl_Facet *facet, vrl_unsigned16bit n);
vrl_unsigned16bit vrl_FacetGetId(vrl_Facet *facet);
vrl_Facet *vrl_RepFindFacet(vrl_Rep *rep, vrl_unsigned16bit id);
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_SIMPLE (no lighting, just a fixed
color), SURF_FLAT (for flat shading), SURF_GOURAUD (for Gouraud
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);
vrl_Surface *vrl_SurfaceCreate(vrl_unsigned8bit hue);
void vrl_SurfaceSetType(vrl_Surface *surf, vrl_LightingType type);
vrl_LightingType vrl_SurfaceGetType(vrl_Surface *surf);
AVRIL Technical Reference Manual 17
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.
Future versions of AVRIL may support specular shading; the
following two functions allow you to set and get the specular
exponent value, which controls the "sharpness" of the specular
highlights:
void vrl_SurfaceSetExponent(vrl_Surface *surf, vrl_Exponent exp);
vrl_Exponent vrl_SurfaceGetExponent(vrl_Surface *surf);
For backwards compatibility with REND386, AVRIL includes
functions to convert a 16-bit REND386 surface descriptor into a
vrl_Surface, and vice-versa:
vrl_Surface *vrl_SurfaceFromDesc(vrl_unsigned16bit desc, vrl_Surface *surf);
vrl_unsigned16bit vrl_SurfaceToDesc(vrl_Surface *surf);
As surfaces are created, they are added to a list; the following
functions allow you to iterate through the list:
vrl_Surface *vrl_SurfaceGetList(void);
vrl_Surface *vrl_SurfaceGetNext(vrl_Surface *surf);
Surface Maps
Surface maps contain an array of pointers to surfaces; you
can create a surface map with room for a particular number of
entries, find out how many entries the map contains, and access
entries within a map, using the following routines:
vrl_Surfacemap *vrl_SurfacemapCreate(int n);
int vrl_SurfacemapCountEntries(vrl_Surfacemap *map);
vrl_Surface *vrl_SurfacemapGetSurface(vrl_Surfacemap *map, int surfnum);
vrl_Surface *vrl_SurfacemapSetSurface(vrl_Surfacemap *map, int surfnum,
vrl_Surface *surface);
As surfacemaps are defined, they are added to a list; the list
can be iterated through using the following functions:
vrl_Surfacemap *vrl_SurfacemapGetList(void);
vrl_Surfacemap *vrl_SurfacemapGetNext(vrl_Surfacemap *map);
AVRIL Technical Reference Manual 18
Lights
Lights in AVRIL have a number of properties; they can be on
or off, they can have an intensity, they can have a "type", 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 and destroyed using the following functions:
vrl_Light *vrl_LightInit(vrl_Light *light);
vrl_Light *vrl_LightCreate(void);
void vrl_LightDestroy(vrl_Light *light);
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);
vrl_Boolean vrl_LightIsOn(vrl_Light *light);
void vrl_LightSetIntensity(vrl_Light *light, vrl_Factor inten);
vrl_Factor vrl_LightGetIntensity(vrl_Light *light);
Notice that the intensity values are vrl_Factors; they should
never be less than zero or greater than VRL_UNITY.
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);
AVRIL Technical Reference Manual 19
void vrl_LightDisAssociate(vrl_Light *light);
vrl_Object *vrl_LightGetObject(vrl_Light *light);
Many of the routines that were used for objects earlier have
counterparts that are used for light sources; they're implemented
as macros that just perform the operations on the object with
which the light source is associated.
vrl_LightMove(light, x, y, z);
vrl_LightRelMove(light, x, y, z);
vrl_LightVectorMove(light, v);
vrl_LightVectorRelMove(light, v);
vrl_LightRotX(light, angle);
vrl_LightRotY(light, angle);
vrl_LightRotZ(light, angle);
vrl_LightRotVector(light, angle, vector);
vrl_LightRotReset(light);
vrl_LightRotate(light, angle, axis, frame, relative_to);
vrl_LightTranslate(light, v, axis, frame, relative_to);
vrl_LightLookAt(light, forward, up);
vrl_LightAttach(obj, newparent);
vrl_LightDetach(obj);
vrl_LightGetWorldX(light);
vrl_LightGetWorldY(light);
vrl_LightGetWorldZ(light);
vrl_LightGetWorldLocation(light, v);
vrl_LightGetWorldRotations(light, rx, ry, rz);
vrl_LightGetRelativeX(light);
vrl_LightGetRelativeY(light);
vrl_LightGetRelativeZ(light);
vrl_LightGetRelativeLocation(light, v);
vrl_LightGetRelativeRotations(light, rx, ry, rz);
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.
Lights can have other attributes as well, just as objects
can; specifically, they can have a name and some application-
specific data, which are accessed using the following functions:
void vrl_LightSetName(vrl_Light *light, char *str);
char *vrl_LightGetName(vrl_Light *light);
AVRIL Technical Reference Manual 20
void vrl_LightSetApplicationData(vrl_Light *light, void *data);
void *vrl_LightGetApplicationData(vrl_Light *light);
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 associated with an
object; there's no such thing as an "ambient" camera. Cameras
are initialized and destroyed just like objects or light sources:
vrl_Camera *vrl_CameraInit(vrl_Camera *camera);
vrl_Camera *vrl_CameraCreate(void);
void vrl_CameraDestroy(vrl_Camera *camera);
Cameras have only a few properties that are important; in
particular, a zoom factor, an aspect ratio, and hither and yon
clipping plane distances. 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, vrl_Scalar hither);
vrl_Scalar vrl_CameraGetHither(vrl_Camera *camera);
void vrl_CameraSetYon(vrl_Camera *camera, vrl_Scalar yon);
vrl_Scalar vrl_CameraGetYon(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.
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 "yon" distance is like an
invisible wall; any object entirely on the far side of the wall
will not be seen.
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);
AVRIL Technical Reference Manual 21
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_CameraVectorMove(camera, v);
vrl_CameraVectorRelMove(camera, v);
vrl_CameraRotX(camera, angle);
vrl_CameraRotY(camera, angle);
vrl_CameraRotZ(camera, angle);
vrl_CameraRotVector(camera, angle, vector);
vrl_CameraRotReset(camera);
vrl_CameraRotate(camera, angle, axis, frame, relative_to);
vrl_CameraLookAt(camera, forward, up);
vrl_CameraTranslate(camera, v, axis, frame, relative_to);
vrl_CameraAttach(obj, newparent);
vrl_CameraDetach(obj);
vrl_CameraGetWorldX(camera);
vrl_CameraGetWorldY(camera);
vrl_CameraGetWorldZ(camera);
vrl_CameraGetWorldLocation(camera, v);
vrl_CameraGetWorldRotations(camera, rx, ry, rz);
vrl_CameraGetRelativeX(camera);
vrl_CameraGetRelativeY(camera);
vrl_CameraGetRelativeZ(camera);
vrl_CameraGetRelativeLocation(camera, v);
vrl_CameraGetRelativeRotations(camera, rx, ry, rz);
Camera can have other attributes as well, just as objects and
lights can; specifically, they can have a name and some
application-specific data, which are accessed using the following
functions:
void vrl_CameraSetName(vrl_Camera *camera, char *str);
char *vrl_CameraGetName(vrl_Camera *camera);
void vrl_CameraSetApplicationData(vrl_Camera *camera, void *data);
void *vrl_CameraGetApplicationData(vrl_Camera *camera);
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)
AVRIL Technical Reference Manual 22
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);
void vrl_LayerAllOff(void);
The last two routines, vrl_LayerAllOn() and vrl_LayerAllOff(),
turn all the layers on or off at once. By default, all layers
are on.
Stereoscopic Rendering
Stereoscopic rendering consists of generating two images, one for
the left eye and one for the right. The world data structure
maintains a flag indicating whether stereoscopic rendering should
be used, as well as pointers to the left-eye and right-eye
cameras and the stereo configuration information; see the section
on Worlds for more information.
There are a number of ways of transferring the separate left and
right images to the viewer's eyes; among them are anaglyph
techniques that use red and blue filters, field sequential
techniques that use shutter glasses, optical techniques that use
a split screen, alternate-scanline techniques, and systems that
use more than one VGA card.
In addition, there are systems that provide stereoscopic depth
without using separate images: the patented "Chromadepth"
technique used in some comic books and laser light shows is one,
and SIRDS (Single-Image Random Dot Stereograms, or "Magic Eye"
pictures) are another.
In AVRIL, information about the current stereo configuration is
stored in a structure of type vrl_StereoConfiguration. Such a
structure can be created (or initialized) using the following
functions:
vrl_StereoConfiguration *vrl_StereoCreateConfiguration(void);
vrl_StereoConfiguration *vrl_StereoInitConfiguration(vrl_StereoConfiguration *conf);
The type of stereoscopic viewing is defined by a constant; the
possible values are listed in Appendix I. Setting and getting
the type of stereoscopic viewing is done using the following
functions:
void vrl_StereoSetType(vrl_StereoConfiguration *conf, vrl_StereoType st_type);
AVRIL Technical Reference Manual 23
vrl_StereoType vrl_StereoGetType(vrl_StereoConfiguration *conf);
Some systems, like Chromadepth and SIRDS, use a single "eye";
most others use two eyes. There's a function for determining the
number of "eyes" used by the current stereo type:
int vrl_StereoGetNeyes(vrl_StereoConfiguration *conf);
You can alter the amount by which each eye's image is shifted on
the screen using the following functions:
void vrl_StereoSetLeftEyeShift(vrl_StereoConfigurtion *conf, int shift);
int vrl_StereoGetLeftEyeShift(vrl_StereoConfigurtion *conf);
void vrl_StereoSetRightEyeShift(vrl_StereoConfigurtion *conf, int shift);
int vrl_StereoGetRightEyeShift(vrl_StereoConfigurtion *conf);
This shift is necessary, due to differences between HMDs. The
total shift (the sum of that which is computed and that which is
explicitly set by the functions above) can be obtained using the
following functions:
int vrl_StereoGetTotalLeftShift(vrl_StereoConfiguration *conf);
int vrl_StereoGetTotalRightShift(vrl_StereoConfiguration *conf);
It may be necessary, because of the way an HMD's optics are
constructed, to rotate the virtual eyes inwards or outwards.
This is done using the following functions:
void vrl_StereoSetLeftEyeRotation(vrl_StereoConfiguration *conf, vrl_Angle rot);
vrl_Angle vrl_StereoGetLeftEyeRotation(vrl_StereoConfiguration *conf);
void vrl_StereoSetRightEyeRotation(vrl_StereoConfiguration *conf, vrl_Angle rot);
vrl_Angle vrl_StereoGetRightEyeRotation(vrl_StereoConfiguration *conf);
The eye spacing and convergence distance (both in world units)
can be manipulated using the following functions:
void vrl_StereoSetEyespacing(vrl_StereoConfiguration *conf, float spacing);
float vrl_StereoGetEyespacing(vrl_StereoConfiguration *conf);
void vrl_StereoSetConvergence(vrl_StereoConfiguration *conf, float conv);
float vrl_StereoGetConvergence(vrl_StereoConfiguration *conf);
By using various values for eyespacing and convergence distance,
a strong or weaker stereo effect can be achieved.
The Chromadepth technique is unusual, in that it uses distance to
compute a color between extreme red and extreme blue. Since only
a finite number of palette entries is available, it's necessary
to optimize their use over a range of distance; this range is
represented by the "ChromaNear" and "ChromaFar" values, which can
be altered using the following functions:
void vrl_StereoSetChromaNear(vrl_StereoConfiguration *conf, vrl_Scalar val);
AVRIL Technical Reference Manual 24
vrl_Scalar vrl_StereoGetChromaNear(vrl_StereoConfiguration *conf);
void vrl_StereoSetChromaFar(vrl_StereoConfiguration *conf, vrl_Scalar val);
vrl_Scalar vrl_StereoGetChromaFar(vrl_StereoConfiguration *conf);
After altering any of the values in a vrl_StereoConfiguration
structure, the following function should be called:
int vrl_StereoConfigure(vrl_StereoConfiguration *conf);
In order for the system to render a scene stereoscopically, both
the left-eye and right-eye cameras must exist in the current
world; to create them initially, use the following function:
int vrl_StereoSetup(void); /* creates a pair of cameras */
There are a number of routines in the scan-conversion module
(i.e., the display driver) that relate to stereoscopic viewing;
they are described in the section on display drivers.
For more information about stereoscopic rendering and its
implementation, see the source code in system.c (which makes all
the appropriate calls).
File I/O Routines
AVRIL supports the PLG file format, the FIG file format, and
most of the WLD file format; these formats are described in the
Appendices. 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() function 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. While
reading a WLD file, statements may be encountered which the WLD
parser doesn't recognize; these are passed to an application-
defined function called
void vrl_ReadWLDfeature(int argc, char *argv[], char *rawtext);
The argc and argv[] parameters are just like the ones passed to a
main() function in C; the rawtext parameter is the original input
line.
AVRIL Technical Reference Manual 25
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 segment names (segnames)
that are assigned in the FIG file will be added to the current
world's list of objects as rootname.segname.
All the routines listed above assume you've already opened
the file; there are also routines for loading objects, figures
and entire worlds from files, given the filename:
vrl_Object *vrl_ObjectLoadPLGfile(char *filename);
vrl_Object *vrl_ObjectLoadFIGfile(char *filename);
int *vrl_LoadWLDfile(char *filename);
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_SetReadFIGpartArray(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.
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 filename fixups should use. The second routine
is used to generate a full filename, with the current loadpath
prepended. Note that filenames beginning with '/' or '\' are not
modified. Also note that vrl_FileFixupFilename() returns a
pointer to an internal buffer, which will be rewritten on the
next call to vrl_FileFixupFilename(). If you really need to keep
the fixed-up filename around, you should strcpy() it to another
buffer or strdup() it.
AVRIL Technical Reference Manual 26
System and Application routines
These routines are described in detail in the Tutorial, but
here's a quick summary:
vrl_Boolean vrl_SystemStartup(void);
void vrl_SystemRun(void);
vrl_RenderStatus *vrl_SystemRender(vrl_Object *list);
vrl_Time vrl_SystemGetRenderTime(void);
vrl_Time vrl_SystemGetFrameRate(void);
void vrl_SystemCommandLine(int argc, char *argv[]);
void vrl_SystemRequestRefresh(void);
vrl_Boolean vrl_SystemQueryRefresh(void);
void vrl_SystemStartRunning(void);
void vrl_SystemStopRunning(void);
vrl_Boolean vrl_SystemIsRunning(void);
void vrl_ApplicationDrawUnder(void);
void vrl_ApplicationDrawOver(vrl_RenderStatus *stat);
void vrl_ApplicationInit(void);
void vrl_ApplicationKey(vrl_unsigned16bit c);
void vrl_ApplicationMouseUp(int x, int y, unsigned int buttons);
These are not really part of AVRIL's "guts", since you don't need
to use anything in system.c (which is the only module that knows
about the vrl_Application functions).
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[]);
int vrl_UserInterfaceMenuDispatch(char *text[], int (**funcs)(void));
vrl_unsigned16bit 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. Either can
be NULL, indicating that you don't care about the values.
The vrl_UserInterfacePopMsg() routine displays a one-line
text message. The vrl_UserInterfacePopText() routine puts up a
AVRIL Technical Reference Manual 27
multi-line message; the array of string pointers has to have a
NULL pointer 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_UserInterfaceMenuDispatch() routine is similar to
vrl_UserInterfacePopMenu(), but it takes an array of pointers to
functions as a second parameter. When an item in the menu is
selected, the corresponding function is called. No parameters
are passed to that function.
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_UserInterfaceDrawCompass(vrl_Camera *camera, int x, int y, int armlen);
void vrl_UserInterfaceDropText(int x, int y, vrl_Color, char *text);
In vrl_UserInterfaceDrawCompass(), 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's viewpoint.
The vrl_UserInterfaceDropText() routine displays the text
message at the given screen coordinates in the given color, with
a black (i.e., color 0) drop shadow.
AVRIL Technical Reference Manual 28
Tasks
The pseudo-tasking mechanism is described in the Tutorial.
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(), which is
normally done in vrl_SystemRun(). 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.
vrl_Boolean vrl_TaskCreate(void (*function)(void), void *data, vrl_Time period);
void vrl_TaskRun(void);
void *vrl_TaskGetData(void);
vrl_Time vrl_TaskGetElapsed(void);
vrl_Time 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(vrl_Scalar width, vrl_Scalar height, vrl_Scalar depth,
vrl_Surfacemap *map);
vrl_Shape *vrl_PrimitiveCone(vrl_Scalar radius, vrl_Scalar height, int nsides,
vrl_Surfacemap *map);
vrl_Shape *vrl_PrimitiveCylinder(vrl_Scalar bottom_radius, vrl_Scalar top_radius,
vrl_Scalar height, int nsides, vrl_Surfacemap *map);
vrl_Shape *vrl_PrimitivePrism(vrl_Scalar width, vrl_Scalar height, vrl_Scalar depth,
vrl_Surfacemap *map);
vrl_Shape *vrl_PrimitiveSphere(vrl_Scalar radius, int vsides, int hsides,
vrl_Surfacemap *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. You can
use vrl_ShapeOffset() to change these choices if you wish.
AVRIL Technical Reference Manual 29
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.
vrl_Boolean vrl_RenderInit(int maxvert, int maxf, int maxobjs, int maxlights,
unsigned int mempoolsize);
void vrl_RenderQuit(void);
The routines in system.c normally handle the calling of
vrl_RenderInit(), and the setting up of an atexit() function for
vrl_RenderQuit().
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(vrl_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() or vrl_WorldUpdate()).
void vrl_RenderHorizon(void);
vrl_Status *vrl_RenderObjlist(vrl_Object *objects);
The vrl_RenderObjlist() function returns a pointer to a status
struct, which is described in the Tutorial.
Since the renderer can highlight objects and draw them in
wireframe, there are functions for setting and getting the colors
used for highlighting and wireframe:
void vrl_RenderSetWireframeColor(vrl_Color color);
vrl_Color vrl_RenderGetWireframeColor(void);
void vrl_RenderSetHighlightColor(vrl_Color color);
vrl_Color vrl_RenderGetHighlightColor(void);
You can also set and get the rendering mode:
void vrl_RenderSetDrawMode(int mode);
int vrl_RenderGetDrawMode(void);
At the moment, the only values supported for the mode are 0
(normal) and 1 (wireframe).
AVRIL Technical Reference Manual 30
For stereoscopic rendering, a horizontal shift factor is
required:
void vrl_RenderSetHorizontalShift(int npixels);
There are two functions that allow you to monitor a particular
point on the screen, render the world, and then see what objects
and facets were under the cursor (and nearest the viewer).
void vrl_RenderMonitorInit(int x, int y);
vrl_Boolean vrl_RenderMonitorRead(vrl_Object **obj, vrl_Facet **facet, int *vertnum);
The x and y values are coordinates in the current screen window
(such as those passed to vrl_ApplicationMouseUp()). The obj
pointer (if not NULL) gets set to point to the object the cursor
was over; similarly, the facet pointer (if not NULL) gets set to
point to the facet the cursor was over. The vertnum pointer is
not currently used. If nothing was under the cursor,
vrl_RenderMonitorRead() returns zero. Remember that you must
call vrl_RenderObjlist() between the call to
vrl_RenderMonitorInit() and vrl_RenderMonitorRead(); typically,
you would call it as vrl_RenderObjlist(NULL) to just re-render
the last object list that was used.
If you need to find out where on the screen a specific
vertex will be drawn, you can use the following function:
void vrl_TransformVertexToScreen(vrl_ScreenCoord *x, vrl_ScreenCoord *y, vrl_Object *obj,
vrl_Vector vertex);
The vertex is assumed to be in the object's coordinate system.
The vertex is transformed, projected, scaled and shifted and the
screen coordinates are stored in x and y. Note that the values
are of type vrl_ScreenCoord, so you should right shift the
results by VRL_SCREEN_FRACT_BITS. The vrl_ObjectToScreen()
function uses information computed during the most recent call to
vrl_RenderBegin(), so the values will not reflect any changes
made since then.
The Timer
AVRIL has a set of routines which deal with the timer; these
will vary from one platform to another, but they should all
provide the same high-level interface to application software.
vrl_Boolean vrl_TimerInit(void);
void vrl_TimerQuit(void);
vrl_Time vrl_TimerRead(void);
vrl_Time vrl_TimerGetTickRate(void);
void vrl_TimerDelay(vrl_Time milliseconds);
AVRIL Technical Reference Manual 31
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). It is expected that all future versions of
AVRIL will use 1000 ticks per second, so that each tick is one
millisecond; however, to be on the safe side, always use
vrl_TimerGetTickRate().
The Mouse
Like the timer routines, the mouse routines will differ from
platform to platform, but the high-level interface should remain
the same.
vrl_Boolean vrl_MouseInit(void);
void vrl_MouseQuit(void);
vrl_Boolean vrl_MouseReset(void);
vrl_Boolean vrl_MouseRead(int *x, int *y, unsigned int *buttons);
void vrl_MouseSetUsage(int u);
int vrl_MouseGetUsage(void);
void vrl_MouseSetPointer(void *u);
void *vrl_MouseGetPointer(void);
These routines let you initialize and read the mouse. Since the
mouse can be used either as a screen-oriented pointer or as a 6D
input device (see the Devices section), there has to be some way
of toggling between those two functions. That's what the
vrl_MouseSetUsage() and vrl_MouseGetUsage() functions are for; a
non-zero value means the mouse is a 6D device, a zero value means
it's a screen pointer.
When the mouse is a 6D device, it's useful to be able to
obtain a pointer to the vrl_Device which is using it; similarly,
the vrl_Device function (described later) must be able to set
that pointer. That's what the vrl_MouseSetPointer() and
vrl_MouseGetPointer() calls do; they set and get a pointer to the
vrl_Device that's using the mouse for 6D input.
The Keyboard
Like the timer and the mouse, the keyboard routines will be
implemented very differently on different platforms, but they
should provide a consistent high-level interface.
vrl_Boolean 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
AVRIL Technical Reference Manual 32
for definitions of special keys (like arrows, function keys,
etc).
Memory Allocation Routines
AVRIL has three functions which are (at the moment) just wrappers
around the standard malloc(), calloc() and free() functions. You
should always use these functions rather than the system
equivalents, for compatability with future versions of AVRIL.
void *vrl_malloc(unsigned int nbytes);
void *vrl_calloc(unsigned int nitems, unsigned int item_size);
void vrl_free(void *ptr);
Raster Routines
AVRIL uses the notion of a "raster", a rectangular array of pixel
values. In most cases, all rendering is done into a raster,
which may or may not correspond to an actual physical screen; in
cases where it doesn't, it's necessary to copy ("blit") the
raster to the display.
Each raster has a height, a width, a depth (number of bits per
pixel) a window (the rectangular region within the raster into
which rendering is done) and a "rowbytes" value (the number of
bytes per horizontal row of pixels).
The following routines are used to deal with rasters:
vrl_Raster *vrl_RasterCreate(vrl_ScreenPos width, vrl_ScreenPos height,
vrl_unsigned16bit depth);
void vrl_RasterDestroy(vrl_Raster *raster);
void vrl_RasterSetWindow(vrl_Raster *raster,
vrl_ScreenPos left, vrl_ScreenPos top, vrl_ScreenPos right, vrl_ScreenPos bottom);
void vrl_RasterGetWindow(vrl_Raster *raster, vrl_ScreenPos *left, vrl_ScreenPos *top,
vrl_ScreenPos *right, vrl_ScreenPos *bottom);
vrl_ScreenPos vrl_RasterGetHeight(vrl_Raster *r);
vrl_ScreenPos vrl_RasterGetWidth(vrl_Raster *r);
vrl_ScreenPos vrl_RasterGetDepth(vrl_Raster *r);
vrl_ScreenPos vrl_RasterGetRowbytes(vrl_Raster *r);
void vrl_RasterSetRowbytes(vrl_Raster *r, vrl_ScreenPos n);
The reason for setting the number of bytes per row of pixels has
to do with alternate scan-line encoding; if you render the left
and right images into the left and right halves of a 640-pixel
wide screen, and then set the rowbytes value to 320, you wind up
with a raster that has the left-eye image on the even scanlines
and the right-eye image on the odd scanlines.
You can read scanlines from a raster into a buffer, or write them
from a buffer into the raster. You can also obtain a pointer to
AVRIL Technical Reference Manual 33
the actual data for the raster (i.e. the raw array of pixel
values).
void vrl_RasterReadScanline(vrl_Raster *r, int n, unsigned char *buff);
void vrl_RasterWriteScanline(vrl_Raster *r, int n, unsigned char *buff);
unsigned char *vrl_RasterGetData(vrl_Raster *r);
Even though you can obtain a pointer to the actual data, it's
generally a bad idea to do anything with it.
Palettes and Huemaps
AVRIL makes use of a "palette", a collection of (up to) 256
colors. Each of those colors has three 8-bit components -- one
for red, one for green and one for blue. On the PC's VGA card,
only 6 bits are actually used for each component.
The vrl_Color values that AVRIL uses are (in a paletted
implementation) used to index the palette to select an actual
color. In order to do shading of facets and vertices, AVRIL
divides the palette into a number of "hues", with a number of
shades for each hue. By default, the first 16 entries of the
256-color palette are simple non-shaded colors (for use in menus
and overlaid text); the remaining 240 colors are treated as 15
hues with 16 shades each.
However, this is not etched in stone. AVRIL supports the use of
a "hue map", which relates a hue index to a start color in the
palette and a count of the number of shades. For example, by
using the hue map, you could choose to have 64 shades of flesh
tone (instead of 16) in order to represent human beings more
accurately.
A hue is represented by a vrl_Hue type, and a palette by a
vrl_Palette type. Palettes have a flag that indicates that
they've been changed; this is important, since it forces the
system to update the physical palette stored in the video
hardware.
The following functions will initialize a palette, read a palette
and huemap data from a file, get and set individual entries in
the palette, get a pointer to a palette's huemap, and read and
check the "changed" status of the palette:
void vrl_PaletteInit(vrl_Palette *pal);
int vrl_PaletteRead(FILE *in, vrl_Palette *pal);
vrl_Color vrl_PaletteGetEntry(vrl_Palette *pal, int n);
void vrl_PaletteSetEntry(vrl_Palette *pal, int n, vrl_Color color);
vrl_Boolean vrl_PaletteHasChanged(vrl_Palette *pal);
void vrl_PaletteSetChanged(vrl_Palette *pal, vrl_Boolean flag);
vrl_Hue *vrl_PaletteGetHuemap(vrl_Palette *pal);
AVRIL Technical Reference Manual 34
Each world structure has a palette built into it; you can obtain
a pointer to it using the following function:
vrl_Palette *vrl_WorldGetPalette(void);
Bear in mind that the palettes we're discussing here are
independent of the actual, physical palette that's maintained by
the video hardware; see the section on Video routines for more
information about the hardware palette.
Video Routines
AVRIL takes care of all the "high-level" functions required
of a VR library, including the handling of input devices, reading
files, maintaining data structures, doing transforms and lighting
calculations and so on. AVRIL makes calls to lower-level
routines to do things like accessing the display device; in fact,
it has two separate levels of interface to the display hardware.
The lowest-level display interface is the video driver. The
video driver is responsible for such things as entering and
exiting graphics mode, hiding and displaying the cursor and
loading the hardware palette. Video drivers are easy to replace,
and you can write your own to support whatever graphics modes you
wish to use.
Internally, the video driver is just a function; see
Appendix G for information about how to write one. The
application-visible functions that access that driver are
described here.
void vrl_VideoSetDriver(vrl_VideoDriverFunction driver);
This function sets the video driver function to use; all
subsequent calls to the vrl_Video family of routines will
ultimately be passed to the specified function. See Appendix G
for more details.
int vrl_VideoGetVersion(void);
char *vrl_VideoGetDescription(void);
These two functions allow you to find out which version of the
video driver specification the currently-selected driver
supports, and to get a textual description of the driver.
int vrl_VideoSetup(int mode);
int vrl_VideoGetMode(void);
The vrl_VideoSetup() function puts the system into graphics mode;
the mode parameter specifies the graphics submode to use (for
drivers that support multiple graphics modes). The meaning of
the mode parameter is specific to the driver being used; 0x1234
AVRIL Technical Reference Manual 35
may mean completely different things to different video drivers.
The function returns a non-zero value if it was unable to enter
the specified graphics mode. The vrl_VideoGetMode() function
returns the current graphics mode; this may or may not be the one
that was requested.
void vrl_VideoShutdown(void);
This function shuts down the video subsystem and returns the
hardware to whatever mode it was in prior to the last call to
vrl_VideoSetup().
The video subsystem provides a vrl_Raster describing the
actual physical framebuffer. To obtain a pointer to the raster
(from which information such as the height, width and depth of
the video adapter may be obtained), use the following function:
vrl_Raster *vrl_VideoGetRaster(void);
Many video adapters support multiple pages. AVRIL has the notion
of a "current drawing page" (to which all output is written) and
a "current view page" (the one that's being displayed on the
physical screen). The following functions allow you to find out
how many pages the adapter has, and to set and query the value of
each of those pages:
int vrl_VideoGetNpages(void);
void vrl_VideoSetDrawPage(int page);
int vrl_VideoGetDrawPage(void);
void vrl_VideoSetViewPage(int page);
int vrl_VideoGetViewPage(void);
Most current video adapters have 8 bits per pixel, and a
"palette" of 256 colors. You can find out whether the video
adapter being used has a palette by using the function
vrl_Boolean vrl_VideoHasPalette(void);
You can read and write a range of values within the palette using
the following functions:
void vrl_VideoSetPalette(int start, int end, vrl_Palette *palette);
void vrl_VideoGetPalette(int start, int end, vrl_Palette *palette);
On some systems, you actually render to an off-screen vrl_Raster
in system memory and then copy (or "blit") the image onto the
screen. This is done by the function
void vrl_VideoBlit(vrl_Raster *raster)
AVRIL Technical Reference Manual 36
You may only want to update the physical display during the
vertical blanking interval; to monitor the vertical retrace, use
the following function:
vrl_Boolean vrl_VideoCheckRetrace(void);
The video subsystem is responsible for the on-screen cursor. The
following functions allow you to hide the cursor, show it again,
reset it to its initial state, move it and set its appearance:
void vrl_VideoCursorHide(void);
void vrl_VideoCursorShow(void);
void vrl_VideoCursorReset(void);
void vrl_VideoCursorMove(vrl_ScreenPos x, vrl_ScreenPos y);
void vrl_VideoCursorSetAppearance(void *app);
Whenever you write to the currently visible page (the viewpage)
you should first call vrl_VideoCursorHide() to prevent the mouse
from being "squashed" under a falling polygon. When you're
finished updating the display, call vrl_VideoCursorShow() 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_VideoCursorHide() and
vrl_VideoCursorShow(), so you don't need to worry about them when
you use those functions.
Note that the system keeps a count of the number of times
you've called vrl_VideoCursorHide() and vrl_VideoCursorShow();
for example, if you hide the mouse cursor twice, you have to show
it twice before it actually appears. The vrl_VideoCursorReset()
routine resets the "hidden" count.
The vrl_VideoCursorMove() routine moves the cursor to a
particular spot on the screen, and the
vrl_VideoCursorSetAppearance() routine sets a new visual
appearance for the cursor (the data that is pointed to by the app
parameter varies from driver to driver).
Display Routines
The display subsystem is the "back end" of the rendering
pipeline; it's responsible for actually drawing polygons (and
lines, and text) into a raster.
Internally, the display driver is just a function (much like
the video driver, or any of the input device drivers); see
Appendix H for information about how to write one. The
application-visible functions that access that driver are
described here.
void vrl_DisplaySetDriver(vrl_DisplayDriverFunction driver);
AVRIL Technical Reference Manual 37
This function sets the display driver function to use; all
subsequent calls to the vrl_Display family of routines will
ultimately be passed to the specified function.
int vrl_DisplayInit(vrl_Raster *raster);
void vrl_DisplayQuit(void);
These two routines are responsible for initializing and de-
initializing the display subsystem. The raster parameter
specifies a raster to use; this may be an off-screen buffer in
system memory, or the actual physical framebuffer returned by
vrl_VideoGetRaster().
You can change the raster being used; to set or query the raster,
use the following functions:
void vrl_DisplaySetRaster(vrl_Raster *r);
vrl_Raster *vrl_DisplayGetRaster(void);
You can directly obtain the height, width and depth of the
current display raster using the following routines:
int vrl_DisplayGetWidth(void);
int vrl_DisplayGetHeight(void);
int vrl_DisplayGetDepth(void);
You can obtain the current version of the display driver, and a
string describing the driver, using the following functions:
int vrl_DisplayGetVersion(void);
char *vrl_DisplayGetDescription(void);
The display subsystem contains routines for clearing the screen,
drawing individual points (i.e. pixels), drawing lines, drawing
boxes, and drawing text:
void vrl_DisplayClear(vrl_Color color);
void vrl_DisplayPoint(vrl_ScreenPos x, vrl_ScreenPos y, vrl_Color color);
void vrl_DisplayLine(vrl_ScreenPos x1, vrl_ScreenPos y1, vrl_ScreenPos x2,
vrl_ScreenPos y2, vrl_Color color);
void vrl_DisplayBox(vrl_ScreenPos x1, vrl_ScreenPos y1, vrl_ScreenPos x2,
vrl_ScreenPos y2, vrl_Color color);
void vrl_DisplayText(vrl_ScreenPos x, vrl_ScreenPos y, vrl_Color color,
char *message);
When drawing a string of text, it's sometimes necessary to find
out how wide and high it will appear on screen; the following two
functions provide that information:
vrl_ScreenPos vrl_DisplayGetTextWidth(char *string);
vrl_ScreenPos vrl_DisplayGetTextHeight(char *string);
AVRIL Technical Reference Manual 38
The rendering engine (or the application) may need to find out
what capabilities the display driver has; in particular, whether
it can do things like Gouraud shading and X-Y clipping. The
following routines provide the answers:
vrl_Boolean vrl_DisplayCanGouraud(void);
vrl_Boolean vrl_DisplayCanXYclip(void);
When rendering, certain tasks have to be performed at the
beginning and end of every frame; the application informs the
display driver when a frame begins and ends using the following
two functions:
void vrl_DisplayBeginFrame(void);
void vrl_DisplayEndFrame(void);
Some display drivers are capable of Z-buffering, either in
hardware or in software. The following routines are provided to
support Z-buffers:
vrl_DisplayUseZbuffer(flag)
void vrl_DisplaySetZbuffer(vrl_Raster *r);
vrl_Raster *vrl_DisplayGetZbuffer(void);
void vrl_DisplayClearZbuffer(depth);
These functions allow you to set and get pointers to the
vrl_Raster that will be used as a Z-buffer (if software Z-
buffering is used). The vrl_DisplayClearZbuffer() routine clears
the currently-set Z-buffer (hardware or software) to the
specified depth value. The vrl_DisplayUseZbuffer() routine tells
the display driver whether or not to use the Z-buffer; the flag
is non-zero if Z-buffering should be enabled, and the return
value is 0 if no Z-buffer is present, 1 if a software Z-buffer is
available, or 2 if a hardware Z-buffer is available. Note that
as of version 2.0, AVRIL does not yet support Z-buffering.
Some shading algorithms have greater computational expense
than others do. It's possible to limit the complexity of the
shading that the display driver uses by calling the following
function:
void vrl_DisplaySetShading(int value);
The higher the value parameter, the more time is spent on shading
(e.g. Gouraud versus flat, dithering enabled or disabled, etc).
The exact meaning is up to the display driver.
Updating the actual, physical display from the off-screen buffer
is done by calling the routine
void vrl_DisplayUpdate(void);
AVRIL Technical Reference Manual 39
For drivers that write straight to the physical framebuffer, the
vrl_DisplayUpdate() function does nothing; for those that use
off-screen buffers, it does the blit.
Some display drivers need to perform certain re-calculations
whenever the palette changes; to inform the display driver of a
new palette, call the following routine:
void vrl_DisplayUpdatePalette(vrl_Palette *palette);
You can specify a rectangular on-screen window, to limit the area
of the screen that the display driver will write to. You can
also find out the location and size of that window. The
functions to do so are as follows:
void vrl_DisplaySetWindow(vrl_ScreenPos x1, vrl_ScreenPos y1, vrl_ScreenPos x2,
vrl_ScreenPos y2);
void vrl_DisplayGetWindow(vrl_ScreenPos *x1, vrl_ScreenPos *y1,
vrl_ScreenPos *x2, vrl_ScreenPos *y2);
Note that stereoscopic rendering may have an impact on the "real"
windows being used.
The display subsystem also has support for stereoscopic viewing.
It understands certain types of viewing, and must be informed
when the stereo type changes. You can set and query the stereo
type using the following two functions:
void vrl_DisplayStereoSetType(VRL_STEREO_TYPE stype);
VRL_STEREO_TYPE vrl_DisplayStereoGetType(void);
You can specify which "eye" is being drawn to, and which should
be displayed, using the following functions:
void vrl_DisplayStereoSetDrawEye(VRL_STEREO_EYE eye);
VRL_STEREO_EYE vrl_DisplayStereoGetDrawEye(void);
void vrl_DisplayStereoSetViewEye(VRL_STEREO_EYE eye);
VRL_STEREO_EYE vrl_DisplayStereoGetViewEye(void);
The eye parameter is one of the values VRL_STEREOEYE_LEFT or
VRL_STEREOEYE_RIGHT.
Some stereoscopic viewing systems subdivide the screen,
putting the left-eye image in one area and the right-eye image in
another; the two are then optically "shuffled" to the appropriate
eye. The specific screen areas to use for each eye are set using
the following functions:
void vrl_DisplayStereoSetLeftWindow(vrl_ScreenPos x1, vrl_ScreenPos y1, vrl_ScreenPos x2,
vrl_ScreenPos y2);
void vrl_DisplayStereoSetRightWindow(vrl_ScreenPos x1, vrl_ScreenPos y1, vrl_ScreenPos x2,
vrl_ScreenPos y2);
AVRIL Technical Reference Manual 40
And finally, some stereoscopic viewing systems use more than one
display card. The following function is used to specify a
"callback" or "upcall" function that should be called to select a
specific card:
void vrl_DisplayStereoSetCardFunction(void (*function)(VRL_STEREO_EYE));
The callback function is told which card should be written to;
the value is one of the constants VRL_STEREOEYE_NEITHER,
VRL_STEREOEYE_LEFT, VRL_STEREOEYE_RIGHT or VRL_STEREOEYE_BOTH.
The function will be called by the video subsystem in order to
select either, neither or both of the video cards.
PCX File Routines
There are two functions that deal with files in PCX format:
vrl_Boolean vrl_ReadPCX(FILE *in);
vrl_Boolean vrl_WritePCX(FILE *out);
The first one reads a PCX file into the current drawing page, the
other writes the current drawing page out to disk as a PCX file.
Devices
AVRIL has support for input devices providing multiple "degrees
of freedom" (DOF). In fact, AVRIL's devices are even more
general; each device can have an arbitrary number of input and
output channels.
To use a device in AVRIL, you must first "open" it; this is
analogous to opening a file. When you're finished with the
device, you should "close" it; you can close all open devices
using vrl_DeviceCloseAll(), which is set as an atexit() function
by vrl_SystemStartup().
vrl_Device *vrl_DeviceOpen(vrl_DeviceDriverFunction fn, vrl_SerialPort *port);
void vrl_DeviceClose(vrl_Device *device);
void vrl_DeviceCloseAll(void);
Most input devices communicate over a serial port; the port
parameter is a pointer to such a port that's been opened with
vrl_SerialOpen(). See the section on Serial Ports for more
details. Devices (such as the keyboard) that don't use a serial
port should pass NULL as the port parameter. The fn parameter is
a function that operates the device; there are a number of these
already defined for popular devices, and it's easy to write your
own if you have unusual devices you wish to support. They are
listed in avrildrv.h, and in the cfg.c file; you should update
those files as you add drivers.
AVRIL Technical Reference Manual 41
If you need to get a pointer to the serial port associated
with a device, just call the following function:
vrl_SerialPort *vrl_DeviceGetPort(vrl_Device *device);
Devices can have a "mode" associated with them, whose meaning is
specific to each device. You can set and get a device's current
mode using these two functions:
void vrl_DeviceSetMode(vrl_Device *device, int mode);
int vrl_DeviceGetMode(vrl_Device *device);
Devices can also have "nicknames"; for example, your application
might deal with a head-tracker called "headtrack" which would be
defined (in the configuration file, most likely) to be the device
used for head tracking (e.g. a Polhemus Isotrak or a Logitech Red
Baron). You can set and query the nickname of a device, or find
a device with a particular nickname, using the following
functions:
char *vrl_DeviceGetNickname(vrl_Device *device);
void vrl_DeviceSetNickname(vrl_Device *device, char *nickname);
vrl_Device *vrl_DeviceFind(char *nickname);
Once a device has been opened, you can easily find out how many
input channels it has, and how many two-state buttons are on the
device, using the following two functions:
int vrl_DeviceGetNchannels(vrl_Device *device);
int vrl_DeviceGetNButtons(vrl_Device *device);
Sometimes devices can get into a strange state, or drift from
their initial settings; the function vrl_DeviceReset() resets a
device to the state it was in just after it was opened.
int vrl_DeviceReset(vrl_Device *device);
For many devices, resetting a device also marks the current
values of all its channels as the "zero" value for that channel.
To mark the current values as being the "maximum" values, call
the function
void vrl_DeviceSetRange(vrl_Device *device);
Devices should be periodically "polled" to see if they have
anything to report; this is normally done in the vrl_SystemRun()
function. An individual device can be polled using
vrl_DevicePoll(), which returns a non-zero value if new data was
acquired. All the devices can be polled by calling
vrl_DevicePollAll(), which returns a non-zero value if any of the
devices had new data.
AVRIL Technical Reference Manual 42
int vrl_DevicePoll(vrl_Device *device);
void vrl_DevicePollAll(void);
You can get the current value of a channel's input by calling
vrl_DeviceGetValue(), and you can read the button status on the
device using vrl_DeviceGetButtons():
vrl_Scalar vrl_DeviceGetValue(vrl_Device *device, int channel);
vrl_unsigned32bit vrl_DeviceGetButtons(vrl_Device *device);
Each bit corresponds to a single button on the device.
The first six channels are special, since they correspond to
the six basic degrees of freedom a device can have. The first
three, whose channel numbers are the #defined values X, Y, and Z,
provide the three-dimensional location of the input device in its
private coordinate system. The next three, whose channel numbers
are the #defined values XROT, YROT and ZROT, provide the rotation
of the device around each of the three axes. Every device is
expected to provide at least those six values; others, such as
glove-like input devices, may have a separate channel for the
flexion of each finger.
You can determine whether a channel's value has changed
since the previous poll by using vrl_DeviceChannelGetChanged(),
and you can use vrl_DeviceGetChangedButtons() to obtain a
vrl_unsigned32bit word whose bits indicate whether the
corresponding buttons have changed state since the previous poll.
vrl_Boolean vrl_DeviceGetChanged(vrl_Device *device, int channel);
vrl_unsigned32bit vrl_DeviceGetChangedButtons(vrl_Device *device);
Note that "previous poll" means the one prior to the most recent
one; in other words, you would check the changed flags
immediately after a call to vrl_DevicePoll() or
vrl_DevicePollAll() in order to see if they've changed since the
last time through.
Some devices (such as the Logitech Cyberman and the Global
Devices Controller) are capable of output as well as input. You
can find out the number of output channels a device has using the
following function:
int vrl_DeviceGetNOutputChannels(vrl_Device *device);
There are some devices that shouldn't be polled too frequently
(possibly because the polling takes a long time). Devices
drivers typically set their own polling frequency when they're
initialized, but you can read and set the polling period using
these two functions:
AVRIL Technical Reference Manual 43
void vrl_DeviceSetPeriod(vrl_Device *device, vrl_Time period);
vrl_Time vrl_DeviceGetPeriod(vrl_Device *device);
Some devices provide fewer than six degrees of freedom; in
particular, some devices (such as sourceless head trackers)
provide only rotational information, while others might provide
only positional information. In addition, some axes are
absolute, while others are relative; for example, a magnetic
tracker provides absolute rotation, whereas a joystick usually
provides a rate of rotation. Two functions are used to determine
what a device's suggested modes of operation are for translation
and rotation:
vrl_DeviceMotionMode vrl_DeviceGetRotationMode(vrl_Device *device);
vrl_DeviceMotionMode vrl_DeviceGetTranslationMode(vrl_Device *device);
Each of these two functions returns one of the values
VRL_MOTION_NONE (i.e., this type of motion is not reported by
this device), VRL_MOTION_RELATIVE (i.e. this device provides
relative information) or VRL_MOTION_ABSOLUTE (this device
provides absolute information).
It's important to note that these are all suggestions from
the device driver as to how it should be used; you can still
choose, in your application, to treat any device as either
absolute or relative.
To understand what these next few functions do, it's
important to understand what kind of processing the system does
on the values once it receives them from the device. Each
channel can be in either "accumulate" or "non-accumulate" mode.
For accumulating devices, the value is first checked to see how
close it is to zero; if it's less than a channel-specific
deadzone value, then the value is considered to be zero. For
non-accumulating devices, the deadzone value is treated as a
minimum change from the most recently read value for this
channel; a device that moves by less than the deadzone amount
between consecutive polls will not change in value.
The value is then scaled so that its maximum value is less
than a channel-specific scale value. For channels with the
accumulate flag set, the value is also scaled by the elapsed
time; the scale for such channels is treated as the maximum rate
of change per second.
The accumulate, scale and deadzone values can be set and read
using the following calls:
vrl_Scalar vrl_DeviceGetDeadzone(vrl_Device *device, int channel);
void vrl_DeviceSetDeadzone(vrl_Device *device, int channel, vrl_Scalar value);
vrl_Scalar vrl_DeviceGetScale(vrl_Device *device, int channel);
vrl_DeviceSetScale(vrl_Device *device, int channel, vrl_Scalar value);
AVRIL Technical Reference Manual 44
vrl_Boolean vrl_DeviceGetAccumulate(vrl_Device *device, int channel);
void vrl_DeviceSetAccumulate(vrl_Device *device, int channel, vrl_Boolean value);
If you like, you can bypass all that processing and obtain the
actual, "raw" value being reported by the device by calling
vrl_Scalar vrl_DeviceGetRawValue(vrl_Device *device, int channel);
Devices are kept internally in a linked list; if you want to
iterate over the list, you can do it with the following two
functions:
vrl_Device *vrl_DeviceGetFirst(void);
vrl_Device * vrl_DeviceGetNext(vrl_Device *device);
You can retrieve a human-readable description of a device by
calling the function
char *vrl_DeviceGetDesc(vrl_Device *device);
You can produce output on any of the device's channels by calling
void vrl_DeviceOutput(vrl_Device *device, int channel, vrl_Scalar value);
The channel and the value (which ought to be in the range 0 to
255) are passed along to the device; if it's capable of
outputting that value on that channel, it does. Not all devices
are capable of producing output, and those that can have a
variety of different ways of doing it (including sound and
vibration). All you can be sure of is that a value of zero will
turn the output off, and a non-zero value will turn it on.
Note that it's possible to have an "output-only" device; it
will report zero for all its input values, but still respond to
output requests. This might be one approach to supporting motion
platforms, for example.
Some 2D devices can map their two axes into 6 degrees of
freedom using combinations of buttons. There are two functions
to get and set the mapping tables they use:
void vrl_DeviceSetButtonmap(vrl_Device *device, vrl_DeviceButtonmap *b);
vrl_DeviceButtonmap *vrl_DeviceGetButtonmap(vrl_Device *device);
Buttonmaps are a fairly complex topic, and are discussed in more
detail in Appendix F.
Serial Ports
Since many input devices use serial communications, AVRIL
contains a library of serial port routines. These will mostly be
AVRIL Technical Reference Manual 45
of interest to people writing device drivers, but they might also
be used for modem communications.
To some extent, serial port support will be platform-
dependent; the meaning of the various parameters in the
vrl_SerialOpen() call will be different on different systems.
However, all the other routines should be the same regardless of
platform.
A serial port can be opened and closed using
vrl_SerialOpen() and vrl_SerialClose() respectively; all the
serial ports can be closed at once using vrl_SerialCloseAll(),
which gets set as an atexit() function by vrl_SystemStartup().
The communications parameters can be set using
vrl_SerialSetParameters().
vrl_SerialPort *vrl_SerialOpen(unsigned int address, int irq, unsigned int buffsize);
void vrl_SerialClose(vrl_SerialPort *port);
void vrl_SerialCloseAll(void);
void vrl_SerialSetParameters(vrl_SerialPort *port, unsigned int baud,
vrl_ParityType parity, int databits, int stopbits);
The address parameter to vrl_SerialOpen() is interpreted
differently on different platforms; on PC-compatible machines,
it's the base address of the UART chip (usually 0x3F8 for COM1,
0x2F8 for COM2). The irq parameter is also system-dependent; on
PC-compatible machines, it's the hardware interrupt level the
serial port uses (usually 4 for COM1, 3 for COM2).
The buffsize parameter is the size of buffer to use for
incoming data. If it's set to zero, the port will be in a non-
buffered mode; this may mean that characters get lost. Such a
mode would only be used if you're doing your own handshake with
the device; in other words, you send it a byte to poll it, and
then sit in a tight loop receiving the resulting data. In this
case, the irq value is ignored.
The baud parameter to the vrl_SerialSetParameters() function
is the baud rate; this is usually 9600 for most input devices.
The parity parameter is one of VRL_PARITY_NONE, VRL_PARITY_EVEN
or VRL_PARITY_ODD; most devices use VRL_PARITY_NONE. The
databits field is the number of data bits per transmitted byte;
this is either 7 or 8, and most devices use 8. The number of
stop bits can be 1 or 2, and is usually 1. Newly-opened serial
ports are set up to be 9600 baud, VRL_PARITY_NONE, 8 data bits
and 1 stop bit.
The vrl_SerialCheck() routine will return a non-zero value
if there are unread characters in the input buffer (or if there's
a character waiting at the UART, if the port is in unbuffered
mode). The vrl_SerialGetc() routine reads and returns a byte.
It should not be called unless you know there's a character
AVRIL Technical Reference Manual 46
waiting; in buffered mode, such a call will return zero, while in
unbuffered mode the call will block until a character arrives!
The vrl_SerialFlush() routine will get rid of any characters
waiting in the input buffer.
vrl_Boolean vrl_SerialCheck(vrl_SerialPort *port);
unsigned int vrl_SerialGetc(vrl_SerialPort *port);
void vrl_SerialFlush(vrl_SerialPort *p);
The vrl_SerialPutc() and vrl_SerialPutString() routines put out
single characters and null-terminated strings of characters
respectively. The terminating null byte is not sent by
vrl_SerialPutString().
void vrl_SerialPutc(unsigned int c, vrl_SerialPort *port);
void vrl_SerialPutString(unsigned char *s, vrl_SerialPort *p);
There are also two routines for controlling the state of the DTR
and RTS lines:
void vrl_SerialSetDTR(vrl_SerialPort *port, vrl_Boolean value);
void vrl_SerialSetRTS(vrl_SerialPort *port, vrl_Boolean value);
and one for setting the size of the serial FIFO, if applicable:
void vrl_SerialFifo(vrl_SerialPort *p, int n);
Packet Routines
Many serial devices communicate by sending "packets" of data.
There are four routines in AVRIL to support the reception of
packets:
vrl_DevicePacketBuffer *vrl_DeviceCreatePacketBuffer(int buffsize);
void vrl_DeviceDestroyPacketBuffer(vrl_DevicePacketBuffer *buff);
vrl_Boolean vrl_DeviceGetPacket(vrl_SerialPort *port, vrl_DevicePacketBuffer *buff);
unsigned char *vrl_DevicePacketGetBuffer(vrl_DevicePacketBuffer *buff);
The vrl_DeviceCreatePacketBuffer() function creates a packet
buffer with room for the specified number of bytes;
vrl_DeviceDestroyPacketBuffer() destroys such a buffer. The
vrl_DevicePacketGetBuffer() routine returns a pointer to the
actual data packet.
The vrl_DeviceGetPacket() routine is designed to handle a
particular type of packet, a fixed-size one in which the leading
byte has the top bit set and none of the other bytes do. This
format is used by the Logitech Cyberman, among other devices.
You can use this routine directly, or you can write your own
(with a different name, of course); the source code is found in
packet.c, and the vrl_DevicePacketBuffer data structure is in
avril.h (and it's not expected to change, unlike many other
AVRIL Technical Reference Manual 47
internal data structures). The vrl_DeviceGetPacket() routine
returns a non-zero value if a complete packet has been received
(i.e. exactly buffsize bytes have been received, starting with a
byte that has the top bit set).
More information
Remember that the appendices to this document are in a separate
file, as is the Tutorial.
AVRIL Technical Reference Manual 48