SFA Draw Class Explanation
This document explains the SFA draw class as of June
15, 1999. The draw class is responsible for controlling all OpenGL based
components of SFA, and the majority of the code used to draw the scene
and glyphs is found in this class.
This document is broken down into two major sections, one describing
SFA
basic functionality and one describing the basic
functional groups of the Draw class.
Basic functionality of the draw class
The draw class is responsible for controlling or calling all OpenGL related
calls in SFA. These range from setting up the OpenGL visual to actual OpenGL
calls drawing glyphs.
The main area of concern in the draw class is the main draw loop. This
is the section of code called once per frame in OpenGL. Depending on the
dataset loaded, the innermost loop of code in SFA can be executed tens
of thousands of times per frame (once per glyph being rendered). This code
must obviously be heavily optimized to maintain interactive framerates
of 15fps or greater if at all possible. Some machines may not be able to
handle the heavy geometry required by some of the more intensive glyph
shapes at interactive framerates, but we should not be constrained by the
speed of the software.
To this end, the inner draw looping structures have been completely
rewritten from last summer. The original looping structures worked on a
more compact, but slower execution model of looping through the glyphs
and preventing code duplication as much as possible. However, this caused
a number of extra comparisons for each glyph since this comparison code
was embedded in the inner loop to prevent code duplication.
The new method adds considerable code duplication, but prevents unnecessary
comparisons at the innermost loop level. Each glyph type (including each
scalar and vector subtype) have their own innermost loops, completely optimized
based on the specific code of that particular glyph type. Some glyphs types
are grouped based on like code types, while others have their own individual
code.
Another significant speed improvement comes from the removal of redundant
state management code. A number of OpenGL state management calls were being
made at incorrect/illegal locations and were causing OpenGL error handlers
to be called. These handlers are extremely slow, and when these error handlers
were being called from the inside of the innermost draw loops for various
glyph types, the slow down to SFA was significant. In the case of non-geometry
bound items such as points, speedups of approximately 15 times have been
achieved.
There are also times when we do not necessarily want to be slowing down
user interaction in order to draw the complete set of glyphs. This would
include times of the user manipulating the viewpoint (dragging the set
around with the mouse or trackers, focusing on single areas of the dataset,
etc). SFA supports a number of mechanisms to increase interaction speed
for these purposes.
At the simplest level, SFA allows the user to specify to not draw every
glyph by cutting out glyphs based on their positions along the three axes.
For example, the user can specify to only draw every 5th glyph along the
X axis. Internally, SFA keeps track of every unique X position in use by
the dataset. This list of positions is sorted, and only glyphs which fall
into every 5th sorted bucket would be drawn.
At a slightly higher level, SFA supports user removal of glyphs based
on subsetting. Subsetting is internally handled in the file Subset.C, but
the interaction and drawing control is handled in Draw.C. The user can
sweep out up to seven boxed subsets (Note: A version of spherical subsetting
has been coded and is awaiting integration with the main SFA development).
In the case of a single subset, only glyphs that fall within the extents
of the single subset are drawn. This allows the user to focus on areas
of interest and eliminate other glyphs not of interest which are slowing
down the rendering or possibly occluding the area(s) of interest.
If multiple subsets are swept out, code in Subset.C calculates if there
are any overlapping zones with the subsets. Any overlapping zones are drawn
in full resolution (all glyphs drawn), while any areas not part of an overlap
are drawn at a lower resolution (currently 1 out of every 5 glyphs is drawn).
This allows the user to continually refine the area of interest until the
desired level is reached.
Finally, SFA supports automatic glyph removal while the user is interacting
with the scene in a way that automatic glyph removal does not detract from
the action being performed. Currently this only happens when the user is
dragging the scene, either with the trackers or the mouse.
While the scene is being dragged around, SFA draws as many glyphs as
possible in 100ms per frame. This ensures a minimum framerate of approximately
8fps, which allows user interaction in realtime even when dealing with
datasets/shapes which normally would draw in much longer times than 100ms.
Which glyphs does SFA draw and which ones does it throw out? Currently,
SFA uses a caching method indexed on the XSize attribute. It then draws
in sorted order, from highest XSize to lowest XSize, as many glyphs as
it can in the given time. This means SFA will draw all the largest glyphs
first, and the smaller glyphs last.
Functional Groups
The draw class is broken down into a series of functionally connected groups.
These groups are:
Initialization Functions
SFADraw( Boolean );
Constructor which initialize all appropriate members of the class, including
pointers to other classes, set sizes, and default values for a handful
of items. Since there is no OpenGL rendering context available at the time
this code is called, no OpenGL specific items can be placed here. OpenGL
specific initialization is found in updateViewport(), described later.
void init( void );
Originally used to initialize class variables. Obsolete. Code has been
moved to constructor. Remove when time permits (requires change to Form.C
as well, since it calls this function).
void setWidget( Widget );
Sets the OpenGL widget to render to. Before we can make any OpenGL specific
calls, we need to have an OpenGL rendering context tied to a special Motif
widget. This function tells the draw class which Motif widget we are using
to create the OpenGL rendering context.
XVisualInfo *GetGLVisual( void );
This function makes X and GL calls in order to request (if possible) a
double buffered stereo visual for SFA to use. If not double buffered stereo
visual is available, a double buffered non-stereo visual is requested.
If this is not available, SFA exits with an error, since we need at least
a double-buffered visual for SFA.
Function called from Form.C to initialize any required variables. This
code has been moved to the constructor, so now is empty.
void buildTranslateMap( void );
void buildVectorMap( void );
void buildArctanMap( void );
These three functions are used to build the internal mappings used to reduce
the number of floating point operations required per glyph. For example,
the translate map stores floating point translation values for each possible
position variable which could come from the PackedGlyph structure. This
saves one floating point division per glyph, which can quickly add up when
thousands of glyphs are being drawn.
The translate map is used for glyph positions, the vector map is used
for quick calculation of vector sizes, and the arctangent map is used for
quick calculation of vector direction.
void create_drawing_lists( void );
void buildBorder( void );
void buildCube( GLfloat );
void buildStem( void );
These functions are used to generate the various OpenGL display lists used
in SFA. Display lists are used to speed up frequently made OpenGL operations
by compiling them into a compact binary representation.
Main Draw Functions
void end_bounding_box( void );
void draw_bbox( void );
void draw_transparent_cube( GLfloat, GLfloat, GLfloat, GLfloat, GLfloat,
GLfloat );
void drawMouseSubsetCube( void );
These functions handle the interaction and drawing of the subset boxes,
both with tracker and mouse based interaction. The transparent cube is
only drawn when the subset is actually being swept out, in order to prevent
the cube from getting in the way of the regular visuals.
void DrawBBoxInsets( void );
This function draws the 'thumbnails' for both subsets and plates. Subset
thumbnails are drawn below plate thumbnails, seperated (if applicable)
by a thick black line showing the difference. Each thumbnail contains a
miniature rendering of the set bounding box and the subset or plate being
represented. No glyphs or other markings are drawn in order to save on
rendering time.
void draw( Widget );
void drawGlyphs( DataSet *, PackedGlyph **, int **, int **, int **, int
);
These are the main draw routines in SFA. The draw() routine is executed
once per frame. It is responsible for making OpenGL calls and calling other
draw specific code in order to have the current frame rendered into the
backbuffer, then swapping the buffers to display the new frame to the window.
The drawGlyphs() function encapsulates the majority of the drawing code
into a single function entry point. This is useful since when we are in
stereo mode each frame must be rendered twice, from difference perspectives.
The draw() routine simply sets up the appropriate perspective and calls
the drawGlyphs() function to render that perspective. It then changes the
perspective appropriately, and calls drawGlyphs() again.
See the section describing the general workings
of the draw class for more information about these functions.
void drawLabels( void );
void drawWallLabels( Point3, char * );
These functions are used to handle text-as-3d-object code. Fonts in SFA
are handled different when using MR than without, so these functions are
called and perform differently based on the current state of using MR.
If MR is present, MR panels are used to draw the fonts (code found in Labels.C).
If MR is not present, a special textured font is used (code found in TexFont.C).
Mouse Functions
void DoMouseTransform( XButtonEvent * );
void DoSpin( void );
void DoMove( void );
void DoZoom( void );
void DoMouse( XbuttonEvent * );
void DoMouseSubset( XbuttonEvent * );
void set_transform( void );
void ui_to_worldspace( short, short, float *, float * );
These functions are used to handle mouse interaction with SFA. The DoMouse()
function is setup as the X mouse event handler, called anytime a mouse
event is generated. This function decides based on the state of the mouse
(based on keyboard presses, Alt, Control, Shift, etc) which sub-function
to call, DoMouseTransform(), DoMouseSubset(), and eventually DoMousePlate().
These functions then manage their appropriate tasks based on the mouse
input. Simple functions like mouse transforms are handled inside the class,
while more complex functions (like subsetting) have their code abstracted
to other classes, which these functions will call.
Assorted Other Functions
int isStereo( void );
This function returns whether or not we are currently in stereo mode.
int findSFAPoint( PackedGlyph **, int *, int *, int, THCursor );
int findSFAPoint2( PackedGlyph **, int *, int *, int, THCursor );
inline float spotdist2( Point3, Point3, Point3 );
These functions cycle through the glyphs and uses either the tracker position
or mouse position (depending on whether we are using MR or not) to determine
which glyph is selected. It uses a line of site calculation through the
cursor (tracker or mouse) and calculates a threshold distance away from
the line of site for each glyph. The glyph with the lowest distance away
from the line of site is the selected glyph.
int getCursorCount( void );
void setCursorCount( int );
Returns/sets the number of trackers we are currently using.
void expose( void );
static Boolean workExpose( void );
These functions are used in conjunction with X window events (including
an XtWorkProc) to cause the SFA OpenGL widget to be redrawn. The workExpose()
function is setup as an event handler for an XtWorkProc setup in the mainline
of SFA. This acts as a 'heartbeat' for animation for SFA.
void resize( void );
An X event handler called anytime the user resizes the main SFA window.
Since a resize does not necessarily preserve the aspect ratio, this function
takes the new aspect ratio into consideration while resetting the OpenGL
view to make sure we don't skew the images.
inline GLfloat atanMap( int, int );
This function queries the prebuilt arctangent map for the given X and Y
values. See the arctan build function description for information on why
this is used.
void set_setsize( Point3, Point3 );
Used to set the minimum and maximum extents of the bounding box. Currently
parameters other than {0.0,0.0,0.0} and {1.0,1.0,1.0} will cause undefined
behaviour, but this function is setup for future use if we want to define
different sized volumes.
void updateViewport( Widget );
Called each time the OpenGL viewport needs to be reinitialized, either
at the start of the application or when the window is resized by the user.
The first time this function is called, it is the first code entry point
to Draw.C where we have a full OpenGL rendering context attached to a widget,
so we can now make our OpenGL initializations here, including lighting
calllists, object calllists, and basic camera information.
~SFADraw( void );
Basic destructor. Frees up all dynamically allocated memory the draw class
uses.
back to main page
James Hall
Last Modified: June 16, 1999