Module
class is fundamentally agnostic when it comes to module displays. It provides and invokes the initializeDisplay
, display
, and displayIdle
methods, but it says nothing about how to implement those for development and debugging display.
While the Module
class doesn't enforce a particular brand of display, the ModUtils package does provide libraries to support several types of display, one for 2D displays and one for 3D displays. Both libraries use FLTK, a simple, cross platform GUI toolkit and GL, the standard 3D darwing package, to do their displays. The ModUtils includes the appropriate version of FLTK as part of its installation and assumes you have an appropriate version of GL on your system.
Since both libraries are FLTK based, for both you should set the displayIdle
routine to simply check for FLTK events so that your display works well when the module is paused.
bool BinaryObstModule::displayIdle() { Fl::check(); return true; }
-lDisplay
and supported by the header file include/Display/Display
.h.
The Display library creates a named display which is an instance of the class Display
. You add callbacks to the display which do the 2D GL drawing, and then you add a Display
redraw
invocation to your Module
display
method to actually render your display.
Display
instance. We suggest you set it to NULL
in the module constructure and delete it in the destructor. As an aside, this is a common idiom for optional things, as deletion of NULL is defined in C++ as a no-op, and displays are not always going to be created, i.e., when the display is turned off, the display member variable will stay NULL
.
In your module's initializeDisplay
method you should do somethinhg like,
const char* spec = config->getString("display_spec"); if (!spec[0]) { fprintf(stderr, "No display_spec\n"); return false; } _display = new Display(spec, table);
_display
to a valid Display
instance.
Once you have created the Display
instance, you will add your display callbacks using
_display->add_callback(static_callback, priority, frame, this);
static_callback
is the static wrapper for the rendering routinepriority
is a number, with the higher value meaning draw later than (and therefor on top of) the lower priority callbacks.frame
determines the frame of reference of the callback, and is one ofDISPLAY_VEHICLE:
in the vehicle frame of reference.DISPLAY_GLOBAL:
in the global frame of reference.DISPLAY_WINDOW:
in the local window coordinates.this
is the standard C++ this
pointer
Then you can invoke any number of Display
methods to configure your display. These can include,
_display->size_range(500, 500); // set size to 500x500 pixels _display->track_veh_pos(true); // means display will follow vehicle position _display->track_veh_rot(true); // means display will follow vehicle orientation _display->set_northing_window(50.0); // means the vertical size will be 50m _display->mouse_zoomable(true); // means user can zoom with the mouse _display->mouse_moveable(false); // means user cannot move display with the mouse
Finally, you need a few bits of magic to finish of the initializeDisplay
which will actually show the display
_display->set_visible(); // make sure it will be drawn _display->show(); // make the window shown _display->redraw(); // force an initial redraw
First you must define a status module member function to pass to Display::add_callback
. This static member function's only purpose will be to call a non-static member function with the this
pointer passed in to the add_callback
invocation. You should define the callback functions like this,
private: // static member function // with lots of things you needn't worry about static void map_render_callback( double e_o, double n_o, double low_x, double low_y, double high_x, double high_y, utils::SymbolTable *st, void *data); // the real drawing function void map_render(double e_o, double n_o);
The static member callback will almost always look something like this:
void BinaryObstModule::map_render_callback( // takes easting and northing offsets double e_o, double n_o, // bounding box double low_x, double low_y, double high_x, double high_y, // global symbol table utils::SymbolTable *st, // and passed in user data (module pointer) void *data) { ((BinaryObstModule *)data)->map_render(e_o, n_o); }
The non-static member callback, in this case map_render
, does the actual drawing. You will use normal GL calls such as glRectd
, glBegin(GL_POINTS), glVertex2d, glEnd
, etc.
In the DISPLAY_WINDOW
frame of reference, the coordinates for any drawing functions are simply the window coordinates, but in the DISPLAY_VEHICLE
and DISPLAY_GLOBAL
coordinate system you have to remember that x and y are switched, i.e., a global or vehicle x should be passed in as the y coordinate of a GL call and a global or vehicle y should be passed in as the x coordinate of a GL call.
In the DISPLAY_VEHICLE
coordinate frame, all drawing is done relative to the vehicle position, which you will be setting using the set_veh_pos
routine (as specified in the next section).
In the DISPLAY_VEHICLE
coordinates you should subtract the easting offset (e_o
in our example) from the y coordinate and the northing offset (n_o
in our example) from the x coordinate before passing them into your GL calls. This annoying feature is due to the fact that GL, internally at least on many machines, is fundamentally a 32 bit library, even though it claims to have double precision routines. Thus if your coordinates are really huge numbers, like you might get in UTM's from a GPS system, then the precision of single precision floating point numbers is not sufficient to represent position accurately. Therefor the Display
library caches a base northing and easting offset which you use as an offset to get the coordinates into the realm of the precision of a single precision floating point number. If you do not do this, or the northing and easting offset are set incorrectly, you will see the display "jump around" when it should be fixed due to insufficient precision in the position.
set_veh_pos
if you are going to use any callbacks in the DISPLAY_VEHICLE
coordinate frame, or if you want the display to track the vehicle motion when the track_veh_pos
and track_veh_rot
options (see the Display Configuring section). Then you need to tell the display instance to redraw and check for any FLTK events.
The boilerplate for a 2D display display
method is something like this,
bool BinaryObstModule::display() { // get the absolute pose of the vehicle double northings, eastings, heading; get_absolute_vehicle_position(northings, eastings, heading); // and tell the display code this so we move the display accordingly _display->set_veh_pos(eastings, northings, heading); _display->redraw(); // update the display Fl::check(); // check for any FLTK events return true; // return success! }
You define and register 3D objects such as ground plane grids and point lists and the GlutDisplay
class takes care of rendering these appropriately. You can change your registered objects' parameterizations and those changes will be reflected on the main display. The GLUtils display is normally used for vehicle centered 3D displays, although there is no reason not to use it for global displays.
To use the GLUtils library you must add -lGLUtils
to your library list and include the file include/GLUtils/GlutDisplay
.h for much of the functionality. In most cases, you will also have to include the header files corresponding to the 3D objects you will be using.
GlutDisplay
and necessary 3D objects as member variables GlutDisplay *_display; // the 3D point display GL_PointList * _pts; // the point list object for 3D display GL_Origin *_ori; // the origin for 3D display
In your initializeDisplay
method you should create and configure the display
_display = new GlutDisplay(); _display->set_z_down(); // necessary to get z-axis in right direction
Then, in initializeDisplay
you create your objects
// Create a fixed origin with 1m arms _ori = new GL_Origin(1.0); // Create a point list, which will actually show our changing data _pts = new GL_PointList;
And then, register them with the display
_display->register_object(_pts); _display->register_object(_ori);
Now _display
will render your registered objects.
set_position
, get_position:
set and get an object's 3D positionoffset:
move an object's position in 3Dhide
, show:
chante an object's visibility. You should investigate the header files and documentation for individual object classes for more information on them.
You can get some general ornamentation objects included via include/GLUtils/GlutDisplay
.h (although they are actually definhed in include/GLUtils/gl_object
.h). These include
GL_Ground_Plane:
a ground plane meshGL_Origin:
a 3D originGL_Hash:
a colored hash mark
Besides these ornamentations, you will most commonly use the GL_PointList
class from the GL_PointList.h
. This class can be used for a 3D point cloud display by setting points its render_points
method, or it can be used for a 3D path by setting points via its render_path
method.
Other 3D objects you can use include
GL_Cylinder:
A parameterized cylinderGL_Quadrangle:
a four sided boxGL_Box:
A cubeGL_Lozenge:
a pill shaped thingGL_Annulus:
a 2D dougnutGL_TextureMap:
a 2D texture map.GL_HeightMap:
a 2.5D height mapGL_TexturedHeightMap:
a 2.5D height map overlaid by a textureGL_VectorField:
a 2 or 3 dimensional field of vectorsGlutDisplay
display is done behind the scenes, but if you are going to change object values there are some details that must be taken care of.
First, in your module's display
method you should change the relevant object values.
// display _points in GL window
_pts->render_points(_points, 1.0, 1.0, 1.0, 1);
If you just change the object values, the changes will not propagate to the display until a redisplay is necessary, which by default will only happen when there are mouse events or window exposures. To force a redisplay of your new variables you have to manually post a redisplay:
_display->post_redraw();