home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 16
/
CD_ASCQ_16_0994.iso
/
news
/
vr386
/
vr_api.h
< prev
next >
Wrap
C/C++ Source or Header
|
1994-01-10
|
89KB
|
2,278 lines
// FULLY DOCUMENTED VERSION
/*
This code is part of the VR-386 project, created by Dave Stampe.
VR-386 is a desendent of REND386, created by Dave Stampe and
Bernie Roehl. Almost all the code has been rewritten by Dave
Stampre for VR-386.
Copyright (c) 1994 by Dave Stampe:
May be freely used to write software for release into the public domain
or for educational use; all commercial endeavours MUST contact Dave Stampe
(dstampe@psych.toronto.edu) for permission to incorporate any part of
this software or source code into their products! Usually there is no
charge for under 50-100 items for low-cost or shareware products, and terms
are reasonable. Any royalties are used for development, so equipment is
often acceptable payment.
ATTRIBUTION: If you use any part of this source code or the libraries
in your projects, you must give attribution to VR-386 and Dave Stampe,
and any other authors in your documentation, source code, and at startup
of your program. Let's keep the freeware ball rolling!
DEVELOPMENT: VR-386 is a effort to develop the process started by
REND386, improving programmer access by rewriting the code and supplying
a standard API. If you write improvements, add new functions rather
than rewriting current functions. This will make it possible to
include you improved code in the next API release. YOU can help advance
VR-386. Comments on the API are welcome.
CONTACT: dstampe@psych.toronto.edu
*/
#include "vr_types.h"
#ifndef PTRBDEF
typedef void PDRIVER;
#endif
#ifndef PTRDEF
typedef void POINTER;
#endif
/////////////////////////////////////////////////////
//// ////
//// VR-386 API HEADER FILE AND DOCUMENTATION ////
//// ////
//// 8/1/94 BY DAVE STAMPE ////
//// ////
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////
//// ////
//// OBJECTS: AN ENCAPSULATION METAPHOR FOR ////
//// ////
//// CREATION AND ANIMATION OF VIRTUAL WORLDS ////
//// ////
/////////////////////////////////////////////////////
// OBJECTS in VR-386 come in three flavors: fixed, moveable,
// and invisible. All are pointed to by the OBJECT type.
//
// Fixed objects are visible but cannot be moved, and
// make most efficient use of memory and processor time.
//
// Moveable objects are visible, and can be moved and
// animated. It is possible to convert objects between
// fixed and moveable types.
//
// Invisible objects are equivalent to SEGMENTS in
// REND386, and act as invisible links in a movement tree.
// In fact, a moveable object is simply a fixed object
// (a VISOBJ) associated with an invisible object (SEGMENT).
// An OBJECT pointer can point to either a VISOBJ or a
// SEGMENT. You can test what the pointer actually is
// poining to with these macros:
#define is_object_visobj(p) (*((WORD *)p)&IS_VISOBJ)
#define is_object_segment(p) (*((WORD *)p)&IS_SEGMENT)
// This function is more general:
// tests if object visible
BOOL is_object_visible(OBJECT *obj);
// You can test if an object is fixed with:
#define is_object_moveable(p) (*((WORD *)p)&IS_MOVEABLE)
// The segments used to move CAMERAs and LIGHTs, as well
// as the body_seg and head_seg associated with the
// VR body cannot be directly deleted, and are marked
// as system owned. They can be identified with:
#define is_system_owned(p) (*((WORD *)p)&SYSTEM_OWNED)
// To use older REND386 segment functions, you must
// convert the object to a segment. You can do this
// conversion with the following functions:
// (These return NULL if given a NULL argument)
// pointer to VISOBJ of OBJECT: NULL if inviible object
VISOBJ *object2visobj(OBJECT *p);
// pointer to SEGMENT of OBJECT: NULL if fixed object
SEGMENT *object2segment(OBJECT *p);
// also see make_fixed_object_moveable() below.
/////////////////////////////////////////////////////
//// ////
//// OBJECT SELECTION ////
//// ////
/////////////////////////////////////////////////////
// OBJECTs may have several characteristics. Selected
// objects appear highlighted (outlined in wireframe).
// An object is selected by highlighting it, either
// explicitly or by clicking on it with the mouse:
// set highlight flag
void highlight_object(OBJECT *obj);
// resets highlight flag
void unhighlight_object(OBJECT *obj);
// tests if visible and selected
BOOL is_object_selected(OBJECT *obj);
// Some objects can be made unselectable: for example, pointers
// or the user's body. You can test or set this with:
// tests if object can be selected
BOOL is_object_selectable(OBJECT *obj);
// force object to be unselectable
void make_object_unselectable(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT LISTS ////
//// ////
/////////////////////////////////////////////////////
// OBJECTS are organized in several ways. For storing
// or manipulating several objects, they can be placed
// on an OBJLIST. This is a double-linked list allowing
// for rapid removal. Each list has a header to allow
// easier removal of the first object in the list. The
// list holds the VISOBJ part of objects, and cannot hold
// invisible objects (segments).
// OBJLISTs are used to store loaded objects for
// processing before they are inserted into the
// world, and there is a list (inactive_object_list)
// containing objects removed from the world (i.e.
// alternative versions of objects).
//
// OBJLISTs should only be used to hold objects that
// are not in the world itself, since the objects in the
// world are held in "area" OBJLISTs, and switch between
// OBJLISTs if they are updated or moved.
// OBJLISTS headers can be created to attach objects to, or you can
// use the pre-created system OBJLIST default_objlist:
extern OBJLIST * default_objlist; // used for loading objects
extern OBJLIST * inactive_object_list; // all objects not currently in world
// create a new objlist
OBJLIST *new_objlist();
// These functions dispose of OBJLIST headers
// If the list is not empty, they keep or destroy
// the VISOBJs on the list
// delete objects on list then delete objlist
void delete_objlist_and_objects(OBJLIST *list);
// delete objlist, but just remove objects from list
// safer if not sure all objects are off list
void delete_objlist_keeping_objects(OBJLIST *list);
// These functions place or remove objects to an OBJLIST.
// It is not recommended to handle world objects diectly:
// remove or add objects to the world using functions in
// the WORLD section first:
// drop object from list it's on
void remove_from_objlist(OBJECT *obj);
// put object at head of list
// remove if already on any list
void add_to_objlist(OBJLIST *list, OBJECT *obj);
// Merging OBJLISTS is safe: Especially useful to add
// lists of loaded objects to the inactive_object_list
// removes all from <from>, adds to start of <to>
void merge_objlists(OBJLIST *from, OBJLIST *to);
// In many cases, you need to perform a function on all
// objects in a list. Leter we will see similar "walking"
// functions for object trees and the world.
void walk_objlist(OBJLIST *objlist, void (*fn)(OBJECT *));
// These are object list-status functions, included for completeness
// is it currently in a list?
BOOL on_any_objlist(OBJECT *obj);
// look for head of list object is in.
OBJLIST *which_objlist(OBJECT *obj);
OBJECT *first_in_objlist(OBJLIST *objlist);
OBJECT *next_in_objlist(OBJECT *obj);
OBJECT *prev_in_objlist(OBJECT *obj);
BOOL is_first_in_objlist(OBJECT *obj);
BOOL is_last_in_objlist(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT TREES, ATTACHMENT AND LINKED MOTION ////
//// ////
/////////////////////////////////////////////////////
// OBJECTS may be attached to each other for the purpose
// of controlling motion. The links between objects are
// "joints", which may be moved or rotated. Many objects
// can descend from one "parent" object, and will all move
// as if part of the parent object when it moves, with
// additional effects from their own joints.
//
// Effectively, there are two "poses" (combinations of
// position and rotation) for each object: the "local"
// or "joint" position, which is the difference between
// the parent and "child" object poses. The "world" pose
// is the actual pose that the object has in the world,
// which is the composite of the parent object's world pose
// and the joint pose of the object.
//
// An object tree can be considered to be a single articulated
// object, with the "root" object being used to move the object
// in the world. This root object (and any object in the tree)
// may be visible or invisible.
// The basic operation is to attach and detach objects from the
// tree. Because the pose of the object depends on that of its
// parent object, the joint pose is rarely the same as that of
// the world pose when the object is attached. So we must choose
// whether to preserve the joint or world pose of an object when
// attaching or detaching it. While we could always choose to
// preserve world pose when attaching or detaching objects, it's
// somewhat expensive on processor time since we may want to immediately
// reset the joint pose of the object.
/* assumes parent is updated! */
// preserves world position if flagged
void attach_object(OBJECT *obj, OBJECT *to, BOOL preserve);
// this is the least expensive attach, and
// does no updates or calculations
// be sure to update <to> later!
void naked_attach_object(OBJECT *obj, OBJECT *to);
// detaches object: preserves world position if flagged
void detach_object(OBJECT *obj, BOOL preserve);
// Here are some informational functions for object trees:
// finds the basic object of a tree
OBJECT *find_root_object(OBJECT *obj);
// tests relationship of objects
BOOL is_object_child_of(OBJECT *parent, OBJECT *child);
// tests for any relationship of objects
// i.e. are they in the same tree?
BOOL are_object_related(OBJECT *o1, OBJECT *o2);
// Much like the OBJLIST-walk functions, these functions will
// execute a function for each object in the tree. They can
// process objects that have certain characteristics. They can either
// process the given object and its decendants, or all objects
// in the same tree as the object.
// object and children:
// execute for object and descendants
void do_for_all_child_objects(OBJECT *obj, void(* fn)(OBJECT *));
// execute for objects and descendants-- ONLY visible objects
void do_for_visible_child_objects(OBJECT *obj, void(* fn)(OBJECT *));
// execute for objects and descendants-- ONLY highlighted objects
void do_for_selected_child_objects(OBJECT *obj, void(* fn)(OBJECT *));
// all objects in tree:
// execute for entire object tree-- ONLY visible objects
void do_for_visible_related_objects(OBJECT *obj, void(* fn)(OBJECT *));
// execute for entire object tree
void do_for_all_related_objects(OBJECT *obj, void(* fn)(OBJECT *));
// execute for entire object tree-- ONLY highlighted objects
void do_for_selected_related_objects(OBJECT *obj, void(* fn)(OBJECT *));
// You may also want to delete the objects in the tree
// completely. This can be done for the children of the
// object, or for the entire tree if find_root_object(obj) is
// used.
// delete object, descendents and objects
void delete_object_and_children(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT POSE, MOTION AND UPDATING ////
//// ////
/////////////////////////////////////////////////////
// OBJECT motion in VR-386 has several stages to increase efficiency.
// In the first stage, moveable objects may have their "pose"
// (position and rotation) modified individually. Since any
// descendent object may have their position affected by this,
// the effect of all motions must be propagted through the tree
// by and update step. (This step is done automatically by
// animation functions, as we'll see later).
//
// Finally, the renderer will determine which objects are visible
// and complete the movement of the object by recomputing their
// vertex positions. Each vertex in a VR-386 object has two
// sets of coordinates: object (ox,oy,oz) which determine the object
// shape, and world (x,y,z) which are updated by the renderer by
// computing them from object world pose and the object coordinates.
//
// Internally, the SEGMENT part of an object contains two matrices:
// a world-position (pmatrix) and a joint position (jmatrix).
// The world matrix is modified by any motions of objects theat the
// object is attached to; the jmatrix is modified by setting object
// pose or by attaching/detaching the object from others.
// To find the true pose of the object (world or joint) may require
// that we look at the angles in the matrix rather than the cached
// record of the last pose change. This is somewhat more expensive,
// but is sometimes needed..
// A POSE is the basic structure used for movement in VR-386.
// It has the following definition:
//
// typedef struct {
// COORD x,y,z;
// ANGLE rx,ry,rz;
// } POSE;
//
// A special value for the fields of the POSE is:
//
#define DONTCARE 0x80000000L
//
// which says that the position or angle of that entry is unspecified,
// and (if the POSE is being applied to an object) that the value
// of that part of the current object pose will not be changed.
//
// Two predefined values for initializing POSE structures are defined:
#define DONTCARE_POSE { DONTCARE,DONTCARE,DONTCARE,DONTCARE,DONTCARE,DONTCARE }
#define ZERO_POSE { 0,0,0,0,0,0 }
// For example, most POSE structures should be initialized to ZERO_POSE.
// You can use DONTCARE_POSE initialization if you're only going to be
// changing a few field of the object pose, for example:
//
// POSE p, pp = DONTCARE_POSE;
// get_object_pose(obj, &p);
// pp.rx = p.x + float2angle(5);
// set_object_pose(obj, &pp);
//
// which will increase the rotation angle about the X axis of the object's
// joint (or world, if the object is unattached) by 5 degrees.
// Here are object pose functions:
// get local "joint" pose of object if moveable
void get_object_pose(OBJECT *obj, POSE *p);
// get "real" pose from joint matrix in object
void get_object_matrix_pose(OBJECT *obj, POSE *p);
// get global physical pose of object from world matrix
void get_object_world_pose(OBJECT *obj, POSE *p);
// set the "joint" pose of object
// ignores DONTCARE values in pose
void set_object_pose(OBJECT *obj, POSE *p);
// place object in world, even if attached!
// can be expensive, especially if DONTCARES used
void set_object_world_pose(OBJECT *obj, POSE *p);
// faster way to get object position than world_pose
void get_object_world_position(OBJECT *obj, COORD *x, COORD *y, COORD *z);
// There are also a lot of older SEGMENT-based REND386 functions available.
// If you use these, be sure to use object2segment() to do the
// translation first!
// Updating of the object tree proceeds from the specified object
// down through its descendents. The objects are scanned to see
// if any have changed: if so, the world pose of the changed
// object and all its descendents are recomputed.
//
// Updating is also essential if an object has been changed
// in any way, so that the changes will be registered to the
// world position. Major changes (e.g in object scale or
// changes in object vertex positions) will require more
// drastic "physical" updating. These will be discussed further
// for object creation and loading:
// checks if moved; recomputes world position
void update_object(OBJECT *obj);
// recomputes object normals, bounding sphere
// and if moved: current representation only
void physical_update_object(OBJECT *obj);
// massive update: recomputes all object's reps,
// forces all connecting objects to update position
void global_update_object(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// ANIMATION AND AUTOMATIC UPDATING ////
//// ////
/////////////////////////////////////////////////////
// NOT FULLY COMPLETED: ALL LISTED ARE FUCTIONAL
// Animation control is a set of functions to allow autonomous
// motion of objects without the need to move objects each
// frame. In the future, functions such as TWEEN will be added.
// As of now, the critical step is an auto-update system for
// efficient calculation of changed objects, used by STATMACH.C
// for now.
//
// When many joints of a figure (objects in a tree) are moved
// it is crucial not to update each joint individually, as this
// would cause objects to be recomputed many times. Instead,
// a list is created of the root objects of the tree of moved
// objects. Then after all animation is complete, the object
// updates may be done quickly. YOU are responsible for updating
// moved objects unless the animation system has moved them!
//
// Objects are automatically updated when attached or detached.
// Because the tree structure is changed, during animation it
// is important to do any attach or detach operations BEFORE
// moving objects.
// puts root of tree on update list if not there
// just pass the object that was move: the code will do the rest
void add_to_object_update_list(OBJECT *obj);
// does the update on all moved objects, then
// removes them from the update list
// returns nonzero if update was needed, so
// you can tell if rendering is needed.
BOOL process_object_update_list();
// Sometimes objects must be removed from the list: before
// deleting objects or worlds, for example. Usually it's
// better to simply process the list.
// fast remove deleted segments
void remove_from_object_update_list(OBJECT *obj);
// dump entire update list
void clear_object_update_list();
/////////////////////////////////////////////////////
//// ////
//// THE WORLD ////
//// ////
/////////////////////////////////////////////////////
// UNDER CONSTRUCTION: NOT ALL FULLY FUNCTIONAL
// In VR-386, a WORLD serves to organize a set of objects into
// an environment. Eventaully, this environment may be loaded
// or deleted, or the user may switch between worlds.
//
// For objects, the world is a visibility structure (a SPLIT tree),
// containing all visible objects in to world. Objects that are
// not visible (e.g. alternate forms of an object) should be
// placed in the inactive_object_list OBJLIST, so they may be
// deleted when the world is terminated. Or they may just be
// left off any list, if you take responsibility for their
// deletion. Note that only VISOBJ (visible part of moveable
// or fixed objects) are part of the world: invisible objects
// (SEGMENTS) are not so you must use care to delete them
// when disposing of a world.
//
// The visibility tree consists of SPLITs: 3-D planes that divide
// the world into AREAs. As yet, there is little support in the
// API for AREAs: there is a lot of work to be done on and with
// them in the future.
//
// For now, you should be able to:
// -create the startup, default WORLD (TESTED)
// -make new WORLDs (but be careful loading them!) (??)
// -switch between WORLDS (??)
// These are the currently defined WORLD functions:
// save current camera, visibility tree, inactive list,
// and user position to WORLD w
void save_world_state(WORLD *w);
// restore old WORLD w
void restore_world_state(WORLD *w);
// create WORLD, save current WORLD data to it
// <template> arg not yet used
WORLD *create_world(WORLD * template);
// just releases WORLD struct now
// In the future, will delete everything in the world
void release_world(WORLD *w); // NOT YET FUNCTIONAL
// for initialization, creates all the default
// objects, structures, lists and so on
// creates a WORLD <default_world>
void create_default_world();
// DON'T USE until a better world file format is developed!
// for now, use read_world() to load a REND386 compatible
// world.
void load_world(FILE *in, WORLD *w); // NOT FUNCTIONAL
// These items are manipulated by changing the WORLD, and
// form the roots of world structures:
extern WORLD *default_world; // the default world (startup)
extern WORLD *current_world; // the WORLD currently in use
extern SPLIT *global_world_root; // the root of current visibility tree
extern SPLIT *current_split; // used to add objects to splits
extern OBJLIST *default_objlist; // used for loading objects
extern OBJLIST *inactive_object_list; // all objects not currently in world
// Objects when first loaded are not part of the world. Objects
// must be explicitly added to the world to become visible, and must
// be removed from the world to make them invisible.
// To add objects to the world, use these functions:
void add_object_to_world(OBJECT *obj);
void add_objlist_to_world(OBJLIST *olist);
// Use this function to remove an object from the world. The
// objects will be placed in the inactive_object_list OBJLIST.
void remove_object_from_world(OBJECT *obj);
// When creating worlds with walls or other visibility-error prone
// features, SPLITs must be used. They are also useful for dividing
// the world into AREAs. A SPLIT is a plane, specified by a point
// on it and a normal to its surface. Normals values should be
// as large as possible: perhaps use a unit normal and convert it
// with float2matval(). Set <flags> to zero for now. The point
// (x,y,z) should be somewhere near the center of the useful area of
// the split, since it will be used to determine the AREA of the
// world the split will subdivide.
//
// Any objects added to the world after a SPLIT has been created will
// be attached to that SPLIT: for example, you'd create a split for
// a room side then add an object to the world for that side of the room.
// Once all SPLITs have been created and their objects loaded, call
// end_of_world_splits(). All objects loaded after this will be
// placed within AREAs.
//
// If not using SPLITs, just ignore them. A default SPLIT at infinity
// is created for each new world.
// Adds a split to the WORLD.
// return FALSE if can't create it.
BOOL add_split_to_world(COORD x, COORD y, COORD z,
COORD nx, COORD ny, COORD nz, WORD flags);
// Marks end of SPLIT object loading, start of
// loading of rest of world objects.
BOOL end_of_world_splits();
//////// WORLD OBJECT TRAVERSALS
// You often need to process all objects in the world
// (usually to count objects, or to process selected
// objects). These functions apply a function to all
// objects in the world.
// for all objects not currently in world...
void do_for_all_not_in_world(void (*fn)(OBJECT *));
// for all objects in world...
void do_for_all_objects(void (*fn)(OBJECT *));
// for all selected (highlighted) in world...
void do_for_all_selected(void (*fn)(OBJECT *));
// for all moveable in world...
void do_for_all_moveable(void (*fn)(OBJECT *));
// for all fixed in world...
void do_for_all_fixed(void (*fn)(OBJECT *));
// for all selected, moveable in world...
void do_for_all_selected_moveable(void (*fn)(OBJECT *));
/////////////////////////////////////////////////////
//// ////
//// LOADING OBJECTS AND FIGURES ////
//// ////
/////////////////////////////////////////////////////
// Objects in VR-386 are loaded as fixed objects, and are not
// placed into the world. You are responsible for placing the
// objects into the world when you wish them to become visible.
// Or you can place them onto the inactive_object_list OBJLIST
// until needed.
//
// Objects are loaded from PLG files, which nmust have been
// opened previously. You can also load out of ANY text file:
// for example, out of the middle of a world or .FIG file. This
// has not been used yet. A change from REND386 .PLG files is
// that the sum of the vertex counts for all polygones is needed.
// This is an extra argument in the header line of the PLG file.
// If no number is supplied, the file will be read twice.
//
// Objects loaded from PLG files are first scaled, then shifted
// and finally rotated to create the vertices of the object.
// Negative scales should NEVER be used, as visibility problems
// will result. The PLG vertex coordinates may be in floating point,
// as are the scaling factors. The rotation and shift affect the
// "object-space" representation of the object, before it is moved
// in the world. This is similar the the <shift> in .FIG files, and
// unlike the shift and rotate for moveable objects in REND386 .WLD
// files. The most important use for this is to position fixed objects
// in the world, and to set the origin (0,0,0) point of objects, as this
// is where it will rotate around when moved.
//
// The <depth> parameter says how the object will be sorted when
// rendered. 0 will let the object's polys be sorted by deepest vertex,
// 1 will cause them to be sorted by vertex midpoint. Set bit 0x0100 to
// have the object sorted as a unit, then polys within it sorted.
// THE DEPTH PARAMETER WILL HAVE NEW FUNCTIONS IN THE FUTURE.
//
// Some objects may have multiple representations. A representation
// (REP) is the descripton of the visible part of an object, and
// consists of its polys, colors, and vertices.
// Multiple representations are usually used to increase rendering speed,
// by letting distant (small) objects disappear or be drawn in a simpler
// form than when close and large. The size in pixels that the object
// must subtend on the screen to be visible is set by the "size"
// parameter, which may be set in the PLG file in the object name.
// A representation of size 0 will always be seen, while larger "size"
// representations can disappear if too small. Use a size of 0 if not
// using this feature.
//
// Additional representation can be used for "morphing" animations.
// The representation to be visible can be "locked" as the one to
// be drawn at all times, and a sequence of representations used for
// animation.
// To specify that an object has multiple representations, precede
// the header line of the PLG object with "##MULTI". Do this for
// all PLG objects to be used as additional representations, except
// the last one. In other worlds, the first PLG without ##MULTI will
// be the last representation loaded.
// This is the simplest function, simply loading the first PLG
// object found in a file. It can load multiple representations.
// Call add_object_to_world(obj) to place object in the world.
// loads single and multi-rep files
// will load only one object: call again if not EOF
OBJECT *load_plg_object(FILE *in, POSE *p,
float sx, float sy, float sz, WORD depth);
// This function adds an extra representation to an existing object.
// It can add several representations if ##MULTI is used. Be sure
// to call physical_update_object(obj) after this so the new representation
// will be properly visible!
// loads a new rep to an old object.
// useful to create morphing animations
// just loads FIRST if not multi-rep file
// Uses <size> parameter to set size field
OBJECT *load_extra_plg_representation(FILE *in, OBJECT *obj, POSE *p,
float sx, float sy, float sz, WORD size);
// When a complex object consisting of many pieces is loaded, or there
// are many objects in a PLG file to be loaded, it is useful to load
// the objects onto an OBJLIST. This will allow us to process the
// object(s) as a unit later on. For example, the objects can be placed into
// the world as a unit, or recolored etc. A group of simple objects
// may be attached to an invisible "handle" object so they can be moved as a
// unit. Usually, the precreated OBJLIST default_objlist will be used for
// loading and processing objects.
// loads single and multi-rep files
// will load many objects
// loads to objlist for later recoloring etc
// loads as fixed objects so can be joined
// can limit number to load (so multi objects from file)
// returns number of objects in list
WORD load_plg_to_objlist(FILE *in, OBJLIST *objlist, WORD maxobj, POSE *p,
float sx, float sy, float sz, WORD depth);
// These functions are used to make loaded, fixed objects moveable. It
// does this by attaching a SEGMENT (invisible, moveable object) to them.
// The object may then be processed as a unit.
// converts loaded fixed object to moveable
// adds as visible part to invisible object if supplied
// if NULL, creates the invisible object part
OBJECT *make_fixed_object_moveable(OBJECT *obj, OBJECT *invis);
// creates moveable object-group from objects in list
// joins multiple objects to new invisible object
// which acts as a handle to move the group.
OBJECT *convert_objlist_to_moveable_object(OBJLIST *olist);
// disconnects visible part of object from
// invisible object, returns visible part.
// invisible part is stored in **invis.
// <preserve> sets whether to leave fixed
// object in same world positon-- DON'T USE
// if you are going to make object moveable again!
OBJECT *make_moveable_object_fixed(OBJECT *obj, OBJECT **invis, BOOL preserve);
// Complex jointed object structures are important for animation
// e.g. human figures. The .FIG file format allows specification of
// figures that can be loaded as a unit, then manipulated.
// Currently, loaded segment files can only be scaled, and are
// placed in position by moving their "handle" or root object.
//
// For animation, it is critical that the various objects in
// the figure be able to be identified. There are two ways to do
// this. An object may be named in the .FIG file, and the name
// searched for later with find_seg_by_name(object2segment(obj),"name")
// Or, you can set up a table: OBJECT *segtable[stmax] to hold pointers
// to the objects, that will be filled as the file is read, from "segnum"
// lines in the file.
// load a .FIG file as an object tree
// resulting tree root is returned
// objects are not in world and held in objlist
// "segnum" entries put pointers into <segtable>
OBJECT *load_figure_as_object(FILE *in, OBJLIST *olist, OBJECT **segtable,
WORD stmax, float sx, float sy, float sz);
// For error reporting, either an error number of a pointer to an
// error message is returned. These functions are provided:
// load error report from .FIG file load:
// returns NULL if no error
char *seg_error(SWORD *errnum);
// load error report from .PLG file load:
// returns NULL if no error
char *plg_error(SWORD *errnum);
/////////////////////////////////////////////////////
//// ////
//// CREATING OBJECTS FROM SOFTWARE ////
//// ////
/////////////////////////////////////////////////////
// Creating an invisible object (used as part of a motion tree)
// is simple:
OBJECT *create_invisible_object();
// Creating a visible object is more complex, since the visible
// part of the object must be specified. YOU MUST NEVER ATTEMPT
// TO ACCESS AN OBJECT REPRESENTATION DIRECTLY, AS IT MAY BE
// STORED IN PAGED EXTENDED MEMORY! See EMMSPPT.C for more
// information.
//
// To allocate memory, each representation needs to know the total
// number of polygons, vertices, and polygon vertices.
//
// Creating an object in software requires a series of steps:
//
// -create an object (automatically creates first representation)
OBJECT *create_fixed_object(WORD nv, WORD np, WORD npverts);
OBJECT *create_moveable_object(WORD nv, WORD np, WORD npverts);
// -add all vertex points to the object
// adds vertex to object: these aren't yet hooked to polys
void add_vertex(OBJECT *obj, COORD x, COORD y, COORD z);
// -for each polygon:
// --create new polygon
// adds poly to rep: still needs vertex list defined
POLY *add_poly(OBJECT *obj, SURFACE color, WORD npoints);
// --add points (polygon vertices, as indexes into vertex table)
// connects vertices to polys thru vertex lists
void add_point(OBJECT *obj, POLY *p, WORD vertindex);
//
// -for every additional representation, create it, then
// repeat the last 4 steps
REP *add_representation(OBJECT *obj, WORD size, WORD nv, WORD np, WORD tpverts);
// After creating the object, you must compute cached data for the
// object: its bounding sphere, and each of it's polygon's normals.
// Use these functions after creating an object:
// The compute_object() functions are OK if the object will
// be placed into or moved within the world immediately.
// Use this for each or last-added representation:
// computes renderer data for object
// for current rep only!
void compute_object(OBJECT *obj);
// Use this if you've added more than one rep to an object
// computes renderer data for object
// for all representations
void compute_all_object(OBJECT *obj);
// this also takes care of wolrd and location updates
// caused by changing an object, and is safer.
// recomputes object normals, bounding sphere
// and if moved: current representation only
void physical_update_object(OBJECT *obj);
// If speed is no problem, global_update_object is safest.
// massive update: recomputes all object's reps,
// forces all connecting objects to update position
void global_update_object(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT SUPPORT: REPRESENTATION CONTROL ////
//// ////
/////////////////////////////////////////////////////
// Some objects may have multiple representations. A representation
// (REP) is the descripton of the visible part of an object, and
// consists of its polys, colors, and vertices.
// Multiple representations are usually used to increase rendering speed,
// by letting distant (small) objects disappear or be drawn in a simpler
// form than when close and large. The size in pixels that the object
// must subtend on the screen to be visible is set by the "size"
// parameter, which may be set in the PLG file in the object name.
// A representation of size 0 will always be seen, while larger "size"
// representations can disappear if too small. Use a size of 0 if not
// using this feature.
//
// Additional representation can be used for "morphing" animations.
// The representation to be visible can be "locked" as the one to
// be drawn at all times, and a sequence of representations used for
// animation.
//
// In some cases, access to a specific representation
// is required. The representations on an object are
// in a list, with "largest" <size field> first.
// Representation of the same size are stored last-first.
// One of the representations will be "current" at any time,
// and is the only one that may be manipulated.
// make first rep the current one: used during lock
void select_first_representation(OBJECT *obj);
// make next rep the current one: used during lock
/* return 1 if wrapping back to first */
WORD select_next_representation(OBJECT *obj);
// find representation that will be
// active at given screen size
void select_representation(OBJECT *obj, WORD size);
// These functions are the heart of morphing animations.
// forces renderer to always draw rep
// if n = 0, it will be the current rep
// Otherwise, it's the Nth representation
void lock_current_representation(OBJECT *o, WORD n);
// goes back to select-by-size mode
void unlock_current_representation(OBJECT *o);
// Sometimes, you need to change the size field of a representation
// set size field of rep
void set_representation_size(OBJECT *obj, WORD size);
// read size field
WORD get_representation_size(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT SUPPORT: CHANGING COLOR AND SURFACE ////
//// ////
/////////////////////////////////////////////////////
// VR-386 is flexible enough to use many methods of coding
// colors and surface types (see COLORMAP.C for details
// of the current hue/reflectance method, similar to that
// used by REND386).
// You should use SURFACE as the type for all color/surface specifiers,
// to improve compatibility with future upgrades, since 32-bit or
// pointers may be needed for RGB colors or texture mapping in the future.
//
// There are two recoloring methods used: one is a global "change everything"
// strategy, and the other can change sets of colors to other colors
// selectively. This last uses an array of SURFACEPAIRS, as:
//
// typedef struct { // pair for surface remapping
// SURFACE old;
// SURFACE new;
// } SURFACEPAIR;
//
// The matching and replacement of bits in the process are controlled
// by masks, making it easy to change only the hue field of the
// surface description, or to select a polygon for recoloring by the
// 3 LSB of its brightness field. These bits can be preserved by
// the write mask.
// This function modifies all polygons in an object. Where bits in
// <bitmask> are set, bits from <new> replace current surface bits
// in the polygon. The <all> flag, if set, processes ALL representations
// of the object, otherwise only the current representation is processed.
// flexible recolor/resurface object
void masked_recolor_object_surface(OBJECT *obj, SURFACE new,
SURFACE bitmask, BOOL all);
// This function modifies only polygons whose current surface matches
// one of the SURFACEPAIR.old in <map>. Only bits where <sbitmask>'s bits
// are set are used in the comparison. Only the set bits in <dbitmask>
// from the pair's .new field will replace current surface bits
// in the polygon. The <all> flag, if set, processes ALL representations
// of the object, otherwise only the current representation is processed.
// very flexible remaps colors of object
void masked_remap_object_surface(OBJECT *obj, SURFACEPAIR map[20], WORD nmap,
SURFACE sbitmask, SURFACE dbitmask, BOOL all);
#define MAP_ALL_MASK 0xFFFF // some useful masks
#define IGNORE_HIGHLIGHT_MASK 0x7FFF
#define MAP_SURFACE_MASK 0x3000 // test/modify surface type only
#define MAP_HUE_MASK 0x0F00 // test/modify hue only
#define MAP_BRIGHTNESS_MASK 0x00FF // test/modify brightness only
#define MAP_PALETTE_MASK 0x00FF // test/modify abs. 256 color palette entry
#define TEST_B3BITS_MASK 0x0007 // test 3 LSB as "type" code
#define KEEP_B3BITS_MASK 0xFFF8 // keep the 3 LSB as "type" code
// When loading objects, we want to be able to process all the objects
// and their representations immediately. The objects are in an
// OBJLIST, and it is most convenient to use this function when loading
// objects:
// very flexible remaps colors
// does for all objects in objlist
void objlist_remap_surface(OBJLIST *olist, SURFACEPAIR map[20], WORD nmaps,
SURFACE sbitmask, SURFACE dbitmask);
// The standard PLG remapping used in REND386 used 0x80xx as the "raw"
// color number, with "xx" being the number in the surfacemap. It is
// trivial to set up a SURFACEPAIR table to do this replacement: set the
// MSB of the .old color numbers, and the .new is the desired surface.
// Use MAP_ALL_MAP for <sbitmask> and <dbitmask>.
// For direct color access into polygons, these low-level functions
// are supplied:
// get data on poly
// use poly number ito object
// use NULL for *color and *nverts
// if you don't want their data
void get_poly_info(OBJECT *obj, WORD polynum, SURFACE *color, WORD *nverts);
// set poly's color number
void set_poly_color(OBJECT *obj, WORD polynum, SURFACE color);
/////////////////////////////////////////////////////
//// ////
//// OBJECT SUPPORT: DELETIONS ////
//// ////
/////////////////////////////////////////////////////
// In most cases, objects are not deleted individually.
// Usually an entire movement tree, OBJLIST or world
// visiblilty tree is deleted at once. More deletions
// support will be available when world deletion is complete.
// This function deltes the visible part of a moveable
// object, or a fixed object entirely. It is best used
// for deletions when you're not sure if the segment
// of the object is in use elsewhere.
// deletes fixed object or
// visible part of moveable object
// also removes from any objlist
void delete_visobj(OBJECT *obj);
// This function deletes invisible, visible or moveable
// objects. It cannot delete objects that are registered
// as system-owned, such as the segments of cameras or lights.
// Delete these with delete_light() or delete_camera()
// can delete segments or visobjs, but not if
// the segment is system owned (camera, light)
// also removes from any objlist
void delete_object(OBJECT *obj);
// object list deletions:
// delete objects on list then delete objlist
void delete_objlist_and_objects(OBJLIST *list);
// delete objlist, but just remove objects from list
// safer if not sure all objects are off list
void delete_objlist_keeping_objects(OBJLIST *list);
// You may also want to delete the objects in the tree
// completely. This can be done for the children of the
// object, or for the entire tree if find_root_object(obj) is
// used.
// delete object, and descendent objects
// both moveable and invisinble objects deleted
void delete_object_and_children(OBJECT *obj);
/////////////////////////////////////////////////////
//// ////
//// OBJECT SUPPORT: MISCELLANEAOUS ////
//// ////
/////////////////////////////////////////////////////
// Every visible object has a bounding sphere. This function
// reports the world position of its center and its radius.
COORD get_object_bounds(OBJECT *obj, COORD *x, COORD *y, COORD *z);
// get data on poly of object
void get_poly_info(OBJECT *obj, WORD polynum, SURFACE *color, WORD *nverts);
// set surface type of object poly
void set_poly_color(OBJECT *obj, WORD polynum, SURFACE color);
// gets number of vertices, polys for current rep
void get_obj_info(OBJECT *obj, int *nv, int *np);
// sum of number of vertices of all polys for current rep
WORD total_object_pverts(OBJECT *obj);
// get/set object dorting type (see LOADING...)
WORD get_object_sorting(OBJECT *obj);
void set_object_sorting(OBJECT *obj, WORD depth_type);
// vertices are referenced in two ways: as an entry in the object's
// vertex table, and as the vertex number of a polygon.
// This fuctions converts a polygon's vertex number (0..n-1)
// to the index in the vertex table.
WORD get_poly_vertex_index(OBJECT *obj, WORD poly, WORD vertex);
// To actually modify the shape of an object, it is only really
// feasible to move the vertices. These functions will allow
// the object's internal vertex coords to be accessed and modified.
// Don't forget to call physical_object_update() after changing
// the object.
void get_vertex_info(OBJECT *obj, WORD vertnum, COORD *x, COORD *y, COORD *z);
void set_vertex_coords(OBJECT *obj, int vertnum, COORD x, COORD y, COORD z);
// This function scales the size of an object. You must call
// physical _update_object afterwards. Not useful with small objects,
// and precision is lost after each scaling. Mostly useful for
// setting up objects.
void scale_object(OBJECT *obj, float sx, float sy, float sz);
// When an existing object must be moved or rotated internally,
// (NOT in the usual way), then its internal representation
// must be changed. Precision will be lost, of course. But
// the best way to do this is to use a homogenous matrix to
// move the world position of the object, then to copy the
// world vertex positions into the "object" vertex information.
// The matrix may be derived from a segment, or may be generated by
// one of the routines in the INTMATH library.
void apply_matrix(OBJECT *obj, MATRIX m); // moves object completely
void copy_world_to_object(OBJECT *obj); // used to "lock" fixed objects
// Once a visible object has been rendered, the world coordinates
// for each of its vertices is cached internally. This routine
// can extract that information for use in collision detection
// or other routines.
void get_vertex_world_info(OBJECT *obj, WORD vertnum, COORD *x, COORD *y, COORD *z);
// This function scans an OBJLIST, looking for the object whose center
// closest to the given world point. It only chacks objects whose
// bounding sphere includes the point of interest. Usually it is used in
// combination with world areas (see MANIP3D.C for an example).
// determines closest object in list
OBJECT *best_collision(OBJLIST *objlist, COORD x, COORD y, COORD z);
// At oresent, there are NO user-manipulable flags in the
// objects. There may be a user flag word in the future.
void set_obj_flags(OBJECT *obj, WORD val);
WORD get_obj_flags(OBJECT *obj);
// set/clear flags in visobj part of object
void set_object_flags(OBJECT *obj, WORD flagmask, WORD bitval);
// For completeness, the object-update functions are listed here:
// recomputes object normals, bounding sphere
// and if moved: current representation only
void physical_update_object(OBJECT *obj);
// massive update: recomputes all object's reps,
// forces all connecting objects to update position
void global_update_object(OBJECT *obj);
// converts loaded fixed object to moveable
// adds as visible part to invisible object if supplied
OBJECT *make_fixed_object_moveable(OBJECT *obj, OBJECT *invis);
// creates moveable object-group from objects in list
// joins multiple objects to new object
OBJECT *convert_objlist_to_moveable_object(OBJLIST *olist);
/////////////////////////////////////////////////////
//// ////
//// CAMERAS, STEREOSCOPIC SUPPORT (SCAMERA.C) ////
//// ////
/////////////////////////////////////////////////////
// A CAMERA is a device for reproducing a monoscopic
// or stereoscopic view of the world to the screen.
// The camera may be moved like any object, or
// attached to any object. A special CAMERA,
// default_camera, is created and set up during
// configuration to use as a template (in many programs,
// this may be the only camera used). Usually the default camera is
// attached to the "body" object's "head" object
// for use with head trackers and HMDs
// The default_camera is created at initialization, and is modified
// during configuration. The current_camera will be used for
// rendering and view-dependant calculations.
extern CAMERA * default_camera; // the "basis" camara for world
extern CAMERA * current_camera; // the current camera used for rendering
// creates, initializes camera
// may be stereo or mono, may copy another camera
CAMERA *create_camera(BOOL is_stereo, CAMERA *template);
// delete camera, free memory
void destroy_camera(CAMERA *c);
// These commect and disconnect the camera from objects that
// can move the camera in the world:
// attach camera to moveable object
void attach_camera(CAMERA *c, OBJECT *obj);
// detach camera
void detach_camera(CAMERA *c);
// These functions manipulate camera pose. Recall that
// local and world pose are the same if the camera is
// not attached to anything: the default_camera is
// always attached to body_seg, so the local pose is the head-to-body
// angle of the head tracker, and the world pose is the "real"
// viewpoint and look angle.
// set camera pose (if attached, set relationship)
void set_camera_pose(CAMERA *c, POSE *p);
// get pose with relation to object attached to
void get_camera_localpose(CAMERA *c, POSE *p);
// get world position/angle
void get_camera_worldpose(CAMERA *c, POSE *p);
// Certain operations must be done with respect to the camera's view
// itself. For example, mouse and 3D pointer operations must
// take place in the camera's view coordinates so that motion in
// the world and on the screen always match. When loading PLG
// files, we want to place the object "ahead" of the camera so it
// appears a constant size... and so on.
// get the MONOSCOPIC camera pose matrix
// usually best match for stereoscopic cameras as well
void get_camera_matrix(CAMERA *c, MATRIX m);
// computes coords of point in fromnt of MONOSCOPIC camera
void camera_point_ahead(CAMERA *c, COORD distance,
COORD *xp, COORD *yp, COORD *zp);
// This is used during rendering to extract a VIEW (window description)
// from the camera, and set up the renderer to use it.
// sets renderer to draw a view, mono or stereo view
// also computes camera position from object it
// is attached to
VIEW *select_camera_view(CAMERA *c, WORD which_eye);
// CAMERA view factors for stereoscopic and monoscopic views are
// set differently. There are many support functions for mono
// cameras, as almost any parameters make sense. For stereo,
// there are too many interrelated factors. Most stereo
// factors are best set up with the STEREO data structure:
// default_stereo is set up during configuration.
//
// After any parameters are changed (except camera position),
// the camera must be recomputed to cache internal coefficients:
// computes the static stereo factors for camera, caches
void compute_camera_factors(CAMERA *c);
// Camera hither and yon clipping can be set in both stereo and
// mono cameras simultaneously:
// set/get hither clipping
void set_camera_hither(CAMERA *c, COORD h);
COORD get_camera_hither(CAMERA *c);
// set/get yon clipping
void set_camera_yon(CAMERA *c, COORD y);
COORD get_camera_yon(CAMERA *c);
// These set monoscopic "zoom" (field of view). FOV is
// equal to 2*atan(1/zoom)
// set the MONOSCOPIC zoom
void set_camera_zoom(CAMERA *c, SCALE zoom);
// get the MONOSCOPIC zoom
SCALE get_camera_zoom(CAMERA *c);
// For stereoscopic "zoom", there is no function to set
// zoom, because many stereoscopic parameters to be changed to
// get good stereo. Instead, adjust:
// get_camera_stereo(CAMERA)->phys_screen_dist
//
// the equivalent zoom is (right-left)/2/distance
// set the MONOSCOPIC view window on screen
void set_camera_window(CAMERA *c, WORD l, WORD t, WORD r, WORD b);
// For stereoscopic cameras, the window must be set through
// the two windows in the camera's STEREO: example: left side:
// get_camera_stereo(CAMERA)->window[LEFT_EYE].l
// get_camera_stereo(CAMERA)->window[RIGHT_EYE].l
// As noted, the STEREO structure controls stereo camera image generation.
// These functions allow you to create cameras with parameters
// different from those set into default_stereo during configuration:
// this pre-initialized STEREO controls the default camera
// usually shared by all cameras
extern STEREO default_stereo;
// make a unique (nonshared) stereo
// parameter structure for camera
STEREO *create_new_stereo(CAMERA *c, CAMERA *template);
// pointer to camera's stereo parameter structure
STEREO *get_camera_stereo(CAMERA *c);
// makes camera use stereo parameter structure
STEREO *set_camera_stereo(CAMERA *c, STEREO *s);
/////////////////////////////////////////////////////
//// ////
//// LIGHTS: POINT, DIRECTIONAL AND AMBIENT ////
//// (LIGHTS.C) and (COLORMAP.C) ////
//// ////
/////////////////////////////////////////////////////
// LIGHTS may be treated as object: moved, rotated and attached
// to other objects. Arrays of (LIGHT *) may be used to
// light scenes, and can be used very flexibly. There are
// 3 types of lights:
//
// AMBIENT_LIGHT lights all polys the same
//
// SPOT_LIGHT lights all polys from the same side,
// determined by the rotation of the light: zero angle
// lights from the +Z side
//
// POINT_LIGHT lights polys from a point in space, set by
// the position of the light only.
//
// It is up to COLORMAP.C to decide how to cope with light data:
// in the basic version, the total of all lights is scaled
// to a constant 127 for integer calculations. See WPARSE.C
// to see code for creating default lights, and USERVID.c
// and COLORMAP.C for a sample lighting system.
// create, initialize a light and its segment
LIGHT *create_light(OBJECT *parent, WORD type, WORD intensity);
// destroy a light and its segment
// light and camera segments can only
// be deleted through their delete functions
void destroy_light(LIGHT *l);
// returns light type
WORD get_light_type(LIGHT *l);
// returns light intensity
WORD get_light_intensity(LIGHT *l);
// sets intensity of light
void set_light_intensity(LIGHT *l, WORD i);
// sets light type
void set_light_type(LIGHT *l, WORD t);
// attaches light to an object
// option to preserve world location
// (see attach_object() for info on this option)
BOOL attach_light(LIGHT *l, OBJECT *obj, BOOL preserve);
// detaches light from object
// option to preserve world location
BOOL detach_light(LIGHT *l, BOOL preserve);
// change angle of a spotlight
// (forces light to be spotlight)
BOOL rotate_spotlight(LIGHT *l, POSE *p);
// change position of point source
// (forces light to be point source)
BOOL position_pointlight(LIGHT *l, POSE *p);
// change pos/rot of light source
// Makes it possible to have both point
// and spotlight data for light
BOOL set_light_pose(LIGHT *l, POSE *p);
// get local (joint) position/angle of light
void get_light_localpose(LIGHT *l, POSE *p);
// get world position/angle of light
void get_light_worldpose(LIGHT *l, POSE *p);
// To actually USE the lights we've created, we must
// pass an array of (LIGHT *) to this routine which
// MUST be supplied by YOU. There is a sample in COLORMAP.C
// usually in COLORMAP.C, sets up lights to renderer
void setup_lights(LIGHT *llist[], WORD nlights);
// fetch light data for computing
// lighting data in COLORMAP.C
WORD get_light_data(LIGHT *l, COORD *xp,
COORD *yp, COORD *zp, WORD *intensity);
/////////////////////////////////////////////////////
//// ////
//// USER INTERFACE SUPPORT ////
//// ////
/////////////////////////////////////////////////////
// VR-386 has many tools for user interfaces. These support:
//
// -keyboard responses and control
// -mouse for object selection and manipulation
// -"joystick" type navigation via keyboard, mouse, joystick
// -popup message boxes and menus on screen
// -PowerGlove and 3d/6d pointing device interfaces
// -support for head tracking devices.
//
// These devices are rather basic; a standardized "pointer"
// interface system makes development and adding of device
// drivers easier. Pointers will be discussed elsewhere in
// more detail, but basically consist of a simple device driver,
// (PDRIVER) which can initialize, reset, read, and control
// the device. Much of the functionality of a normal device
// driver is taken over by the VR-386 pointer system, such as
// scaling and motion detection. A data block describing the
// device (PCONFIG) contains information of the driver's resolution,
// range and so on.
// Pointers support two basic modes: pointer and mouse. The idea
// is to let any device funstion both as a pointing device
// (2D, 3D, etc) and as a 2D mouse, scaled to the screen, for
// use in object selection and menu selection.
//
// Many of the keyboard functions in this release are in
// KEYBOARD.C, which is a large key and menu support interperter.
// If you need to see how any VR-386 editing feature is implemented,
// look there first. You can easily remove the KEYBOARD.C from the link,
// as very few functions in it are critical. Only a few functions
// are really needed, mostly Quit and the basic key processing for
// motion control. This module basically is written to be compatible
// with REND386 release 5.
/////////////////////////////////////////////////////
//// ////
//// USER INTERFACE SUPPORT: MOUSE, CURSOR ////
//// ////
/////////////////////////////////////////////////////
// VR-386 uses the mouse to move a 2D cursor, or to
// manipulate objects (the mouse-navigation is discussed
// later). Mouse functions can be split into cursor-related
// and menu-related, each driven by a different pointer device:
// cursor_device and menu_device. These are usually the same.
// Cursor drawing is implemented by the video driver.
// (Future drivers may support multiple types of bitmap cursors).
//
// Before drawingto the screen, the cursor must be hidden,
// then shown after drawing. cursor_hide() returns a flag that
// can be saved to determine if the cursor will need to be
// redrawn later.
// If the cursor is not to be seen (mouse is not available) the
// cursor drawing may be turned off: cursor_show is ignored.
extern void cursor_enable(BOOL state); /* hardwire on/off */
// The basic cursor operations are to hide and redisplay it.
// Flags are used to ensure that a cursor is not erased or drawn
// twice, as this will leave garbage on the screen.
// All cursor operations are performed to the current_video_page,
// set by the screen refresh to be the same as the displayed video page.
extern BOOL cursor_hide(void); /* erase cursor, rtn visible flag */
extern void cursor_show(void); /* redisplay cursor */
// During rendering, drawing is done to a different video page,
// and the cursor is erased from the old page and drawn to the
// new current page after the screen refresh is done. Thus
// a function to specify which page the cursor is to be erased
// from is needed.
extern BOOL last_cursor_hide(WORD page); /* erase cursor, on specific page */
// Combining hide and redraw at a new position moves the
// cursor on the screen:
extern void cursor_move(WORD x, WORD y); /* move cursor if visible */
// Integrating mouse reading and cursor motion into one
// function. This reads the mouse once, moves the cursor and returns.
// <d> is a pointer to a mouse driver (usually cursor_device),
// and *x, *y, and *b will be filled with current mouse position and
// button status: b&0x01 for left button, and b&0x02 for right.
extern WORD move_2D(PDRIVER *d, WORD *x, WORD *y, WORD *b);
// Adding a loop to move the mouse till a click is detected:
// Unlike move_2D(), <b> is a mask for which buttons are to
// be tested: 1 for left, 2 for right, 3 for both. The button
// clicked is returned (same code).
extern WORD move_till_click(PDRIVER *d, WORD b, WORD *x, WORD *y);
// Often a function needs to see if the mouse is available. This returns
// Returns 1 if true, else pops up warning message.
extern BOOL can_point_2D(void);
/////////////////////////////////////////////////////
//// ////
//// MOUSE AND SCREEN SELECTION OF OBJECTS ////
//// ////
/////////////////////////////////////////////////////
// A critical feature of VR-386 is its ability to select
// objects by simply clicking on their image on the screen.
// The screen-to-object system can also find which polygon
// and (if possible) vertex of the object was clicked on,
// and is based on the screen-monitor built into the renderer.
// this function reports the object which is visible at
// that point on the screen. This is done by enabling the
// screen monitor, refreshing the screen, then recording
// the results. Returns NULL if no object found.
OBJECT *find_object_on_screen(WORD x, WORD y);
// this is called internally by find_object_on_screen to
// process the screen monitor output:
void process_screen_monitor();
// The results may be read by calling these functions. The
// poly and vertex indices are -1 if no object, poly, or
// vertex was found. It is possible to find an object but
// not to find a vertex.
OBJECT *screen_found_object(); // object (NULL if none)
SWORD screen_found_poly(); // poly index in object
SWORD screen_found_poly_vertex(); // which vertex in poly (order)
SWORD screen_found_object_vertex(); // which vertex in object (table)
// This function will read the mouse once, move the cursor,
// and check if it was clicked. If so, it checks to see
// which object was clicked on. <*buttons> will contain
// the button state, and NULL will be returned if no
// button was clicked. This function should be called in a loop.
OBJECT *move_and_find_object_2D(PDRIVER *d, WORD *buttons);
// This function implements the core of the "screen idle" call
// It reads the mouse once, moves the cursor, and checks for
// a button click (left button only). Returns 0 if no click
// or click in inactive area, -1 if menu-area click (top of screen)
// or 1 if new object selected/deselected.
// INSTALL EXTRA SCREEN-CLICK HANDLERS HERE.
SWORD move_and_select_2D(PDRIVER *d);
// THis is the main mouse-processing call for the VR-386 run loop.
// It processes menu mouse clicks as well.
extern void mouse_process();
/////////////////////////////////////////////////////
//// ////
//// SCREEN INTERFACE SUPPORT: DIALOGS, MENUS ////
//// ////
/////////////////////////////////////////////////////
// reads keyboard, checks for CTRL-F10 (screen dump)
// Encodes left/right shifts, ALT and CTRL keys
// as modifiers on all keys. See USERINT.H for
// key codes.
WORD getkey(void);
// saves screen to old video page
// only done for first call after rendering
void save_screen();
// restores screen as saved by save_screen();
// used to erase menus, text boxes, etc.
void restore_screen();
// prints box for text, centered on screen
// with width <w> and height <h>
void neatbox(int w, int h, int *x, int *y);
// print a text box containing several lines (37 char MAX)
// in the center of screen. Last entry of array must be NULL
void poptext(char *text[]);
// print a text box containing one line (37 char MAX)
// at center of screen
void popmsg(char *msg);
// prints to "popmsg" box
// useful for debugging
// prints to stderr if not in graphics mode
WORD popprintf(char *fmt, ...);
// This is a way to get text response from user.
// A prompt is printed, and up to <n> characters
// are accepted. Prompt and <n> should be less than
// 36 characters. Backspace is supported. ESC
// will exit with an empty buffer: buff[0]==0
// Returns exit character.
WORD askfor(char *prompt, char *buff, WORD n);
// test for response event, used to terminate waits,
// abort loops, and so on. A joystick or mouse
// click is accepted as a response as well as a keypress.
// returns 0 if no response, buttons (1,2,3) for mouse,
// 4 if joystick click, else key value
// if <wait>, then the function will not return till
// a response is given.
WORD get_response(BOOL wait);
// prints out menu (a NULL-terminated array of char *)
// at center of screen. Responses should correspond to
// the first capitalized letter in each line, which is
// returned if the line is clicked on by mouse.
// A click outside the menu or by joystick, or ESC exits
// returns 0 or the capitalized letter of the selected entry.
WORD menu(char *text[]);
/////////////////////////////////////////////////////
//// ////
//// KEY PROCESSING ////
//// ////
/////////////////////////////////////////////////////
// The key process routines must process key
// preses, passing certain ones on to be used
// for moving the user, and ignoring the key presses
// that are intercepted by the key monitor. The method
// suggested is to have a series of key processing
// routines, as in KEYBOARD.C, that look for keys they
// can handle, returning TRUE if they have processed the key.
// The routines should be:
// processes key <c>, calling sequence of key handlers
// until one returns TRUE to mark key as processed
void process_a_key(unsigned c);
// calls getkey() to read a key, calls
// process_a_key to handle it
// should get first key in queue, dump others
// to prevent type-ahead
void key_process();
// Look at the end of KEYBOARD.C for sample routines.
/////////////////////////////////////////////////////
//// ////
//// SCREEN REFRESH ROUTINES ////
//// ////
/////////////////////////////////////////////////////
// Screen refresh is supported by the video driver,
// the renderer kernal, as well as a variety of user-
// customizable routines. The refresh is designed to support
// monoscopic as well as several types of stereoscopic
// views: switched (left-right alternate frames displayed),
// windowed (seperate left and right windows on screen,
// and seperate viddeo displays for left and right eye
// views.
// User customizable routines in USERVID.C are used to perform the
// basic drawing tasks, such as lines, polygons, text,
// and screen clearing. These provide alternates to the
// standard video driver interface. THe polygon drawing
// routine, in combination with the color-coding routines
// in COLORMAP.C, allow for almost any lighting and color
// system to be supported.
//
// Screen refresh proceeds in stages. First, the video page
// to be drawn is selected. The rendering areas are then
// prepared with a call to prerender_process(), which clears
// the area or draws a horizon (see RENDERER.H) in the area.
// Then the renderer itself is called to draw in the window.
// Finally, postrender_process() is called to add overlays
// such as status or axis compass. These routines are easily
// customized, and are in USCREEN.C: Look at REFRESH.C to
// see the sequence of calls. For stereoscopic rendering,
// the sequence is repeated twice.
// Routines in USERVID.C:
// These are the colors used to draw in the world. For two-color
// horizons, sky_color and ground_color set the horizon: if the
// same. the whole window is cleared. screen_clear_color is used
// if the screen must be completely blanked. Highlight_color
// is used to outline selected objects.
extern WORD screen_clear_color;
extern WORD sky_color;
extern WORD ground_color;
extern WORD highlight_color;
// call on initialization to set defaults for the colors
// listed above. the video driver These vary depending on
void preset_default_colors();
// This structure is actually part of the video driver.
// It has information on the driver's screen size,
// aspect ratio and number of colors.
extern struct Screeninfo *screeninfo; // ptr to data in video driver
//struct Screeninfo {
// WORD xmin; // left edge of useable area
// WORD ymin; // top edge
// WORD xmax; // right edge
// WORD ymax; // bottom + 1
// WORD xcent; //
// WORD ycent; // where text boxes, menus should be centered
// WORD colors; // number of colors supported
// WORD pages; // number of video pages
// WORD bw; // monochrome only flag
// SCALE aspect; // vertical/horizonal pixel size * 65536
// char id[80]; // driver title
// };
/********************************************************/
/* USER ROUTINES CALLED BY THE RENDERING LIBRARY */
/* KEEP THIS AS FAST AS POSSIBLE: SOME MAY BE */
/* CALLED UP TO 1000 TIMES PER SCREEN! */
/********************************************************/
/* USER ROUTINE TO SETUP FOR POLY DRAWING - CALLED ONCE PER FRAME */
void user_setup_blitter();
/* USER ROUTINE TO RECOVER AFTER POLY DRAWING: ONCE PER FRAME */
void user_reset_blitter();
/* CALLED FROM RENDERER TO DRAW POLYS, LINES, POINTS */
/* <color> was created by colormap.c: modify it to */
/* do your own special color modes/effects! */
// supplied data: number of points, array of (x,y) screen coords (WORD),
// color value generated by COLORMAP.C function user_poly_color(),
// and the depth of the deepest point in polygon.
void user_render_poly(WORD number, WORD *pcoords, SURFACE color, COORD maxz);
// These routines draw items for the user interface.
// Colors are entries in the screen palette
// All are drawn to current video page
/* Draws filled box for text and menus
// color is entry in color palette
void user_box(WORD x1, WORD y1, WORD x2, WORD y2, WORD color);
// draws a line on the screen (current page)
void user_draw_line(WORD x1, WORD y1, WORD x2, WORD y2, WORD color);
// draws a box outline on the screen (current page)
void user_draw_box(WORD left, WORD top, WORD right, WORD bottom, WORD color);
/* USER ROUTINES TO DRAW TEXT */
// These will draw reversed text if the current page's
// orientation requires it.
void user_text(WORD x, WORD y, WORD color, char *string);
// draws text with a black shadow (color 0)
void shadowprint(WORD x,WORD y, WORD color, char *t);
/////////////////////////////////////////////////////////////////
/// USER VIDEO MODE SUPPORT ROUTINES:
///
/// NEVER CHANGE MOST OF THESE UNLESS YOU NEED
/// MORE THAN THE VIDEO DRIVERS CAN DO
/* enter and setup graphics screen */
// required to initialize all screens for
// multi-VGA driver
void enter_graphics(unsigned vdmode, int bw);
/* exit and restore text screen */
// must reset all screen for
// multi-VGA driver
void exit_graphics();
// clear full video page: rarely needed
void clear_display(WORD pge);
// This is the "critical" function when your screen
// has complex borders for the view window. It is
// called when the size of the window changes, or
// there is a chance the the border has been munged.
// It is also called on startup.
// If you have no border, just clear all the video
// pges. If you have a border, redraw it on one
// page and copy it to all other pages.
void reset_screens();
// IN <REFRESH.C>
void screen_refresh(CAMERA *c);
// This is the main screen-refresh routine. It switches video pages,
// calls window clearing, rendering, and postrender overlay routines.
// It draws the current world with the given camera (which also specifies
// where on the screen it is to draw). The proper stereo mode is set by:
extern WORD stereo_type;
#define MONOSCOPIC 0 /* stereo types */
#define SWITCHED 1
#define SPLITLR 3
#define SEPARATE 5
//These variables are updated by screen_refresh():
// where to save the screen while using menus
extern WORD screen_save_video_page;
extern BOOL screen_has_been_saved;
extern WORD current_video_page; // current video page
extern WORD current_orientation; // flip flags of current page
// This routine is called by user interface routines to stop
// the flicker of SWITCHED stereo displays.
void stop_stereo();
// This routine can be called by user postrender() routines to
// draw an axis compass on the screen. Use the VIEW passed to
// postrender()-- it contains window edges and the view matrix.
// xc,yx : screen location of center
// V : viewport structure
// xcolor,ycolor,zcolor: color of axes
// bcolor: "shadow" color
void coord_ref(WORD xc, WORD yc, WORD size, VIEW *v,
WORD xcolor, WORD ycolor, WORD zcolor, WORD bcolor);
// Internally, screen_refresh uses this function from SCAMERA.C
// to get monoscopic, left, or right eye views from the
// camera to control rendering. The renderer is set up by
// the routine to use the VIEW, and it is returned so it may be
// passed to user routines.
// sets renderer to draw a view, mono or stereo view
// also copies segment position
VIEW *select_camera_view(CAMERA *c, WORD which_eye);
// IN USCREEN.C
//////////////////////////////////////////
/// THESE ROUTINES LET YOU CONTROL HOW THE
/// SCREEN IS CLEARED AND WHAT IS DISPLAYED
/// THE ARGUMENTS ARE:
/// v : the view being draw. left/right eye views are different
/// vpage: the video page being drawn
/// isfirst, islast: 1 if this is the first/last access to the
/// video page. Useful for toggling screen
/// clears etc. when several views are on the
/// same page
/// whicheye: 0 for left eye/mono, 1 for right eye view
///
// found in <USCREEN.C>
// This is called before drawing objects. It should
// at least clear the screen
void prerender_process(VIEW *v, WORD vpage, WORD isfirst, WORD whicheye);
// This is called after drawing objects. It can be used
// to put up status, etc.
// lots of if() stuff, but just because we're supporting
// so many stereo types
void postrender_process(VIEW *v, WORD vpage, WORD islast, WORD whicheye);
/////////////////////////////////////////////////////
//// ////
//// USER NAVIGATION AND JOYSTICK ////
//// ////
/////////////////////////////////////////////////////
// The currently implemented navigation method in VR-386 uses
// a 2D device: a joystick. Two buttons are used to map this
// 2D to full 6D motion. Joystick "devices" include PC game
// port, mouse, key press, and key-down monitor.
//
// The joystick devices are read through the regular pointer
// interface, and are implemented in JOYPTRS.C. Up to 9
// devices may by polled for motion (NAVJOY.C), with the sum
// of all device's effects used to move the user.
// All navigation devices (even key presses) are mapped
// through pointer devices. Please look at JOYPTRS.C
// to see how easy and flexible this tecnique is. The
// pointer devices need only return buttons and the
// relative (change) motion required.
// Joystick devices are added with:
BOOL add_joy_device(char *name);
// which loads and/or initializes the pointer driver for the
// joystick, then adds it to the list of drivers. New drivers
// can be added or created by adding to the internal driver table
// in POINTER.C, and adding a new pointer driver and pconfig.
// The joysticks are processes to move a POSE structure to move
// the user's body or point of view.
// <body_pose> is the POSE to move user with
// <spinmode>: if true, maps all motion to rotation
// <sstep>: scaling of position changes: 100 is usual
// <astep>: scales turning: angle2float(20) is usual
// <flymode>: makes all motions relative to the
// view direction. Otherwise, motion is relative
// to the "ground" always.
// RETURNS: TRUE if body has been moved by any device
BOOL do_joy_navigate(POSE * body_pose, BOOL spinmode,
COORD sstep, ANGLE astep, BOOL flymode);
// The main loop of VR-386 is written to call joystick_process().
// This routine is found at the end of KEYBOARD.C, and can easily
// be modified to remove unwanted options.
extern void joystick_process();
// The usual POSE to set user position is *body_pose. This
// is used by world and teleports as well.
extern POSE initial_body_pose; // start position
extern POSE *body_pose; // current "teleport" used
// See PCDEVICE.H for more information on the PC game port joystick device.
/////////////////////////////////////////////////////
//// ////
//// USER BODY AND TELEPORTS ////
//// ////
/////////////////////////////////////////////////////
// To use head trackers and 3D manipulators such as the PowerGlove,
// a set of invisible connected objects is used to build the user
// a body. The body_seg is the root of the body, which is moved
// through the world by the navigation "joystick" devices. The body can
// also be attached to other objects, thus letting the user "ride"
// the object as it moves. Joystick motion is then relative to
// the object instead of the world.
//
// The head tracker moves the head_seg relative to the body,
// and the default_camera (user's viewpoint) is attached to
// the head_seg. This is an elegant implementation of head
// tracking.
// There is also a wrist_seg attached to the body. It is used with
// the PowerGlove or 3d/6d pointer devices, and used to "hold" the
// glove or pointer 3D cursor. It is also used to manipulate objects.
//
// These link objects are created during initialization by a call to
void init_body_links();
extern OBJECT *body_seg, *wrist_seg, *head_seg;
// which creates these links, and attaches the camera to the head.
// You can easily eliminate the body code in BODY.C if you wish to have a
// simpler viewpoint metaphor. But you will also have to eliminate
// all head tracker, PowerGlove, and 3D pointer support device.
// If the body has been moved for any reason, you must update the
// body links so the manipulation and camera positions will be
// current. Do this by a call to
void update_body_links();
// This will already have been done if the body is attached to an
// object that was moved then updated.
// To attach the body to objects, it is recommended that you use
// these functions from BODY.C:
// disconnects body from any object currently attached to
void disconnect_body();
// connects body to new ovject (disconnects old object if any)
void connect_body(OBJECT *s);
// This variable records curent object: NULL if none
extern OBJECT *body_vehicle_object;
// TELEPORTs are records of user positions and status.
// They record the world, pose (as seen in *body_pose),
// and the object the user is connected to (for moving).
// They are used to implement the HOME key, as well as
// the F1..F10 "cameras" (which aren't cameras, but were
// called that in the REND386 .WLD files!).
// The basic TELEPORT operations are create, delete,
// record pose and "vehicle" object, and execute. At
// present. between-world teleports are not implemented,
// until WORLD support for this is implemented.
// create. record present pose, world, vehicle
TELEPORT *create_teleport();
// release memory of TELEPORT
void delete_teleport(TELEPORT *t);
// record where to teleport to
void teleport_set_here(TELEPORT *t, POSE *p);
// what we're attached to at teleport entry
void teleport_set_vehicle(TELEPORT *t, OBJECT *vehicle, char *vname);
// go to the teleport position/world
// updates *body_pose and reconnects body
void teleport_to(TELEPORT *t);
/////////////////////////////////////////////////////
//// ////
//// POWERGLOVE, MANIPULATION AND DEVICE SUPPORT ////
//// ////
/////////////////////////////////////////////////////
// Other than the mouse manipulation routines in KEYBOARD.C,
// objects may be manipulated with a 3D or 6D pointer device,
// or with the PowerGlove. These routines are well encapsulated
// so little effort is needed to support them: just initialize
// and call them in the main loop.
// These routines should be given a driver file or internal
// driver name, the name of a .PLG file for the 3D pointer,
// and the name of a .FIG file for the glove's hand object.
// The POSE is for scaling: it should contain a SCALE value
// for all parameters. (i.e use float2scale() )
// These are already done based on the .CFG file in INIT.C
// initialize a 3D/6D pointer device
PDRIVER *pointer_initialize(char *ptrdrv, char *ptrobj, POSE *scale);
// initialize the PowerGlove
PDRIVER *glove_initialize(char *glvdev, char *glvobj, POSE *scale);
// To use the devices, simply call one of these in the main loop.
// It reads the device, moves the image of the device, and looks
// for button or gesture to control manipulation.
// read, handle 3D/6D pointer commands
void pointer_process();
// read, handle glove gestures
void glove_process();
// Internally, the processing routines call this routine to
// actually grasp, release, and rotate objects. The commands
// passed depend on the current gesture or button state:
// 3D/6D select/move/rotate
void do_3D_manip(WORD command);
// commands to do_3D_manip
#define FREE_DO 0 // released
#define GRASP_DO 1 // holding
#define ROTATE_DO 2 // "pinch" rotation emulation for 3D dev.
#define SELECT_DO 3 // looking for an object to grasp...
// Because a pointer device works in real-world coords, with
// Z going into screen, the pointer must be mapped to and from
// the internal world coord system, by these functions (POINTER.C)
void pointer_to_world(POINTER *p, CAMERA *c, long *x, long *y, long *z);
void rotate_to_view( CAMERA *c, long *x, long *y, long *z);
// For reportingthe current glove gesture, this function is useful:
// returns text string with gesture,
// or NULL if no glove
char *get_glove_gesture_name();
// Finally, we need a way to initialize the head tracker device.
// This function does that, given a driver file or device name.
// The POSE is the head tracker "offset": a matrix used to
// compensate for tilt and offset of tracker with respect
// to the user's head.
PDRIVER *init_head_device(char *dfname, POSE *hdo);
// and to process the tracker data :
void head_tracker_process();
// the PDRIVER arguments may be ignored as the driver address
// is kept internally by the support modules. However, if NULL
// is returned by the initiializing functions, the driver could not
// be initialized.
/////////////////////////////////////////////////////
//// ////
//// INITIALIZATION AND PROGRAMS ////
//// ////
/////////////////////////////////////////////////////
// FOR OTHER INITIALIZATIONS, LOOK AT INIT.C, PCDEVICE.H ETC.
// SEE ALSO EXAMPLES IN MAIN.C
// alternate-frame stero driver start
// may take over timer
void init_switch_driver(char * swdname);
//////// FROM INIT.C
// sets up renderer, memory, load video driver
// Also reads config file, loads video driver
void preload_initialize(int argc, char *argv[]);
// read configuration. mark loadable file args
// loads .CFG flie, calls EMM init if used
void read_configuration(int argc, char *argv[]);
// This calls:
void read_config_file(FILE *in);
// read input files from cmd line
// loads .PLG, .WLD, and .FIG
void read_input_files(int argc, char *argv[]);
// (WPARSE.C) This can load REND386 v5 .WLD files via a call to:
void read_world(FILE *in);
// and retrieves name list memory later with a call to
void dump_lists();
// If you want to use the standard lights, use this code from WPARSE.C
// It creates the standard lights: one ambient, two point sources.
void create_default_lights();
extern LIGHT *std_lights[3];
extern LIGHT *amb_light;
extern LIGHT *light1;
extern LIGHT *light2;
// report on memory use after loading
// reports EMM use as well
void load_memory_report();
// initialize video system, enter graphics mode
void video_initialize();
// initialize all hardware devices and pointers
void device_initialize();
// call at exit to reset all to DOS mode
void exit_handler(void); /* end program */
/// MAIN PROGRAM: SHOULD:
// - call above initialization routines
// - set up any devices or items not set up by above.
// - start main loop, until running==FALSE:
extern BOOL running;
extern BOOL in_graphics;
// Main loop should do (in this order)
// key scan, process keys for key joystick etc
extern void key_process();
// read and proccess joystick navigation devices to move user
extern void joystick_process();
// move cursor, select objects or menu commands
extern void mouse_process();
// CALL ONLY IF THESE DEVICES ARE ACTIVE
extern void head_tracker_process(BOOL recenter);
extern void glove_process();
extern void pointer_process();
// run any tasks, animations, etc.
// also, update any objects changed by animation
// Now, refresh display if required.
// There are several flags which should be set
// if anything changes, and cleared when the
// screen is redrawn:
extern WORD world_changed, display_changed, position_changed;
// Suggested refresh:
//
// update_body_links();
// screen_refresh(current_camera);
// position_changed = 0;
// world_changed = 0;
// display_changed = 0;
// SEE MAIN.C FOR EXAMPLE.
/////////////////////////////////////////////////////
//// ////
//// OTHER FUNCTIONS ////
//// ////
/////////////////////////////////////////////////////
// ERROR REPORTING
extern FILE *log_file; // error log file, opened in read_cfg
// print error message in appropriate way(s)
WORD errprintf(char *fmt, ...);
// FILE NAME SUUPPORT
// add extension to filename
void add_ext(char *name, char *ext);
extern char loadpath[100]; // default load path
// adds default load path to filename
char *fix_fname(char *name);
////////// PCXMODEY.C
// load, save full-screen PCX file
load_pcx(FILE *in, int page);
save_pcx(FILE *out, int page);
//////////// DRVLOAD.C
// load a video or pointer driver
void *load_driver(char *dfile);
////// THESE ARE ALSO IN PCDEVICE.H
extern void tdelay(long msec); /* replaces borland delay() */
extern long current_time(void); /* returns time in msec */
// returns count per int, speed is a hint (0 for default)
extern int init_timer(unsigned speed, void (*timer_hook)());
extern int init_frame_lock(unsigned speed, void (*frame_hook)());
extern int get_ticks_per_second(void); // always returns 1000