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