Dec 192017
REND386 -- A 3-D Polygon Rendering Package for the 386 and 486. Complete C library.
File R386DEV4.ZIP from The Programmer’s Corner in
Category C Source Code
REND386 — A 3-D Polygon Rendering Package for the 386 and 486. Complete C library.
File Name File Size Zip Size Zip Type
ANIM.C 2920 1055 deflated
COLORMAP.C 4961 1778 deflated
CURSORS.C 7198 2410 deflated
DEMO4.C 20902 6627 deflated
DOC 0 0 stored
DEVEL4.DOC 74534 22889 deflated
MEMMODEL.DOC 5122 2233 deflated
PDRIVER.DOC 9307 3785 deflated
VDRIVERS.DOC 8408 3389 deflated
GLOVEPTR.C 4531 1790 deflated
HDMANIP.C 7071 2371 deflated
INCLUDE 0 0 stored
CURSOR.H 1564 665 deflated
F3DKITD.H 4355 1709 deflated
INTMATH.H 3652 1288 deflated
PLG.H 900 454 deflated
POINTER.H 6757 2364 deflated
REND386.H 10991 3452 deflated
SEGASUPP.H 4661 1922 deflated
SEGIO.H 709 376 deflated
SPLITDEF.H 1512 689 deflated
SPLITS.H 2117 708 deflated
TASKS.H 687 366 deflated
USERINT.H 1169 553 deflated
INT 0 0 stored
3DSTRUCT.H 9080 3514 deflated
3DSUPP.C 17878 4680 deflated
DRVLOAD.C 993 563 deflated
INTMATHX.C 13229 3340 deflated
INTSPLIT.C 2402 814 deflated
INTTRIG.C 4811 1729 deflated
MAKEFILE 977 324 deflated
MATRIX.C 16739 3408 deflated
RENDREP.C 57086 13512 deflated
SEGMENT.C 8269 2411 deflated
VDRINTE.ASM 4454 1252 deflated
KEYBOARD.C 29104 6828 deflated
MAKEFILE 1990 512 deflated
MOUSEPTR.C 3553 1326 deflated
REND386.LIB 47616 19688 deflated
RENDER.C 15851 4037 deflated
SEGA 0 0 stored
SEGA.LIB 9728 4755 deflated
GLOVDELH.ASM 4359 1181 deflated
MAKEFILE 328 167 deflated
SGSPPT.C 16586 5426 deflated
TIMER.C 6364 2086 deflated
SUPP 0 0 stored
SUPP.LIB 28672 12105 deflated
CURSORS.C 4795 1689 deflated
MAKEFILE 898 294 deflated
MATHINIT.C 3247 1321 deflated
PCXMODEY.C 3022 1313 deflated
PLG.C 5002 1736 deflated
POINTER.C 7734 2352 deflated
SEGIO.C 5418 1970 deflated
SPLITS.C 10012 2491 deflated
STEREOV.C 4023 1443 deflated
TASKS.C 1897 788 deflated
USERINT 0 0 stored
USERINT.LIB 4096 1989 deflated
JOYSTICK.C 3922 1415 deflated
MAKEFILE 360 170 deflated
USERINT.C 3098 1149 deflated
WORLD.C 23833 6070 deflated

Download File R386DEV4.ZIP Here

Contents of the DEVEL4.DOC file

REND386 -- A 3-D Polygon Rendering Package for the 386 and 486
Written by Dave Stampe and Bernie Roehl

LIBRARY Documentation
Version 4.01 - September 1992

This package contains a library of routines (callable from C) that will
render 3 dimensional scenes on 386 and 486 based systems with a VGA display.

The package also includes a simple user-interface library with joystick
support, and a demo program to show what it's capable of.

The package is designed to be fast. It accomplishes that goal by making
use of the special instructions that exist only on 386 and 486 processors;
it will not run on 286s, 8086s or 8088s.

It also requires Turbo C++ or Borland C to link; Turbo C 2.00 seems to
generate unresolved references.


Speed is not a straightforward thing to measure. There is a relationship
between the speed of the processor, the complexity of the scene, and the
number of frames per second.

With this software, a 512-polygon scene can be rendered at speeds up to
14 frames/second on a 486/25; this corresponds to a speed of over 7000


These libraries may be freely used to write software for release into the
public domain. Permission to use these libraries for the production of
commercial software (including shareware) must be obtained from the authors,
Bernie Roehl ([email protected]) and Dave Stampe
([email protected]).

Licensing terms will be quite reasonable.


The following sessions describe the various data structures and routines
that comprise the rend386 package.


The following functions deal with OBJECTs. OBJECTs can have several different
"representations", each at a different level of detail. The rendering
software will automatically select an appropriate representation based on
the apparent size on-screen of the object.

Every object has at least one representation.

Each representation has a set of vertices and a set of polygons associated
with it.

In addition, objects can have an "owner" field indicating the segment which
tracks the objects movements. (See below for more information on segments).
Objects also have a set of flags.

Each vertex has an x,y,z location in the object's native coordinates, and a
(probably different) x,y,z location in world coordinates. Each polygon has
a color value and an array of pointers to the vertices that define that

OBJECT *new_obj(int type, int nv, int np)
Creates a new object, with a single representation which will have
room for up to nv vertices and np polygons.

REP *add_representation(OBJECT *obj, long size, int nv, int np)
Adds an additional representation to an object, that should be used
if the screen size of the object is greater than the "size" field
of the object. The object representations are linked in order of
size, and an object with size 0 is always drawn.
A call to this function makes the newly-created representation
the current one for purposes of adding vertices and polygons. It
will have room for up to nv vertices and np polys.

void delete_rep(OBJECT *obj)
Deletes the current representation of the given object.

void select_representation(OBJECT *obj, long size)
Find the representation of the given object that would be used
if the object had the given apparent size, and make it the current
representation of the object.

void first_rep(OBJECT *obj)
Makes the highest-resolution representation of given object
the current one.

void next_rep(OBJECT *obj)
Selects the next coarser representation of the given object.

long get_rep_size(OBJECT *obj)
Returns the size value for the current representation of the
given object.

void set_rep_size(OBJECT *obj, long size)
Sets the size value for the current representation of the given

void add_vertex(OBJECT *obj, long x, long y, long z)
Adds a vertex to the current representation of the given object,
with x,y,z as its coordinates in object space.

POLY *add_poly(OBJECT *obj, unsigned color, int npoints)
Adds a polygon to the current representation of the given object,
with the given color and room for up to npoints vertices.

void add_point(OBJECT *obj, POLY *p, int vertnum)
Adds a point to a polygon. Vertex number vertnum in object obj is
added to the given polygon. The vertices must be added in a
"clockwise" order as seen from outside the object.

void delete_obj(OBJECT *obj)
Deletes the given object and frees all memory associated with it.

long get_object_bounds(OBJECT *obj, long *x, long *y, long *z)
Obtains the x,y,z extents of the given object.

void compute_obj(OBJECT *obj)
Does internal computation of polygon normals, bounding sphere, and
so forth. Must be called once the object is complete (i.e. all the
vertices and polygons have been added, and the points comprising
the polys have been defined) and before the object is rendered.
Note that this routine must be called for each representation,
since different reps have different polygons and thus different
normals. Also note that the object has a single bounding sphere
(not one per representation), and the bounding sphere is calculated
based on the current representation of the object (so compute the
largest representation last for best results).

unsigned get_object_sorting(OBJECT *obj)
Returns the current value of the object's depth-sorting type field.
Values are:
DEEPEST - the default; sorts by deepest (i.e. farthest) vertex
AVERAGE - use average depth rather than maximum
ATBACK - sorts the polys as if they were very far away
You can also depth sort by objects before sorting by poly by OR'ing
in the BYOBJECT value.

void set_object_sorting(OBJECT *obj, unsigned depth_type)
Sets an object's depth-sorting type field.

unsigned get_default_depth_sort()
Returns the current default value for the depth-sort type.

void set_default_depth_sort(unsigned value)
Sets the current default value for the depth-sort type. This
value becomes the default setting for all new objects.

void set_obj_flags(OBJECT *obj, unsigned char value)
Sets the object's flags.

unsigned get_obj_flags(OBJECT *obj)
Gets the object flags.

The bottom 14 bits of the flags are user-accessible; the top two bits are
used internally by the rendering software. The bits defined in rend386.h
are OBJ_NONSEL (which indicates the object should be non-selectable, for
example a 3D representation of a pointer device), OBJ_INVIS (which indicates
the object is invisible, and should not be drawn), and OBJ_HIGHLIGHTED
(indicating that the object should be drawn highlighted).

void get_obj_info(OBJECT *obj, int *nv, int *np)
Extracts the number of vertices in obj and stores it in *nv, and
similarly stores the number of polys in obj in *np.

void get_vertex_info(OBJECT *obj, int vertnum, long *x, long *y, long *z)
Extracts the x, y and z values of vertex number vertnum in obj.
This function obtains the coordinates in the object coordinate system.

void get_vertex_world_info(OBJECT *obj, int vertnum, long *x, long *y, long *z)
Extracts the x, y and z values of vertex number vertnum in obj.
This function obtains the coordinates in the world coordinate system.

void get_poly_info(OBJECT *obj, int polynum, unsigned *color, int *nverts,
int *verts, int maxverts)
Extracts information from polygon number polynum in obj. The color
parameter is set to the polygon color (see colors.doc for details).
If maxverts is non-zero, then the array of integers verts is filled
in with the index numbers of the vertices comprising the polygon.
No more than maxverts vertices will be stored; if there are more
than that number of vertices in the object, the rest will be ignored.

void set_poly_color(OBJECT *obj, int polynum, unsigned color)
Sets the color of polygon number polynum in obj to the given color.
See colors.doc for details of how the color parameter is interpreted.

void *get_object_owner(OBJECT *obj)
Returns the owner field of the given object. The owner field is
used by the renderer to hold a pointer to the owning segment, but
can be used by the application programmer if the object is fixed.

void set_object_owner(OBJECT *obj, void *owner)
Sets the object's owner field.

void copy_world_to_object(OBJECT *obj)
Copies the world coordinates of the object into its object coordinates,
effectively making the object's coordinate system the same as the
world coordinate system. Rarely used.

void highlight_obj(OBJECT *obj)
Turns on the HIGHLIGHT bit in the color field of all the polys in the
given object.

void unhighlight_obj(OBJECT *obj)
Turns off the HIGHLIGHT bit in the color field of all the polys in the
given object.


The following functions deal with object lists, or OBJLISTs. An OBJLIST is
what you pass to the renderer (i.e. you tell it to render a given list of

OBJLIST *new_objlist()
Creates a new object list and returns a pointer to it.

void add_to_objlist(OBJLIST *list, OBJECT *obj)
Adds an object to an object list. WARNING: be sure object is NOT
on another objlist first! If on_objlist(obj) does not return NULL,
remove it from the old list first.

OBJLIST *void remove_from_objlist(OBJECT *obj)
Removes an object from its object list.

void del_objlist(OBJLIST *list)
Deletes an object list.

OBJLIST *on_objlist(OBJECT *obj)
Returns the objlist that the given object is on, or NULL if it's
not on any objlist.

OBJECT *first_in_objlist(OBJECT **objlist)
Returns a pointer to the first object in a list.

OBJECT *next_in_objlist(OBJECT *obj)
Returns a pointer to the next object (after obj) in its list.

OBJECT *prev_in_objlist(OBJECT *obj)
Returns a pointer to the previous object (before obj) in its list.

int is_first_in_objlist(OBJECT *obj)
Returns non-zero if obj is the first entry in its list.

int is_last_in_objlist(OBJECT *obj)
Returns non-zero if obj is the last entry in its list.

void walk_objlist(OBJLIST *objlist, void (*fn)())
Walks down the given objlist, calling the given function on each
object in turn (i.e. fn is called as fn(obj)).


In addition to an OBJLIST, the rendering routines need to know about your
current viewpoint; the VIEW structure contains this information. It has
three fields (ex, ey, ez) that define the current location of your "eye"
or "camera" in world coordinates, and another three (pan, tilt and roll)
that define the "camera's" rotation about the Y (vertical) axis, X
(horizontal) axis, and Z (forward) axis respectively.

The VIEW also has near/far clipping information in the hither and yon
fields (in world coordinates). Keep hither >10, yon >1000000 for best results.

(A note about coordinates: X is positive to the right, Y is positive up and
Z is positive away from you).

There is also a zoom factor, which works like the zoom on a camera. It
is equal to 65536/(tan(FOV/2)) where FOV is the horizontal field of view.

The angles and zoom factor are all stored as 32-bit (long) integers, but
are best thought of as floating point numbers multiplied by 65536L. This
is referred to in this document as "16.16" format (16 bits of integer,
16 bits of fraction).

A VIEW also has information about what area on the screen should be used to
present the scene; these left, right, top and bottom values are in absolute
screen coordinates. There is also an "aspect ratio" which determines the
relative size of a non-square pixel. (See the default_view structure in the
demo for defaults).

The world-space coordinates of a single light source are stored in the lx,ly
an lz fields. In addition there's a flag that indicates whether the light
is a point source or a directional source, and a measure of the amount of
ambient light in the environment.

There is also a "flags" field whose bottom bit is set if the objects should
all be drawn in wire-frame mode rather than as filled polygons. Because no
explicit edge information is stored in the OBJECT data structures, wireframing
is actually slower than filled-polys since each edge winds up getting drawn

The next two bits (called HIDE_HIGHLIGHTED and HIDE_UNHIGHLIGHTED) determine
whether highlighted (and/or unhighlighted) objects should be invisible.

There is now a pair of values (x_offset and y_offset) that effectively shift
the screen center by a given amount (number of pixels), and an orientation
field. The orientation field can have the OR of the values NOFLIP,
XFLIP or YFLIP to determine which axis or axes to flip the image over in this
view. Use it with some HMDs or mirror stereo. There's also a viewpoint
transform matrix which should only be accessed by the provided routines,
and can be wiped by a number of routines such as fast_view_factors() or

Finally, there's a 200 byte "work area" associated with each VIEW, used by the
renderer for precomputed values.

void compute_view_factors(VIEW *v)
This function has been replaced by initialize_screen_factors()
and fast_view_factors(), but can still be used since it just calls
those two routines.

void initialize_screen_factors(VIEW *v)
Computes the screen factors and sets them into the given VIEW struct.
Should be called once initially, and again whenever the zoom,
x_offsets, y_offset or orientation have changed.

void fast_view_factors(VIEW *v)
Should be called whenever the viewpoint changes. Just recomputes the view
matrix from the pan, etc.

void matrix_view_factors(VIEW *v,MATRIX m)
Converts the current view matrix to a homogenous matrix. The view
matrix may be set from pan, etc. by a call to fast_view_factors()

void view_to_matrix(VIEW *v,MATRIX m)
Sets up the view matrix from a homogenous matrix. Takes the place
of fast_view factors().

In addition to all this, there's a global struct called Screeninfo that
has the minimum and maximum X and Y values, the number of available colors,
the number of pages, and the screen coordinates of the screen "center".
This is set up by the video driver, and should not be changed.


A matrix is a 4 by 3 array of long (32-bit) integers. It can best be thought
of as an upper 3x3 matrix representing rotations, and 3-element bottom row
vector representing translation. A matrix is used to transform a point or
an object.


All numbers are fixed point, and are 32 bits long in total.
The decimal point is expressed as , where ii is the
number of integer bits and ff is the number of fractional bits.
<16.16> is usually used for angles, and is 65536 times the angle
in degrees. <3.29> is used for sines, cosines, and matrix entries
where a magnitude of one or less is expected. This is 536870912
times the real value.

called to initialize the math tables.


All these fuctions are very fast: less than 5-10 uS each.
This is about twice as fast as a floating point coprocessor.

long isine(long angle)
Given <16.16> angle (65536L * degrees) returns <3.29> sine
or sin(angle)<<29. Uses table with entry every 0.35 degrees,
with interpolation.

long icosine(long angle)
Given <16.16> angle (65536L * degrees) returns <3.29> cosine
or cos(angle)<<29. Uses isine(90*65536L-angle).

To compute tangent, use isine(angle)/cosine(angle).

long arcsine(long x)
Given <3.29> sine, computes <16.16> angle. Accurate to 25 bits.

long arccosine(long x)
Given <3.29> cosine, computes <16.16> angle. Accurate to 25 bits.

long arctan2(long y, long x)
Equivalent to C atan2, uses the y and x argument's signs and ratio
to compute full 360 degree angle. Accurate to 25 bits.


This is a set of routines for multiplying vectors and homogenous matrices,
inverting matrices, and creating rotation matrices from angles. There is
also a routine to convert a matrix back to rotation angles, but it depends
on the matrix being very close to unit scale (0.999 or so).

Homogenous matrices express a rotation followed by a translation, and
may be multiplied to cascade transforms or coordinate shifts. A
homogenous matrix is 4x4, but since our scale is always 1, we can use a
3x4 2D array to store the matrix. All rotation entries are in <3.29>
format, translation entries are <32.0>.

This is a storage map of a matrix:

{ m[0][0] m[0][1] m[0][2] | m[3][0] } { rotation | translation }
{ m[1][0] m[1][1] m[1][2] | m[3][1] } = { matrix | vector }
{ m[2][0] m[2][1] m[2][2] | m[3][2] } { | }
{ 0 0 0 1 } { 0 0 0 1 }

Some matrices are rotation only: the translation vector is 0.
Applying a matrix M|t to a vector v does Mv + t.

void matrix_mult(MATRIX a, MATRIX b, MATRIX c)
Multiplies the rotation parts (3x3 submatrices) so C = AB. Does not
affect the translational parts. Any or all of A, B, and C may be
the same matrix.

void matrix_product(MATRIX a, MATRIX b, MATRIX c)
Full homogenous matrix multiply. Does C = AB, and computes new
translational part for C. Any or all of A, B, and C may be
the same matrix.

void matrix_point(MATRIX m, long *xp, long *yp, long *zp)
Applies the homogenous matrix M to the given vector. Also equivalent
to applying the matrix transformation to the point (x,y,z).

void matrix_rotate(MATRIX m, long *xp, long *yp, long *zp)
Applies rotational part of matrix M to the given vector. Also
equivalent to applying the matrix rotation to the point (x,y,z).

void matrix_transpose(MATRIX a, MATRIX b)
Computes the rotational inverse of matrix A and stores it in B.
The transpose is equivalent to the inverse for orthonormal matrices.
A and B may be the same matrix.

void inverse_matrix(MATRIX a, MATRIX b)
Computes the inverse for homogenous matrix A, and stores it in B.
This matrix will perform the reverse transform of matrix A.
A and B may be the same matrix.

void identity_matrix(MATRIX m)
Fills the matrix M with zero rotate, zero translate data.

void matrix_copy(MATRIX s, MATRIX d)
Copy matrix s to matrix d.

void matrix_rot_copy(MATRIX s, MATRIX d)
Copies upper left 3x3 submatrix, zeros translation part of
matrix s to matrix d.

void multi_matrix(MATRIX m,long rx,long ry,long rz,
long tx,long ty,long tz,int type)
Creates a rotation/translation matrix. The rotations are around the
X, Y, and Z axes, CCW looking "out" along the axis (left-hand coordinate
system). Positive X is right, positive Y is up, positive Z is into

The order that these rotations are applied is specified by "type", which
is one of:

#define RXYZ 1 /* matrix rotation types */
#define RYXZ 0 /* ONLY RYXZ guaranteed to be tested */
#define RXZY 2
#define RZYX 5
#define RZXY 4
#define RYZX 6

void std_matrix(MATRIX m,long rx,long ry,long rz,long tx,long ty,long tz)
Creates a matrix as above, but with RYXZ (the standard order) implied.

void matrix_to_angle(MATRIX m, long *rx, long *ry, long *rz)
Converts a well-scaled matrix to a set of rotation angles (RYXZ) and
a translation. Results will be somewhat erroneous if the matrix is
badly scaled (i.e after many multiplies).

void vector_to_matrix(MATRIX m, long x, long y, long z)
Makes rotation-only matrix that will xform Z axis to given vector.
Used by "pinch" glove rotation.

long dot_prod_29(long a, long b, long c, long x, long y, long z)
Computes a 2-vector dot product, using <3.29> values. This is defined
as (ax + by+ cz) >> 29.

void cross_column(MATRIX m, int col)
Replaces one column of the rotational part of a matrix with the cross
product of the other 2 columns. This can be used to reduce
calculations, but will square the scaling error of the matrix.

long plane_y(long a, long b, long c, long d, long x, long z)
Computes -(ax + cz + d) / b in order to compute the y coordinate of
a plane (a,b,c,d) given the x and z coordinates.

void fix_matrix_scale(MATRIX m)
Uses floating point calculations to restore the unit scale of a
rotational matrix. This is expensive, but needed every 1000 or
so matrix self-multiplications to prevent shrinkage and
distortion of objects.

long m_mult(long a, long b)
Performs (a*b) >> 29.

long scale_16(long s, long a, long x)
Performs s*(x + a) >> 16. Used for scaling pointer devices.

long calc_scale_16(long a, long b, long s)
Computes a scale factor to center and scale the range of a pointer

void apply_matrix(OBJECT *obj, MATRIX m)
Completely moves an object (current representation, bounds, etc)
by translating object coordinates to world coordinates using the
matrix M.

void matmove_osphere(OBJECT *obj, MATRIX m)
Moves an object's position and bounding volume to world coordinates
using the matrix M. The translation of the current representation
will be performed the first time the object is rendered.

void matmove_rep(REP *rep, MATRIX m)
Moves an object's position to world coordinates using the matrix M.
This translates the vertices and poly normals of the current representation
and is performed the first time the object is rendered.

long sphere_pretest(OBJECT *obj, long x, long y, long z)
Bounding sphere pretest for collision detection and selection.
Returns 0x40000000L if not in bounding sphere, else returns square
of distance from center of object to the point (x,y,z).

long big_dist(long x1,long y1,long z1,long x2,long y2,long z2)
Returns estimate of distance between points:
abs(x1-x2) + abs(y1-y2) + abs(z1-z2)

int find_normal(long x1,long y1,long z1,long x2,long y2,long z2,
long x3,long y3,long z3,long *xn,long *yn,long *zn)
Compute polygon normal, given 3 points on polygon. Normal is
scaled to a length of 2^29 (to 8 bits of accuracy) for lighting
purposes. This code is quite fast: about 20 uS or so. It is
part of a larger polygon normal computation that determines the best
set of vertex points to use (in 3DSUPP.C). It returns the
power of 2 closest to the normal magnitude before unitizing, which
can be used as a measure of the "goodness" of the normal.


A segment is a piece of an articulated (multi-jointed) figure. Each segment
has a parent segment; the parent of the "root" segment is NULL. Each segment
has a linked list of children, and each segment has a 'sibling' which
is the next segment in the parent's linked list of children.

Each segment has a pointer to the object that the segment controls. Each
segment may also have a name associated with it.

Segments keep track of the position of objects by storing transformation
matrices that transform the object into the parent's coordinate system;
you can think of a segmented figure as being a tree of transformation
matrices. Segments also cache the transformation matrix used to convert
the object to world coordinates (i.e. the product of all the matrices
higher up in the tree).

SEGMENT *new_seg(SEGMENT *parent)
Creates a new segment, with the given segment as its parent. If the
'parent' parameter is NULL, this is a root object.

void seg_set_object(SEGMENT *s, OBJECT *rep)
Specifies the object that the given segment should keep track of. Used
to be called seg_setrep().

OBJECT *seg_get_object(SEGMENT *s)
Returns a pointer to the object controlled by the given segment.
Used to be called seg_getrep().

WARNING: all the routines that get the angle of a segment call
matrix_to_angles(), which may be expensive (100-1000 uS).

void seg_getposang(SEGMENT *s, long *rx, long *ry, long *rz)
Stores the current orientation of the given segment (relative to the
world coordinate system) in *rx, *ry and *rz. The angles are in
16.16 format.

void seg_getjointang(SEGMENT *s, long *rx, long *ry, long *rz)
Stores the current orientation of the given segment (relative to the
its parent's coordinate system) in *rx, *ry and *rz. The angles are in
16.16 format. Used to be part of seg_getposition().

void seg_getposxyz(SEGMENT *s, long *x, long *y, long *z)
Stores the current position of the given segment (relative to
the world coordinate system) in *x, *y and *z.

void seg_getjointxyz(SEGMENT *s, long *x, long *y, long *z)
Stores the current position of the given segment (relative to
its parent's coordinate system) in *x, *y and *z. Used to be part of

char *seg_getname(SEGMENT *s)
Returns the name of the given segment.

void set_setname(SEGMENT *s, char *name)
Sets the name of the given segment to the given value; a copy of the
string is made.

void rel_move_segment(SEGMENT *obj, long x, long y, long z)
Performs a relative move of the given segment; x, y and z are the amounts
to move the segment along each of the three axes. After calling this
routine, you must at some point call update_segment().

void abs_move_segment(SEGMENT *obj, long x, long y, long z)
Performs an absolute move of the given segment; x, y and z are the new
absolute location of the segment along each of the three axes. After
calling this routine, you must at some point call update_segment().

WARNING: all relative rotates multiply the segment matrix by another matrix.
After 1000x or so, the matrix (and the objects) will start to shrink. A
rescaling routine is called at pseudorandom intervals every 1000 or so
multiplies, but this can take up to 3000uS, so be warned!

void rel_rot_segment(SEGMENT *seg, long rx, long ry, long rz)
Performs a relative rotation of the given segment; x, y and z are the
angles to rotate the segment along each of the three axes (in 16.16
format). After calling this routine, you must at some point call

void abs_rot_segment(SEGMENT *seg, long rx, long ry, long rz)
Performs an absolute rotation of the given segment; x, y and z are the
angles to rotate the segment to along each of the three axes (in 16.16
format). After calling this routine, you must at some point call

void abs_mat_segment(SEGMENT *s, MATRIX m)
Directly sets the transformation matrix for the given segment.

void abs_rotmat_segment(SEGMENT *s, MATRIX m)
Directly sets the rotation part of the transformation matrix for the
given segment. Only the first three rows of the matrix m are used.

void rel_mat_segment(SEGMENT *s, MATRIX m)
Multiplies the given segment's transformation matrix by the given
matrix, doing a rotation and translation relative to the segment's
current position.

void rel_rotmat_segment(SEGMENT *s, MATRIX m)
Multiplies the given segment's transformation matrix by the given
matrix, doing a rotation only relative to the segment's current
position. Only the first three rows of the matrix m are used.

void update_segment(SEGMENT *s)
Takes the current position and orientation and uses it (and similar
information from the parent segment, and its parents, and so on) to
transform the segment's representation. Also transforms all the
segments attached to this one (recursively). May use lots of stack
space. Only recomputes the segments that need it.

void full_update_segment(SEGMENT *s)
Similar to update_segment, but explicitly recomputes the matrices
at each node even if it's not needed. You should not need to use
this function.

SEGMENT *find_root_segment(SEGMENT *s)
Walks up the tree from the given segment and finds the root segment for
the figure of which the segment is a part.

SEGMENT *parent_segment(SEGMENT *s)
Returns the parent of the given segment.

SEGMENT *child_segment(SEGMENT *s)
Returns the child of the given segment.

SEGMENT *sibling_segment(SEGMENT *s)
Returns the sibling of the given segment.

void delete_segment(SEGMENT *s, void (*delrep_fn)())
Deletes the given segment and frees the memory associated with it; calls
the given function (if not NULL) to delete any representation associated
with the given segment.

void attach_segment(SEGMENT *s, SEGMENT *to)
Attaches the given segment to the given parent ('to'), making it a child
of that segment.

void detach_segment(SEGMENT *s)
Detaches the given segment from its parent, and makes it an independent
segment with no parent. Its children remain attached to it, and other
siblings are unaffected (i.e. they still descend from the same parent
that has 'disowned' the given segment).

SEGMENT *find_segment_by_name(SEGMENT *s, char *name)
Returns a pointer to the segment (descended from the given segment)
whose name matches the given name. If none is found, NULL is returned.


For background information on the color mapping system used in REND386, be
sure to read the file "colors.doc".

The application programmer should supply a module that provides support
for color mapping; a sample has been supplied as "colormap.c".

The following function should be defined in the color mapping module:

int user_poly_color(POLY *p, int pcolor, long maxz)
Does the mapping of user-specified color values into whatever format
gets passed to lower-level drawing routines; this is where cosine
lighting is done, for example. The maxz specifies the polygon depth
(so you can do distance-based shading). This routine is called by
the renderer, and might call poly_cosine(p) to determine the cosine of
the lighting angle for shading purposes.

This routines may be changed at will, but bear in mind that this may make
color usage incompatible with data files generated elsewhere.


These routines are the main interface to the renderer.

void setup_render(unsigned k, int maxp)
Sets up the internal data structures used by the renderer; should be
called before doing any other calls to any of the rend386 library
routines. The parameter k gives the Kbytes of memory to allocate,
and maxp is the maximum number of polys that will be rendered in any
frame. (40, 800 suggested).

void reset_render()
Frees memory allocated for internal data structures used by the renderer;
should be called before exiting.

void subrender(OBJLIST *objlist)
Renders all the objects in the given objlist, writing to the current
page (see set_drawpage()). Call render_set_view() first to set viewport.
Especially fast if you have a lot of short objlists to draw.

void render_set_view(VIEW *view)
Recalculates view for rendering, and loads renderer with it.

void render(OBJLIST *objlist, VIEW *view)
Calls render_set_view(view) then subrender(objlist). Use this if
you're rendering 1 or 2 big objlists.

These next three routines support "monitoring" of a point on the screen, to
find the frontmost polygon that is under that point.

On the next rendering of the screen, the system will watch the point in
question and see which polys cover it.

void set_screen_monitor(int x, int y)
Specifies the point to monitor. The x and y values are in screen

void clear_screen_monitor()
Disables monitoring.

POLY *read_screen_monitor()
Returns a pointer to the nearest polygon that covers the monitor point.

This next routine can be called by the user part of the renderer code.

int poly_cosine(void *poly)
Given a pointer to a polygon, returns the cosine of the angle between
the current light source and the polygon's surface normal; the value
returned is actually the cosine times 128. VERY fast (5-10 uS).


These routines are provided by the user (sample source in render.c); you can
implement your own "back end" to the renderer just by rewriting these
routines. BE VERY CAREFUL-- the new video driver makes most of these
obsolete, and you could make your code incompatible if you screw around too
much with them.

void user_setup_blitter()
Sets up the polygon blitter.

void user_reset_blitter()
Resets the polygon blitter.

void user_box(int x1, int y1, int x2, int y2, int color)
Draws a box with top left corner (x1,y1) and bottom right corner (x2,y2)
filled in the given color.

void user_text(int x, int y, int color, char *string)
Displays a string at (x,y) in the given color.

void vgabox(int left, int top, int right, int bottom, int color)
Draw a box on the display at the given location in the given color.
No clipping is done!

void user_render_poly(int number, int *coords, int color, long MAXZ)
Called by the renderer to actually draw polys.
See render.c for details. The maxz values species the
polygon depth, so you can make distance-based polygon rendering

There is also a global flag, an integer called 'wireframe', which, if non-zero,
will cause all objects to be rendered in wireframe mode. Note that this is
in general slower than actual filled-polygon rendering, since the absence of
explicit edge information forces each edge to be drawn twice.


OBJECT *where_screen_pt(int *pol, int *vert, int x, int y)
Given the x, y location of a point on the screen, this routine returns a
pointer to the object that point is on; if pol is not NULL, it is
set to be the index in the object of the polygon the cursor is on.
The vert pointer is currently not used. THIS FUNCTION MAY REDRAW THE

long where_pt(OBJLIST objlist, long x, long y, long z, OBJECT **obj, int *vert)
Finds which object (if any) the point (x,y,z) is contained within, and
sets *obj to point to that object. Also sets *vert to be the index of
the vertex on that object which is closest to (x,y,z) and returns the
distance from (x,y,z) to that vertex. THIS ROUTINE HAS NOT BEEN
EXTENSIVELY TESTED! Caveat programmer.


As of version 4.00, REND386 supports loadable graphics drivers. To load
a driver, use the following call:

void *load_driver(char *filename)
Loads a device driver from the given file into memory, and returns a
handle for it (pointer to segment-aligned start, which is after the 16-byte
pad at the file start).

These routines are loaded from the external device driver file, and provide
low-level access to the display hardware. All display access should go
through these routines, in order to maintain compatibility with new hardware.

void far VGA_select(int card)
Selects which of several VGA cards should be the one written to;
useful for stereoscopic systems that use one display per eye. The
parameter 'card' is the OR of MAIN_VGA, LEFT_VGA, and RIGHT_VGA. You
can use ALL_VGA to refer to all three. (Of course, your video device
driver must support switching multiple VGA cards!). This is supported
with the SEPARATE stereo type only.

void *far screen_data()
Returns a pointer to a screen_info struct that holds the information
for the current display.

void far clipline(int x1, int y1, int x2, int y2, int color)
Does Cohen-Sutherland clipping and draws the line in the given color.

void far set_clip_rect(int l, int t, int r, int b)
Sets the on-screen clipping rectangle. ONLY used for clipline().

void far vsync()
Pauses until the next vertical retrace.

void far set_vpage(int page)
Sets the current video page through the BIOS.

void far setup_hdwe(int mode)
Sets up the VGA card for a series of line or poly draws. The mode
is one of PUT, AND, OR, or XOR; you should usually use PUT.

void far reset_hdwe()
Resets the VGA to BIOS state after drawing.

int far clr_page(int page, int color)
Clears the video page to a solid color. Returns -1 if the page number
is invalid. The Mode Y driver does this in 10 ms per call.

int far copy_page(int source, int dest)
Copies one complete page to another for use as a background. Returns -1
if either page number is invalid. The Mode Y driver does this in 21 mS
per call.

void far vgaline(int x1, int y1, int x2, int y2, int color)
Fast VGA line draw ; the Mode Y driver does about 15600 24-pixel
vectors/sec (much faster for horizontal lines (y1 == y2)).

void far vgapoint(int x, int y, int color)
Sets a point on the screen; no clipping is done.

void far set_gmode()
Enters graphics mode and clears the screen.

void far exit_gmode()
Leaves graphics mode.

int far set_drawpage(int page)
Sets the page to draw into. The Mode Y driver supports pages 0 through 3.

void far fastpoly(int count, int far *pcoords, int color)
Fast multi-sided polygon draw routine. Draws convex polygons with up to
20 sides. The pcoords are a series of X, Y coordinate pairs (x,y,x,y...)
in counter-clockwise order.

void far m_fastpoly(int count, int far *pcoords, int color,
int gmask, int toggle)
Same as fastpoly(), but does color cycling for pseudo-metallic effects
and masking (alternate bits on/off for pseudo-transparency).
The color cycles through the lower 4 bits (0-3), up then down then up
then down. The next 4 bits (4-7) give the hue. Bit 8 is a "sign" bit
for determining the initial cycle direction. The gmask is XOR'd with
the toggle every line to generate a halftone pattern.

void far printxyr(int x, int y, int color, char far *pstring, int reversed)
Prints text on the screen at a given location and in a given color.
If reversed is non-zero, the text is written right-to-left with x now
being the right-hand (rather than left-hand) edge of the text.

void far draw_cursor(int x, int y, int color, int savebuff)
Draws a crosshair cursor on the screen at the given location, saving
what was there before into the given buffer.

void far erase_cursor(int savebuff)
Restores an 8x8 area that was saved by draw_cursor().

int far copy_block(int spage, int sx, int sy,
int dpage, int dx, int dy, int xs, int ys)
Copies a rectangle of pixels from the given location (sx,sy) on the
given source page (spage) to a given location (dx,dy) in a given
destination page. The size of the array to copy is given by (xs,ys).
The left boundary must be a multiple of 8, and the size also.

int far clr_block(int left, int top, int right, int bottom,
int page, int color)
Clears a block defined by (left,top,right,bottom) on the given page
to the specified color. The left side must be a multiple of 8,
and the right side a multiple of 8, minus 1. May be a bit slower than

void far load_DAC_colors(char far *pal, int n, int bw)

The given palette should have n entries of 3 bytes each, whose values
can be between 0 and 63 inclusive. The first of each triple is the Red
value, the second is the Green value, and the third is the Blue value.
If pal is NULL, the default palette is used (but specify n anyway!).
If bw is 1, the palette will be mapped into greyscales.

void far read_DAC_colors(char far *pal, int n)
Read n entries from the palette into the provided buffer.


There is a set of routines for manipulating both 2D and 3D cursors.

The 2D cursor is a simple crosshair, and has support routines are as follows:

void cursor_move(int x, int y)
If the cursor is visible, it is moved to the given screen location.

int cursor_hide()
Hide the cursor if it's visible (an internal counter is decremented;
the cursor is considered visible if the counter is greater than or
equal to zero). The number of the currently displayed page is returned.

void cursor_show(page)
Increment the internal counter, and if it's greater than or equal to
zero the cursor is displayed. The currently displayed page should be
passed as a parameter.

int cursor_forget()
Simply decrements the internal counter, and does not bother hiding the
cursor (since this page will be overwritten soon anyway). Returns the
number of the currently displayed page.

int move_2D(PDRIVER *drvr, int *x, int *y, unsigned *b)
Reads the 2D pointing device associated with drvr; if the position
or button status has changed, the cursor is automatically moved on-screen.
The variables pointed to by *x and *y are filled with the x and y
position of the cursor on-screen, and b is filled with the status of the
pointing device's buttons. A non-zero value is returned if the pointing
device has moved or a button been pushed on it since the last call to
this routine.

int move_till_click(PDRIVER *drvr, unsigned buttmask, int *x, int *y)
Tracks the pointing device specified by the given drvr, until the button
status on that device (AND'ed with the given buttmask) yields a non-zero
value. The *x and *y variables are filled in with the on-screen position
of the pointing device.

The following routines handle 3D pointing device cursors:

void pointer_to_world(POINTER *p, VIEW *v, long *x, long *y, long *z)
Updates the world coordinates of the pointer information in the given
POINTER structure (see the section on POINTERS). The x, y, and z
variables are filled in with the view-relative coordinates of the
pointing device.

void rotate_to_view( VIEW *v, long *x, long *y, long *z)
Rotates the given x, y, z values using the viewing transform.

int glove_update(PDRIVER *drvr, POINTER *p)
Reads the glove device specified by drvr, and updates the position and
orientation information in the given POINTER struct. See the section
on POINTERS for more details.

int cursor_update3D(PDRIVER *d, POINTER *p) /* read pointer, update positions */
Reads the 3D pointer device specified by drvr, and updates the position
and orientation information in the given POINTER struct. See the section
on POINTERS for more details.

SEGMENT *manip_data(PDRIVER *d, long *x, long *y, long *z)
Sets *x, *y and *z to be the location in world coordinates of the
pointing device, and returns a pointer to the segment corresponding
to the pointing device.


The package provides support for reading a joystick. The following struct
describes the information associated with a joystick port:

typedef struct {
int x, y, buttons;
int cenx, ceny;
int xrange, yrange;
long scale;
int port; /* port number, 0 or 1; -1 means 'unused' */
} joystick_data;

The x, y and buttons fields are the values read from the joystick. The
cenx and ceny values are the values of x and y that are read when the joystick
is centered; the values the application program sees are relative to these
center values, so (0,0) is a centered stick.

The xrange and yrange values are the maximum values for x and y; the scale
factor is the ratio of movement on the screen to movement of the joystick.

The value the user sees after a call to joystick_read() is, in the case of
x, ((x_as_read - cenx) * scale)/xrange.

The port value specifies which of the two possible joysticks this structure
corresponds to.

int joystick_check()
Checks for the presence of one or two joysticks; the returned value has
the low-order bit set if joystick 1 is present, and the next higher bit
set if joystick 2 is present.

void joystick_init(joystick_data *joy, int port)
Initializes the joystick on the given port (0 or 1)

void joystick_setscale(joystick_data *joy, int value)
Sets the scale factor for the given joystick

void joystick_scale(joystick_data *joy, int dir)
Used for joystick calibration; should be called with dir = 0 and joystick
forward and to the left, then again with dir = 1 and joystick backward and
to the right. This sets the xrange and yrange values for you.

int joystick_read(joystick_data *joystick)
Reads the current values of the specified joystick.

void joystick_quit()
De-initializes joystick processing (currently does nothing)


Support is provided for 3-dimensional pointing devices. These are supported
by pointer drivers and by the .cfg files. Not all the possible manipulation
methods are implemented, but 2D, 2D plus mapping to Z axis, 3D, and 6D
are supported with cursors, as well as gloves.

The routines documented here are the low-level pointer driver interface.
There are others, but there hasn't been time to document them yet.

The following structure is used to return information about the current
location, orientation, and control-status information about a 3D pointer:

typedef struct {
long x, y, z; /* location in scaled coordinates */
long dx, dy, dz; /* position change from last read */
long rx, ry, rz; /* scaled orientation around x, y, z */
long drx, dry, drz; /* orientation change from last read */
unsigned buttons; /* 16 bits: raw mouse buttons */
unsigned gesture; /* glove gesture ID, or a mapping */
unsigned keys; /* keypad return value */
int flex[16]; /* 16 words of flexion (i.e. fingers) */
int changed;
int wpos_valid; /* set if world pos'n is valid */
MATRIX wpos; /* world position/rotation */

The x, y and z values are the location of the pointer in 0-centered coordinates.
The rx, ry and rz are the rotation of the pointer in 16.16 format. Every
time you call pointer_read(), it compares the current values to the ones
it last read, and stores the differences in dx, dy, dz and drx, dry, drz.
These will not be updated if the databits field of the pconfig structure
indicates they are not available.

The buttons represent up to 3 buttons, each of which is down or up;
Again, there is a mask field in databits in pconfig.

The flexion information is only meaningful for glove-type devices.
For gloves, it is 10 entries from 0 (straight) to 255 (fully bent) in the
order (T, I, M, R, P) for fingers, and (base, distal) bend joint.

The keys field indicates any keys that may be on the device (e.g. the keypad
on the Nintendo PowerGlove). The nullkey field in pconfig gives the
"no-key" code (see pdriver.doc for details).

The changed flag is set if the data has changed since the last read. It
is set by pointer_read() (see this function for the codes).

The routine pointer_to_world() (see section on CURSOR ROUTINES for details)
will update the world transform matrix and set the wpos_valid flag; this flag
is cleared when fresh data is read in.

The gesture field is provided for those systems that support gesture
recognition. Some codes are identified in the pointer.h file. The glove
driver must do the recognition.

The following routines provided support for pointer devices:

PDRIVER *pointer_init(int type, char *driverfile)
Initializes a driver, and returns a handle for reading data from it.
The type field is a subset of the bits in the type field of pconfig,
and can configure some pointer drivers.
The given driverfile name is the name of a device driver file that
gets loaded in ("mouse", "sega", and "pglove" are built-in). If the
device cannot be initialized, NULL is returned.

PCONFIG *pointer_check(PDRIVER *drvr)
Checks the current status of the given device, and returns a pointer
to the pconfig struct that describes the characteristics of the device.

unsigned pointer_read(PDRIVER *drvr, POINTER *pointerdata)
Reads the pointing device associated with drvr, and updates the fields
in the given pointer data struct. Returns a "changed" mask that's the
bitwise OR of the following:

PNEW_POS position in 3-space has changed
PNEW_ROT orientation has changed
PNEW_BUT button status has changed
PNEW_GEST gesture state has changed
PNEW_KEY a key has been pressed/released
PNEW_FLEX the flexion information has changed

int last_pointer(PDRIVER *drvr, POINTER *p)
Similar to pointer_read(), but returns the previously read value (i.e.
does not re-read the pointer device.

void pointer_reset(PDRIVER *drvr)
Resets the specified pointer driver to a known state.

void pointer_quit(PDRIVER *drvr)
Shuts down the specified driver.

PCONFIG *device_command(PDRIVER *drvr, int command)
Sends a command to the pointer device (sort of like an ioctl() call).
Possible commands are bits of the commands field of pconfig.

void pointer_tscale(PDRIVER *drvr, long x, long y, long z)
Sets the amounts by which translation should be scaled in x, y and z.
The range will be set to +/- the given value.

void pointer_rscale(PDRIVER *drvr, long rx, long ry, long rz)
Sets the amounts by which rotation about the x, y and z axes.
The range will be set to +/- the given value in (16.16) format.

void pointer_abscale(PDRIVER *drvr, long xs, long ys, long zs,
long rxs, long rys, long rzs)
Sets the absolute scale values for translation and rotation.
This means that the xres etc. values are used to compute a scale
so that the returned positions are in 1 mm per unit, and the
rotation in <16.16> degrees (real world measures).

int mouse_read(PDRIVER *drvr, int *x, int *y, unsigned *b)
Emulates a mouse driver with a pointer device; sets *x and *y to
screen position, and *b to button status. Returns non-zero if the
values have changed.

int mouse_last(PDRIVER *drvr, int *x, int *y, unsigned *b)
Similar to mouse_read(), but returns the previously read position
(i.e. does not read the pointer device).

int set_mouse_limits(PDRIVER *drvr, int maxx, int maxy)
Sets the maximum X and Y limits for the mouse device.


These routines allow you to provide a simple user interface.

void neatbox(int w, int h, int *x, int *y)
Displays a neat-looking box of height h and width w, centered on the
screen. The *x and *y values are set to the computed top-left corner
of the box.

void poptext(char *text[])
Pops up text box on the screen; the 'text' parameter is an array of
strings, the last of which is NULL.

void popmsg(char *msg)
Pops up a one-line message in a box centered on the screen.
Especially useful for debugging.

unsigned askfor(char *prompt, char *buff, int n)
Prompts the user to enter a string; buff must contain room for up to n
characters plus a null byte to terminate the string.

int menu(char *text[])
Pops up a menu of text strings, and returns the index of the one
the user selects (either with a pointing device or by hitting the
first letter of any entry)


The ".plg" file format is described in plg.doc; it is not tightly coupled to
the rest of the renderer software.

OBJECT *load_plg(FILE *in)
Loads a .plg file and returns a pointer to the newly-created OBJECT.
The global variable load_err will be set to a non-zero value if there
was a problem loading the file. As the file is loaded, it is first
scaled by the current scaling factor and then shifted by the current
x,y,z offset.

OBJECT *load_multi_plg(FILE *in)
Works just like load_plg(), but loads a series of representations
from the plg file and stores them all into the object it creates.
See plg.doc for details on multi-rep .plg files.

void set_loadplg_offset(long x, long y, long z)
Specifies the x,y,z offset to use; see figure.doc for details.

void set_loadplg_scale(float x, float y, float z)
Specifies the x,y,z scaling to use; see figure.doc for details.

save_plg(OBJECT *obj, FILE *out)
Saves the given object as a .plg file.

void set_loadplg_depthsort(type)
Sets the depth-sorting type to a particular value (see description of
depth sorting earlier in this document for details).

void set_loadplg_colormap(unsigned *map, int msize)
If the top bit of a color value in a .plg file is set, the bottom
15 bits are taken to be an index into a "map" that specifies the
actual color value to be used. The map consists of msize values,
each of which is an unsigned 16-bit number.

void strip_comment(char *buff)
A utility routine (used outside of .plg file parsing) that strips
off everything after the first '#' character in the given buffer.

In addition, a global int variable called load_err is set to a non-zero
value if an error was encountered while loading a .plg file; see the code in
plg.c for details.


A figure file consists of a set of segments. See figure.doc for details.
The format of a figure file is not tightly coupled to the rest of the

SEGMENT *readseg(FILE *in, SEGMENT *parent)
Allocates a new segment, with the given parent, and reads a segment
description into that segment. If the file contains nested segments,
readseg will recursively call itself to load them. See figure.doc for
more information. The global integer variable readseg_err will be set
to a non-zero value if an error was encountered during loading.

void set_readseg_scale(float x, float y, float z)
Specifies a set of values that get used to "pre-scale" loaded plg files.
Note that this is used in addition to the plg scaling; i.e. the two
scale factors are multiplied together to give the actual factor.

void set_readseg_objlist(OBJLIST *objlist)
Sets the given object list as the one to add newly-loaded representations

void set_readseg_seglist(SEGMENT **seglist, int maxsegs)
Segments in .FIG files now have an optional "segnum" attribute that
associates a simple integer with a the segment. This allows you to
subsequently refer to segments by number. The given seglist is an
array of pointers to segments that you supply, which will get filled
in as segments get loaded. The value maxsegs specifies the number of
slots in the seglist. Note that you can have as many seglists as you
like, possibly even one for each figure.

writeseg(FILE *out, SEGMENT *s, int level)
Writes the given segment out to a file; 'level' is used to provide
levels of indentation, since writeseg calls itself recursively. In
general, root segments should be written out with level = 0.

In addition, a global int variable called readseg_err is set to a non-zero
value if an error was encountered while loading a .fig file; see the code in
segio.c for details.


These routines are only available in Mode Y. They allow you to read
and write .PCX files (PC-Paintbrush format). THESE ARE NOT SUPPORTED
BY THE VIDEO DRIVERS, so use at your own risk!

load_pcx(FILE *in, int page)
Load a 320x200x256 .PCX image from the given file onto the given
display page.

int save_pcx(FILE *out, int page)
Save a 320x200x256 .PCX image into the given file from the given
display page.


The rend386 package supports stereoscopic rendering. The structure
STEREO is used to store information about the stereo environment.

The fields in this structure are:
phys_screen_dist - the distance in mm from the eye to the screen
phys_screen_width - the viewport width on the screen in mm
pixel_width - the viewport width in pixels
phys_eye_spacing - the physical spacing between the eyes, in mm
world_scaling - world units per physical mm
phys_convergence - the convergence distance in mm (usually the same
as phys_screen_dist)

All units are stored as long integers.

The stereo_type variable is set to one of:

#define MONOSCOPIC 0 /* monoscopic */
#define SWITCHED 1 /* time-multiplexed (like Sega glasses) */
#define SPLITLR 3 /* seperate left, right windows on screen */
#define SEPERATE 5 /* seperate VGA cards for left, right */

The following routines support stereoscopic rendering:

void compute_stereo_data(STEREO *stereo, int eye, int xflip, int xdist,
long yr, long left, long top, long right, long bottom );
Computes stereo parameters and caches them for use with make_stereo_view().
Eye is 0 (left) or 1 (right). xflip will flip left to right on this eye
(mirror). xdist is a pixel offset of the center of the window used mostly for
HMDs or split-screen displays. yr is a <16.16> angle to "tilt" the image plane
about the Y axis (for HMDs, etc). The left, top, bottom, and right are
the viewport for this eye (useful mostly for split screens)-- use the
viewport clipping if not needed.

void make_stereo_view(VIEW *root, VIEW *nview, int eye);
Given a view (the center or cyclopean view) will fill in nview with
the view for that eye. Calls initialize_view_factors() so is a bit

void update_stereo_view(VIEW *v, STEREO *s, int eye);
Sufficient if only view point has changed and no twist or offset.

void default_stereo_setup(VIEW *v, STEREO *s);
Sets up a Sega time-multiplexed view.

See the routine refresh_display() in demo4.c for an example of how all this


The Sega 3D glasses are now supported by rend386. See the file sega.txt for
details on how to build an interface for the Sega glasses that allows them to
be connected to a serial port; the sega.txt file is in

The Nintendo Powerglove is now supported by rend386. See the article in the
July 1990 Byte magazine for details on constructing a cable that allows the
glove to be connected to a parallel port.

but may be useful otherwise:

In order to use the Sega glasses or the Nintendo Powerglove, you must call
the following routine:

void init_SG_interrupt(void (*sega_switcher), void (*glove_handler), int tc)
Installs interrupt support for the Sega glasses and the Nintendo glove.
The sega_switcher() routine will be called just before the vertical
retrace triggers, and the glove_handler() routine will be called at
least once per screen refresh. If either routine is NULL, then that
routine will not be called. The tc value indicates the interval (in
units of 1.19 microseconds) for polling the glove; 6500 is a good
value to use if the sega_switcher is also being used.

If you do not use this, call init_timer() to start the timing
for animation and speed control. See demo4.c for more information.

Two routines are provided which may be passed as parameters to the
init_SG_interrupt() routine: switch_sega() and glove_int_handler().

Starting with version 4 of REND386, it is possible to load a driver for
doing the left/right switching.

init_switch_driver(char *filename)
Loads a switching driver from the given file.

void switch_sega(to_go)
Switches the sega glasses from left eye to right eye or vice-versa, and
swaps the screen as well. To_go will be 0 for switching, 1 for the
video page switch.

int left_page;
int right_page;
The global variables left_page is the number of the screen page that
contains the left-eye image, and right_page is the number of the screen
page that contains the right-eye image. In Mode X, four pages are
available altogether, so you can have an "active" left-right pair and a
"visual" left-right pair.

void sega_off()
The routine sega_off() can be used to make both lenses on the Sega
glasses transparent; it should be called before exiting to avoid
damaging the glasses.

void select_sega_port(int port)
Specifies the i/o port address at which the Sega controller is found.

int sega_port_image, sega_mask, sega_left, sega_right, sega_doff;
The meaning of these global variables is defined in the SEGAPORT
description in the world.doc file.

The following struct contains data from the Powerglove:

typedef struct {
signed char x, y, z, rot, fingers, keys, gstat1, gstat2, rxflags;
unsigned int nmissed;
} glove_data;

The x, y, and z values are the location of the glove in three-space; the
rot value is the rotation position (0 to 12). The fingers are a one-byte
bitmap, two bits for each of the thumb and three fingers (pinky not included).
The keys value indicates which keys are down on the wrist control panel.

The following routines can be used to interface to the Powerglove:
(but use of the "pglove" driver is recommended).

void glove_init(int gdeg)
Sets up the glove and enters hi-res mode. If gdeg is non-zero, deglitching
is enabled.

int glove_ready()
Returns 0 if the glove is not ready, 1 if data is ready, 2 if data is ready
and the rxflags are valid.

int glove_read(glove_data *gdata)
Reads the glove data into the given struct, and returns non-zero if the
data has changed since the last time the routine was called.

long gesture_time;
This global variable counts the number of glove-reads for which the
current gesture has been seen.

unsigned char gesture_type;
The current gesture, after mapping. See segasupp.h for definitions of
possible values.

In order to allow greater configurability, the following global variables
are available and may be set by the user:

glove_in_port, glove_out_port, glove_write_mask, glove_none_mask,
glove_latch_mask, glove_clock_mask, glove_clock_latch, port_image

All are ints and are describe in the PGLOVEPORT entry in the world.doc file.

The timing may also be configured by altering the glove_bit_delay and
glove_byte_delay values; again, both are ints.

Note that there is still considerable inherent glitching in the glove due
to noise, reflections and so forth.

See the demo file for other details.


Before reading this section, be sure you've read "splits.doc" (which
describes the basic ideas behind splitting planes).

A splitting plane divides space into two parts, each of which can be
further subdivided into two parts by another plane, and so on. The
resulting is a binary 'splitting tree' which partitions space into

SPLIT *add_split(SPLIT **tree, long x, long y, long z,
long nx, long ny, long nz, unsigned flags)
This routine creates a splitting plane that passes through the given
point [x,y,z] and has a normal pointing in the [nx,ny,nz] direction.
The 'tree' parameter is a pointer to a pointer to a splitting tree,
and the function returns a pointer to the newly-created split.

AREA *what_area(SPLIT *tree, long x, long y, long z)
Returns a pointer to the area containing the point [x,y,z].

void add_obj_to_area(AREA *a, OBJECT *obj)
Adds an object to the list of objects contained in the given area.

void add_obj_to_split(SPLIT *tree, OBJECT *obj)
Adds an object to the list of objects. Finds the correct split, then
adds object to list on split side (area) or on the split plane
itself (not recommended).

void split_move_handler(OBJECT *obj)
When an object moves around, it may wind up in a different place
in the splitting tree; this function gets called to move an
object in the tree as needed.

void initial_world_split(SPLIT **split_ptr)
Creates an initial split to define the "world", and stores it in the
given pointer. Typical usage would be:
SPLIT *world;

void set_global_split_root(SPLIT **split_tree)
Causes the given split tree to become the root of the splitting tree
describing the world. Note that it is entirely possibly to have
multiple worlds, all independent and all having their own splitting

OBJLIST *which_area_objlist(SPLIT *tree, long x, long y, long z)
Returns the objlist for the area containing the given point [x,y,z].

OBJLIST *which_objlist(SPLIT *tree, long x, long y, long z)
Returns the objlist for the splitting tree that divides the area
containing the point [x,y,z].

void add_obj_to_split_area(SPLIT *tree, OBJECT *obj)
Adds an object to the objlist for the appropriate area within the
given split tree. Recommended if object is NOT supposed to be
a wall or surface to divide areas.

void add_obj_to_split_center(SPLIT *tree, OBJECT *obj)
Adds an object to the objlist for the given split in the tree.
Used to place objects on area divisions (e.g. walls).

void render_monitor_point(int x, int y)
Defines a point on the screen for the 'monitor' routines; the next
time the scene is rendered, the object and polygon 'under' this point
and nearest the user will be noted. This information may be retrieved
using render_check_monitor().

OBJECT *render_check_monitor(int *poly, int *vert)
Return a pointer to the object nearest the user and 'under' the point
on the screen specified in the most recent call to the function
render_monitor_point(). The poly pointer (if not NULL) is set to
the number of the polygon (within the object's polygon list) of the
polygon 'under' the screen point, and the vert pointer (if not NULL)
is set to the index of vertex (within the object's vertex list) whose
projection onto the screen is nearest the screen point.

void render_objlist(OBJLIST *objlist)
Renders the given object list.

void render_area(AREA *a)
Renders the objects contained in the given area.

void render_split(SPLIT *tree, VIEW *view)
Renders the objects contained in the given splitting tree, from the
given view. The tree is walked such that objects on the far side of
any given plane (relative to your view) are rendered first, followed
by objects "on" the splitting plane, followed by objects on the near
side of the plane. This is done recursively, and eliminates visibility
errors between areas.

void walk_area(AREA *a, void (*fn)())
Walks the list of objects in the given area, calling the given function
on each object found.

void walk_split_tree(SPLIT *tree, void (*fn)())
Recursively walks down the given splitting tree, calling the given
function on each object found.

void add_floor(AREA *area, long a, long b, long c, long d)
Defines a plane (ax + by + cz + d) giving the tilt of the 'floor'
in the given area.

void add_ceiling(AREA *area, long a, long b, long c, long d)
Defines a plane (ax + by + cz + d) giving the tilt of the 'ceiling'
in the given area.

long floor_at(AREA *a, long x, long z)
Finds the height of the 'floor' at a point [x,z] in the given area.

long ceiling_at(AREA *a, long x, long z)
Finds the height of the 'ceiling' at a point [x,z] in the given area.

void set_area_function(AREA *a, void (*fn)())
Associates a function with the given area; the function can be called
when you enter an area, for example.

void call_area_fn(AREA *a)
Calls the function associated with the given area; the function is
passed the area as a parameter.


REND386 now takes over the timer hardware; it chains to the existing handlers
at appropriate intervals, but you can do more accurate timekeeping than would
be possible using the standard 18.2 ticks/second timer.

void init_timer()
Initializes the timer into high-resolution mode. The timer will be
automatically set back to normal when your program exits.

long current_time()
Returns the current time in high-speed ticks.

void set_current_time(long newtime)
Allows you to set the current time value returned by the current_time()
function. The time will, of course, advance from that point onwards.

long get_ticks_per_second()
Returns the number of ticks per second in high-resolution mode.

volatile interrupts_occurred;
This flag can be used to determine whether interrupts have occurred
during a given period of time. To use it, set its value to zero and
then perform other activities. You can check the flag periodically
to see if interrupts have occurred.

The renderer code in render.c updates the variable last_render_time
which records the time (in ticks) to redraw the screen. Useful for
scaling speeds, etc. or computing the drawing speed.


A very primitive "multi-tasking" system has been incorporated into REND386.

TASK *add_task(TASK **tasklist, void (*fn)(), long period, void *param)
This will create a 'task', adding it to the given tasklist. The parameter
'fn' is the function defining the tasks, 'period' is the number of ticks
that should elapse between calls, and 'param' is an arbitrary parameter
to be passed to the 'fn'.

void del_task(TASK **tasklist, TASK *tsk)
Deletes a task, removing it from the given tasklist and releasing its

void run_tasks(TASK *tasklist)
Should be called regularly in polling loops, etc to run the tasks in
the given task list.

TASK *get_current_task()
Returns the id of the currently-running task; typically called by a
task function.

void *find_task_data(TASK *task)
Returns a pointer to the task-specific data. Typically called by a
task function.


This documentation is still incomplete, but we're releasing source
so you can find out other routines' specs as needed.

This rendering package is intended to be the foundation for a wide range
of applications.

It is suggested that radical changes not be made if you want the code to
be at all portable. Redefining the standards and ideas in REND386 is
also discouraged without consultation, as there are many implicit concepts
in the code for future expansion. Suggestions are always welcome.

For further information about the package, feel free to contact us:

Bernie Roehl ([email protected])
Dave Stampe ([email protected])

Note that the ".ca" stands for CAnada, not CAlifornia!

The major contribution others can make to the project at this stage is
to write converters from other polygon formats to OFF. In particular,
a DXF to OFF converter would let us create objects with a CAD package.

OFF is a reasonably good, open format that encodes author/copyright
information along with geometry and colors. While it's a bit of a
nuisance to parse, it's very easy to generate.

Two of our data files were converted from OFF files using off2plg: the bishop
and the banana. Below is the author/copyright information from their .aoff

name bishop8
description chess piece - bishop
author Randy Brown, [email protected]
copyright (c) Randy Brown, OK to distribute if copyright/author appears

name banana
description Banana made on Frank Crow's U. Utah surface design system
copyright (c) Ohio State Univ. - ok to distribute if copyright appears

That's it. Please direct any questions to [email protected] or
[email protected], or join the mailing list. To join the list,
send mail to [email protected] (the list itself is just
[email protected]).

 December 19, 2017  Add comments

Leave a Reply