gleem: OpenGL Extremely Easy-to-use Manipulators Documentation

[Picture of Manipulators]

Last modified: 1/27/2002. Current gleem version: 1.2

Introduction and Description

gleem is a small, self-contained library of 3D widgets that support direct user interaction with a 3D scene. Both Java and C++ programming language implementations are included and have similar APIs. The user interfaces are roughly based on the manipulators in Silicon Graphics' Open Inventor; the names are borrowed from Inventor.

The currently implemented manipulators are:

The most recent version of gleem is available from http://www.media.mit.edu/~kbrussel/gleem/.

gleem uses OpenGL for rendering of its manipulators. The Java programming language version of gleem is built on top of Jausoft's OpenGL for Java and uses the Java platform's window system support to collect mouse events; the C++ version uses GLUT for window system interaction. All of the data structures for storing shapes and computing mouse pointer-object intersections are done within the library, not using the OpenGL picking mechanisms.

gleem currently does not implement Inventor-style fields, field-to-field connections, reference counting, run-time type checking, instantiation by type ID, or any other of the basic mechanisms which made Inventor possible. It is implemented entirely using virtual functions; there is no casting down the hierarchy based on run-time type checks.

The most fundamental limitation of gleem is that, in the interest of portability, it has no notion of a scene graph. It defines what little such support it needs internally. All manipulators live conceptually in world coordinates. If you want to attach a manipulator to the end of a kinematic chain, for example, you will need to manually keep the manipulator in place as your application moves the links.


How to Incorporate gleem Into Java Programs

Compiling gleem for Java

The Java programming language version of gleem was developed with JDK 1.4 and OpenGL for Java 2.8. The sources make use of the new assert keyword and therefore will not compile with earlier JDKs.

Rather than dropping the OpenGL for Java jar and DLL/DSO into the JDK, it is recommended to set up CLASSPATH and either PATH or LD_LIBRARY_PATH entries to point at an installation elsewhere on the local disk. This should avoid conflicts when launching applications with Java Web Start that attempt to download a particular version of OpenGL for Java.

Incorporating Manips into a Java Program

To use gleem's manipulators in a Java program:

  1. Use the GLEventListener programming model in OpenGL for Java rather than subclassing GLCanvas or any of the other widget classes. gleem's ExaminerViewer, in particular, relies on being able to add a GLEventListener to the GLDrawable to which it is attached.
  2. Register each GLDrawable with the ManipManager by calling ManipManager.registerWindow() from within your GLEventListener's init method.
  3. If you choose not to allow the ManipManager to install MouseListeners and MouseMotionListeners for a given window (usually because you need to consume some of these events in your application without their being processed by gleem; see the ExaminerViewer for an example), call ManipManager.removeMouseListeners() and then send mouse motion, drag, and button events to gleem using ManipManager's mouseMoved, mouseDragged, mousePressed, and mouseReleased methods.
  4. Register each manipulator in the windows you want it to be visible by calling ManipManager.showManipInWindow().
  5. Add a call to ManipManager.updateCameraParameters() to your GLEventListener's display method with parameters of your viewing frustum (forward and up vectors, vertical FOV, image plane aspect ratio, horizontal and vertical size of window). gleem currently only handles frusta corresponding to symmetric truncated pyramids.
  6. Add a call to ManipManager.render() to your display method.

Note that gleem's Java programming language APIs are more strict than the C++ versions about explicitly registering and unregistering windows. The Java platform's window system interface is inherently multithreaded and the added strictness should help ensure the correctness of applications using the library.

You can then call methods such as getTranslation() and getRotation() on the above manipulator classes in, for example, your display method, and feed those values elsewhere. You can also register a motion listener via Manip.addMotionListener() which will be called each time the manipulator is moved.

See the sources TestTranslate1.java, TestTranslate2.java, and TestHandleBox.java for specific, small examples of instantiating manipulators.

Incorporating the Examiner Viewer

The ExaminerViewer class implements trackball-style rotation, translation, and dollying of the scene. Its controls are similar to those of Open Inventor's Examiner Viewer; see the user interface section below for details.

To use the ExaminerViewer in conjunction with the ManipManager:

  1. Register the GLDrawable with the ManipManager. This should generally be done within the init method of your GLEventListener.
  2. Instantiate an ExaminerViewer. The MouseButtonHelper class can be used to determine how many mouse buttons are present.
  3. Attach the ExaminerViewer to the drawable. Note that this must be done after the drawable has been registered with the ManipManager. At this stage a BSphereProvider, used during viewAll() operations, must be specified.
  4. (Recommended) Call the viewAll method on the viewer to set it up for initial interaction.

See TestExaminerViewer.java for an example which creates a HandleBoxManip inside of an ExaminerViewer.

Multiple Window and Multi-Threading Support

As the Java platform's window system is inherently multithreaded, the Java version of gleem is necessarily mostly thread-safe. The ManipManager class is thread-safe, but individual manipulator instances are not. Multiple window support is handled through the ManipManager's registerWindow, unregisterWindow, showManipInWindow, and removeManipFromWindow methods.


How to Incorporate gleem Into C++ Programs

Compiling gleem for C++

The C++ version of gleem was originally developed under SGI's Irix 6.5 and later ported to Windows. The Unix Makefile is currently set up for SGI's CC and include directories. Type "make" in the gleem subdirectory to build the library and tests. You will need to set your LD_LIBRARY_PATH to, for example, /users/yourname/gleem-1.2/c++-impl/lib/iris before running any of the tests.

Project files for Microsoft's Visual C++ are located in the subdirectory c++-impl/gleem/visualc .

The sources should be quite portable and compilable with any recent C++ compiler. STL support is required and SGI's version of the STL is included in the distribution. By default gleem puts all of its datatypes into the namespace "gleem", but if your compiler doesn't support namespaces you can define the preprocessor variable GLEEM_NO_NAMESPACES (Namespace.h). If you port gleem to another platform, please consider making a cross-platform Makefile for gmake and submitting your work. The "machtype" script in the top-level directory may be useful for this purpose.

Incorporating Manips into a C++ Program

To use gleem's manipulators in a C++ program using OpenGL and GLUT:

  1. Add a call to ManipManager::init() to your main(). There must be an open window at this point if you want the ManipManager to install its callbacks. (To disable callback installation, call ManipManager::init(false).)
  2. If you choose not to allow the ManipManager to install mouse motion, passive motion, and mouse button callbacks (usually because you need to consume certain kinds of events in your application without their being processed by gleem; see the ExaminerViewer and TestExaminerViewer for an example), you need to call ManipManager's mouseFunc, motionFunc, and passiveMotionFunc methods at the end of your callbacks for events that should be processed by gleem.
  3. Make regular calls to ManipManager::updateCameraParameters() with parameters of your viewing frustum (forward and up vectors, vertical FOV, image plane aspect ratio, horizontal and vertical size of window). gleem currently only handles frusta corresponding to symmetric truncated pyramids.
  4. Add a call to ManipManager::render() to your display callback.

You can then call methods such as getTranslation() and getRotation() on the above manipulator classes in, for example, your GLUT render callback, and feed those values elsewhere. You can also register a motion callback via Manip::addMotionCallback() which will be called each time the manipulator is moved.

See the sources TestTranslate1.cpp, TestTranslate2.cpp, and TestHandleBox.cpp for specific, small examples of instantiating manipulators.

Incorporating the Examiner Viewer

The ExaminerViewer class implements trackball-style rotation, translation, and dollying of the scene. Its controls are similar to those of Open Inventor's Examiner Viewer; see the user interface section below for details.

To use the ExaminerViewer in conjunction with the ManipManager:

  1. Instantiate an Examiner Viewer (i.e., ExaminerViewer *viewer = new ExaminerViewer();). This creates a new window internally and overrides mouse and motion callbacks.
  2. Call ManipManager::init(false);. It isn't possible to re-install the ExaminerViewer's callbacks if they're overridden, so it's important not to let the ManipManager do so.
  3. Set up the ManipManager as a mouse and motion delegate for the ExaminerViewer.
      viewer->setMouseDelegate(ManipManager::mouseFunc);
      viewer->setMotionDelegate(ManipManager::motionFunc);
    
  4. To get highlighting of manips to work, set up the ManipManager's passive motion callback manually.
      viewer->makeCurrent();
      glutPassiveMotionFunc(ManipManager::passiveMotionFunc);
    
  5. (Recommended) Give the ExaminerViewer a BSphereProvider so it can compute the bounding sphere of the scene, and call viewAll() before the first render. See TestExaminerViewer.cpp for an example of how you might compute a bounding sphere for a manipulator.
  6. Add a call to viewer->update(); before you begin drawing your geometry. This recomputes the ModelView and Projection matrices for the current rendering context. You should then be able to push matrices on top of the one loaded by the ExaminerViewer to effect a camera moving about the scene.

See TestExaminerViewer.cpp for an example which creates a HandleBoxManip inside of an ExaminerViewer.

Multiple Window and Multi-Threading Support (or Lack of)

gleem supports rendering manipulators in multiple windows. See ManipManager::windowCreated(), ManipManager::windowDestroyed(), ManipManager::addManipToWindow(), and ManipManager::removeManipFromWindow. By default, manipulators are rendered in the window which was current at the time of their instantiation (determined via glutGetWindow().) See TestMultiWin.cpp for an example of rendering the same manipulator into two windows.

The C++ version of gleem is not thread-safe. You should not attempt to instantiate or use gleem objects in more than one thread. Further, gleem assumes it is valid to make OpenGL calls, so the library will probably not work properly in a multiprocessing environment like SGI's Performer without modification.


User Interface

Translate1Manip and Translate2Manip User Interface

Click and drag to cause motion along a line or in a plane.

HandleBoxManip User Interface

The HandleBoxManipulator contains a box with six faces. Clicking and dragging on any of these six faces causes translational motion in the plane parallel to the face.

Each of the six rotation handles, which lie above the cube's faces, can be used to rotate the manipulator about two possible axes. At the start of a drag motion, one of the two rotation axes is chosen: the one most parallel to the viewing direction. If you visualize the two perpendicular planes which go through the handle, the center of the cube, and the three other rotation handles, the plane in which rotation occurs is the one which is facing the camera the most when the drag is initiated.

Scaling is performed with the eight corner handles. Clicking and dragging these handles causes aspect ratio-preserving scaling about the origin of the cube. Shift-clicking and dragging allows stretching of the cube about one of two axes. The two possible axes are chosen when the handle is first shift-clicked, and are chosen to be the principal axes of the face adjacent to the handle which most faces the camera. Scaling along either of these two axes may be the result of the drag; which of the two is chosen is determined using a "snap-to" paradigm, and can change during the drag.

ExaminerViewer User Interface

The ExaminerViewer behaves similarly to Inventor's Examiner Viewer. It grabs all drag motions when the Alt key is depressed. The ExaminerViewer has a conceptual "focal point" which is always defined as being the focal distance directly in front of the camera.

Drag motions with Alt key depressed:

Calling the viewAll() method re-positions (but does not re-orient) the ExaminerViewer so the bounding sphere given by the BSphereProvider is contained entirely within the viewing frustum. The focal distance is reset to be the distance between the camera and the center of the bounding sphere.


Developer Information

Per-Class Documentation

gleem's javadoc documentation can be browsed on-line.

Library Overview

Manipulators contain Manipulator Parts. The ManipPart hierarchy is divided into two sections, one containing triangle-based pieces of manipulators and one which contains grouping nodes (analogues to Group and Transform nodes). This is gleem's scene graph mechanism. A warning: in the C++ version, shared instancing of "nodes" (ManipParts) in this scene graph is not allowed (since reference counting is not implemented). The ManipManager keeps track of all instantiated manipulators and handles the picking mechanism as well as rendering.

The Manip base class only implements a minimal interface; it is intended that you refer to manipulators through a pointer to the concrete type rather than the abstract Manip type. The simple manipulators (Translate1Manip, Translate2Manip) contain replaceGeometry() methods so you can customize the draggers with whatever geometry you like. It is not so simple to customize a more complex manipulator like the HandleBoxManip, so it contains no such method.

Each manipulator understands its constraints and implements its drag() method from scratch. Clearly there is some commonality between the Translate2Dragger and the translation functionality of the HandleBoxManip. However, this commonality has not been abstracted into a set of classes like Inventor's projectors. Instead there are a few geometrical classes (Line, Plane, PlaneUV) which encapsulate such functionality as ray casting and point projection, and the manipulators determine how to use this information based on what manipulator part was selected and the current configuration of the manipulator.

The linear algebra gleem uses is quite simple and should be accessible to anyone with anyone who has taken an undergraduate linear algebra course. I encourage you to experiment with more sophisticated algorithms for the drag mechanisms.

Recommended texts:


Licensing Terms

gleem is distributed under the GNU Lesser General Public License, version 2.1, or any later version.

gleem includes a copy of SGI's STL implementation. Here is the copyright notice from this library:

/*
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Hewlett-Packard Company makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 *
 * Copyright (c) 1996
 * Silicon Graphics Computer Systems, Inc.
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Silicon Graphics makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 */


Frequently Asked Questions

Q: Why don't the manipulators show up in the right place in my application? (Or, why can't I drag them?)

A: gleem expects the following from the rendering state at the time you call ManipManager's render method:

The latter is likely to be the problem. gleem expects to be able to render its manipulators in world coordinates, and the camera parameters you give the library must correspond to the current state of the ModelView and Projection matrices. See TestExaminerViewer.java or TestExaminerViewer.cpp for an example of how an application using gleem might be structured.


Known Problems

The event handling code is inefficient in the case of multiple events per render, which typically occurs when there are many manipulators in the scene and rendering is slow. The mouse motion and passive motion event handlers in ManipManager should be restructured to compress the event input stream into salient events like "highlighted here" (after a stream of passive motion events) and "dragged here" (after multiple mouse drag events), the actual work being done in render(). This will probably require tricky state management in ManipManager. Perhaps render() should be renamed update() since it will be doing more work.


Interesting Links


Version History


Kenneth B. Russell - kbrussel@alum.mit.edu
$Id: index.html,v 1.4 2002/01/28 03:54:34 kbr Exp $