This chapter discusses using FLTK for your OpenGL applications.
Using OpenGL in FLTK
The easiest way to make an OpenGL display is to subclass Fl_Gl_Window.
Your subclass must implement a draw() method which uses
OpenGL calls to draw the display. Your main program should call
redraw() when the display needs to change, and
(somewhat later) FLTK will call draw().
With a bit of care you can also use OpenGL to draw into
normal FLTK windows. This allows you to use Gouraud shading for
drawing your widgets. To do this you use the gl_start() and gl_finish() functions around your
OpenGL code.
You must include FLTK's <FL/gl.h> header
file. It will include the file <GL/gl.h>, define
some extra drawing functions provided by FLTK, and include the
<windows.h> header file needed by WIN32
applications.
Making a Subclass of Fl_Gl_Window
To make a subclass of Fl_Gl_Window, you must provide:
- A class definition.
- A draw() method.
- A handle() method if you need to receive
input from the user.
If your subclass provides static controls in the window, they
must be redrawn whenever the FL_DAMAGE_ALL bit is set
in the value returned by damage(). For double-buffered
windows you will need to surround the drawing code with the
following code to make sure that both buffers are redrawn:
#ifndef MESA
glDrawBuffer(GL_FRONT_AND_BACK);
#endif // !MESA
... draw stuff here ...
#ifndef MESA
glDrawBuffer(GL_BACK);
#endif // !MESA
Note:
If you are using the Mesa graphics library, the call
to glDrawBuffer() is not required and will slow
down drawing considerably. The preprocessor instructions
shown above will optimize your code based upon the
graphics library used.
|
Defining the Subclass
To define the subclass you just subclass the
Fl_Gl_Window class:
class MyWindow : public Fl_Gl_Window {
void draw();
int handle(int);
public:
MyWindow(int X, int Y, int W, int H, const char *L)
: Fl_Gl_Window(X, Y, W, H, L) {}
};
The draw() and handle() methods are
described below. Like any widget, you can include additional
private and public data in your class (such as scene graph
information, etc.)
The draw() Method
The draw() method is where you actually do your
OpenGL drawing:
void MyWindow::draw() {
if (!valid()) {
... set up projection, viewport, etc ...
... window size is in w() and h().
... valid() is turned on by FLTK after draw() returns
}
... draw ...
}
The handle() Method
The handle() method handles mouse and keyboard
events for the window:
int MyWindow::handle(int event) {
switch(event) {
case FL_PUSH:
... mouse down event ...
... position in Fl::event_x() and Fl::event_y()
return 1;
case FL_DRAG:
... mouse moved while down event ...
return 1;
case FL_RELEASE:
... mouse up event ...
return 1;
case FL_FOCUS :
case FL_UNFOCUS :
... Return 1 if you want keyboard events, 0 otherwise
return 1;
case FL_KEYBOARD:
... keypress, key is in Fl::event_key(), ascii in Fl::event_text()
... Return 1 if you understand/use the keyboard event, 0 otherwise...
return 1;
case FL_SHORTCUT:
... shortcut, key is in Fl::event_key(), ascii in Fl::event_text()
... Return 1 if you understand/use the shortcut event, 0 otherwise...
return 1;
default:
// pass other events to the base class...
return Fl_Gl_Window::handle(event);
}
}
When handle() is called, the OpenGL context is not
set up! If your display changes, you should call
redraw() and let draw() do the work. Don't
call any OpenGL drawing functions from inside handle()!
You can call some OpenGL stuff like hit detection and texture
loading functions by doing:
case FL_PUSH:
make_current(); // make OpenGL context current
if (!valid()) {
... set up projection exactly the same as draw ...
valid(1); // stop it from doing this next time
}
... ok to call NON-DRAWING OpenGL code here, such as hit
detection, loading textures, etc...
Your main program can now create one of your windows by doing
new MyWindow(...). You can also use FLUID by:
- Putting your class definition in a
MyWindow.H file.
- Creating a Fl_Box widget in FLUID.
- In the widget panel fill in the "class"
field with MyWindow. This will make FLUID
produce constructors for your new class.
- In the "Extra Code" field put #include
"MyWindow.H", so that the FLUID output
file will compile.
You must put glwindow->show() in your main code
after calling show() on the window containing the
OpenGL window.
Using OpenGL in Normal FLTK Windows
You can put OpenGL code into an Fl_Widget::draw()
method or into the code for a boxtype or other places with some
care.
Most importantly, before you show any windows,
including those that don't have OpenGL drawing, you must
initialize FLTK so that it knows it is going to use OpenGL. You
may use any of the symbols described for Fl_Gl_Window::mode()
to describe how you intend to use OpenGL:
Fl::gl_visual(FL_RGB);
You can then put OpenGL drawing code anywhere you can draw
normally by surrounding it with:
gl_start();
... put your OpenGL code here ...
gl_finish();
gl_start() and gl_finish() set up an OpenGL
context with an orthographic projection so that 0,0 is the
lower-left corner of the window and each pixel is one unit. The
current clipping is reproduced with OpenGL glScissor()
commands. These functions also synchronize the OpenGL graphics stream
with the drawing done by other X, WIN32, or FLTK functions.
The same context is reused each time. If your code changes
the projection transformation or anything else you should use
glPushMatrix() and glPopMatrix() functions to
put the state back before calling gl_finish().
You may want to use Fl_Window::current()->h() to
get the drawable height so that you can flip the Y
coordinates.
Unfortunately, there are a bunch of limitations you must
adhere to for maximum portability:
- You must choose a default visual with Fl::gl_visual().
- You cannot pass FL_DOUBLE to
Fl::gl_visual().
- You cannot use Fl_Double_Window or
Fl_Overlay_Window.
Do not call gl_start() or
gl_finish() when drawing into an Fl_Gl_Window!
OpenGL Drawing Functions
FLTK provides some useful OpenGL drawing functions. They can
be freely mixed with any OpenGL calls, and are defined by
including <FL/gl.H> which you should include
instead of the OpenGL header <GL/gl.h>.
void gl_color(Fl_Color)
Sets the current OpenGL color to a FLTK color. For
color-index modes it will use fl_xpixel(c), which is
only right if this window uses the default colormap!
void gl_rect(int x, int y, int w, int h)
void gl_rectf(int x, int y, int w, int h)
Outlines or fills a rectangle with the current color. If Fl_Gl_Window::ortho()
has been called, then the rectangle will exactly fill the pixel
rectangle passed.
void gl_font(Fl_Font fontid, int size)
Sets the current OpenGL font to the same font you get by
calling fl_font().
int gl_height()
int gl_descent()
float gl_width(const char *)
float gl_width(const char *, int n)
float gl_width(uchar)
Returns information about the current OpenGL font.
void gl_draw(const char *)
void gl_draw(const char *, int n)
Draws a nul-terminated string or an array of n
characters in the current OpenGL font at the current raster
position.
void gl_draw(const char *, int x, int y)
void gl_draw(const char *, int n, int x, int y)
void gl_draw(const char *, float x, float y)
void gl_draw(const char *, int n, float x, float y)
Draws a nul-terminated string or an array of n
characters in the current OpenGL font at the given position.
void gl_draw(const char *, int x, int y, int w, int h, Fl_Align)
Draws a string formatted into a box, with newlines and tabs
expanded, other control characters changed to ^X, and aligned
with the edges or center. Exactly the same output as fl_draw().
Speeding up OpenGL
Performance of Fl_Gl_Window may be improved on some types of
OpenGL implementations, in particular MESA and other software
emulators, by setting the GL_SWAP_TYPE environment
variable. This variable declares what is in the backbuffer after
you do a swapbuffers.
- setenv GL_SWAP_TYPE COPY
This indicates that the back buffer is copied to the
front buffer, and still contains it's old data. This is
true of many hardware implementations. Setting this
will speed up emulation of overlays, and widgets that
can do partial update can take advantage of this as
damage() will not be cleared to -1.
- setenv GL_SWAP_TYPE NODAMAGE
This indicates that nothing changes the back buffer
except drawing into it. This is true of MESA and Win32
software emulation and perhaps some hardware emulation
on systems with lots of memory.
- All other values for GL_SWAP_TYPE, and not
setting the variable, cause FLTK to assume that the
back buffer must be completely redrawn after a swap.
This is easily tested by running the gl_overlay demo
program and seeing if the display is correct when you drag
another window over it or if you drag the window off the screen
and back on. You have to exit and run the program again for it
to see any changes to the environment variable.
Using OpenGL Optimizer with FLTK
OpenGL
Optimizer is a scene graph toolkit for OpenGL available from
Silicon Graphics for IRIX and Microsoft Windows. It allows you
to view large scenes without writing a lot of OpenGL code.
OptimizerWindow Class Definition
To use OpenGL Optimizer with FLTK you'll need to create a
subclass of Fl_Gl_Widget that includes several state
variables:
class OptimizerWindow : public Fl_Gl_Window {
csContext *context_; // Initialized to 0 and set by draw()...
csDrawAction *draw_action_; // Draw action...
csGroup *scene_; // Scene to draw...
csCamara *camera_; // Viewport for scene...
void draw();
public:
OptimizerWindow(int X, int Y, int W, int H, const char *L)
: Fl_Gl_Window(X, Y, W, H, L) {
context_ = (csContext *)0;
draw_action_ = (csDrawAction *)0;
scene_ = (csGroup *)0;
camera_ = (csCamera *)0;
}
void scene(csGroup *g) { scene_ = g; redraw(); }
void camera(csCamera *c) {
camera_ = c;
if (context_) {
draw_action_->setCamera(camera_);
camera_->draw(draw_action_);
redraw();
}
}
};
The camera() Method
The camera() method sets the camera (projection and
viewpoint) to use when drawing the scene. The scene is redrawn after
this call.
The draw() Method
The draw() method performs the needed initialization and does
the actual drawing:
void OptimizerWindow::draw() {
if (!context_) {
// This is the first time we've been asked to draw; create the
// Optimizer context for the scene...
#ifdef WIN32
context_ = new csContext((HDC)fl_getHDC());
context_->ref();
context_->makeCurrent((HDC)fl_getHDC());
#else
context_ = new csContext(fl_display, fl_visual);
context_->ref();
context_->makeCurrent(fl_display, fl_window);
#endif // WIN32
... perform other context setup as desired ...
// Then create the draw action to handle drawing things...
draw_action_ = new csDrawAction;
if (camera_) {
draw_action_->setCamera(camera_);
camera_->draw(draw_action_);
}
} else {
#ifdef WIN32
context_->makeCurrent((HDC)fl_getHDC());
#else
context_->makeCurrent(fl_display, fl_window);
#endif // WIN32
}
if (!valid()) {
// Update the viewport for this context...
context_->setViewport(0, 0, w(), h());
}
// Clear the window...
context_->clear(csContext::COLOR_CLEAR | csContext::DEPTH_CLEAR,
0.0f, // Red
0.0f, // Green
0.0f, // Blue
1.0f); // Alpha
// Then draw the scene (if any)...
if (scene_)
draw_action_->apply(scene_);
}
The scene() Method
The scene() method sets the scene to be drawn. The scene is
a collection of 3D objects in a csGroup. The scene is redrawn
after this call.
From greg.ercolano, 06:42 Nov 18, 2005 (score=3)
If it's the case that FLTK widgets can't be children of an Fl_Gl_Window, that should probably be mentioned here.
[ Reply ] From Anonymous, 11:01 Sep 10, 2004 (score=1)
gl_start and gl_end are deprecated and don't work properly on OSX
[ Reply ] |