Next: Molecule objects
Up: Program component descriptions
Previous: Utility objects
Figure 2 illustrates the structure and relationship of the many objects which are used by VMD for storing and rendering graphical objects. These objects are described in detail in section 24. Images are displayed through the use of four main objects (and subclasses thereof):
- DisplayDevice objects, which are responsible for rendering a list of drawing commands to a given device or file;
- DispCmd objects, of which there are several subclasses; each DispCmd subclass represents a single drawing command, such as a command to draw a line or to set the current color.
- Displayable objects, which each represent a single graphical item with a list of drawing commands that may be rendered to a DisplayDevice;
- Scene objects, which store lists of Displayable objects. When requested, a Scene takes a given DisplayDevice and requests the device to render each of the Displayable objects it stores.
Display objects used in VMD.
DisplayDevice is the base class for objects which do the actual rendering of a Scene. Each derived class of this parent must provide versions of virtual functions which prepare the device for drawing (i.e., clear a screen or open a file), render a list of drawing commands, and update the display after drawing, among many other functions. This class is where library-specific drawing commands are encapsulated, for example there exists a OpenGLDisplayDevice subclass which is used to draw images using OpenGL graphics hardware.
A DisplayDevice can also be defined which renders a Scene to a file instead of a monitor, i.e., to a postscript file, a raytracing program input script, or a bitmap image file.
DispCmd objects are used by Displayable objects to construct a list of drawing commands (tokens) in a format which can be processed by a DisplayDevice. When rendering an image, a DisplayDevice does not work directory with a Displayable; instead, the DisplayDevice is given a simple byte array into which drawing commands have been assembled. Each Displayable contains one or more of these drawing lists; they are stored by the Scene and processed by the render routine of a DisplayDevice. The DispCmd objects act to put the data into these drawing lists in the proper format for the operation specified. Each DispCmd object appends its data to the current end of a provided drawing list in this format and order:
Why are things done this way? There are a few reasons:
- Integer code specifying the drawing operation (codes are defined in the file DispCmds.h).
- Integer stating the size of the command, in bytes (not including the code or this size count).
- Data necessary for the drawing command (i.e., XYZ positions of the endpoints of a line or a cylinder).
- Speed - Once a compact display list has been created in a single block of memory, that block may be read quickly by the DisplayDevice when drawing the object. Putting it all as a contiguous block helps take advantage of cache memory.
- CAVE access - The CAVE programming paradigm now requires the drawing program to fork into several different processes, including one process for each wall and an ``update'' process. All these processes run on the same computer (a multi-headed SGI, i.e. a tower Onyx) and use shared memory to communicate what to draw between the update process and the drawing processes. Using a list of simple integer codes and copies of the coordinates for drawing lines, triangles, etc. instead of, say, a list of pointers to DispCmd objects helps reduce the amount of shared memory required and simplifies the drawing processes. Drawing in the CAVE then requires only a specialized CaveDisplayDevice and CaveScene to deal with this shared memory requirement (along with a few other CAVE support routines).
Each Displayble object contains a display list, as well as information about the item to be drawn such as whether to display or hide the object, it's current transformation (how much to rotate, scale, and translate the object), whether it is a 2D or a 3D object, whether it is fixed (set to ignore any requests to scale, translate, or rotate the object), and a list of children. A Displayable registers with one or more Scene objects, so that the Scene then will contain a list of all the Displayable objects which should be drawn when requrested. The Scene is responsible for providing the physical memory storage for a display list, through a request by a Displayable. Each molecule and other graphical item in VMD is a Displayable subclass. These derived objects supply the methods to fill the object's display list with drawing commands for the item to be drawn, i.e., the commands to draw the points for each atom and the lines for each bond in a molecle.
A key feature of each Displayable is that it may contain any number of child Displayable objects, and may also be a child of some other parent Displayable. Each child may be individually translated, rotated, etc., and may be turned on or off individually; but operations to a parent such as a transformation typically affect all the children of that parent as well. For example, if a parent is currently being hidden, so will be all the children of that parent. Also, only Displayable objects which have no parent register with a Scene; all children of a parent are drawn properly when the parent is drawn, so it is only necessary for the very top-level parent to be stored in the Scene.
Since it has been discussed quite a bit already, it is somewhat obvious at this point that Scene objects are used to maintain a database on what should be drawn to a user-specified DisplayDevice. A Scene contains routines for applying transformation such as rotate, scale, etc. to all the (non-fixed) Displayable objects which have registered with it, and contains routines to manage the memory used for display lists. Each Scene contains actually two categories of lists of items, and in each category there are two lists, one for 2D objects and one for 3D objects. These two types of lists are:
- Pointers to all the Displayable objects which have registered with the Scene.
- Pointers to the beginning of each display list (essentially treated as arrays of chars). There may be more of these lists than there are registered Displayable objects, since each Displayable (regardless of whether it is a parent, child, or both) provides the Scene object with a copy of the pointer to its display list.
A Scene is the object primarily responsible for collecting all the objects that are to be drawn and for giving these objects to a DisplayDevice for rendering. This is done as follows:
- The routine scene->prepare(DisplayDevice *) is called by the user. The Scene will call a routine for each registered Displayable that allows the object to prepare to be drawn. This preparation may include, for example, changing which frame in an animation should be shown, or updating the current position of a 3D pointer, or most anything else that needs to be done each time the Scene is drawn.
- The routine scene->draw(DisplayDevice *) is called by the user. This routine proceeds in the following steps:
- Prepare the DisplayDevice for drawing 3D objects.
- For each 3D display list stored by the Scene, call the routine display->render(void *) with the pointer to the display list.
- If stereo is being used, repeat the following step for the other eye.
- Prepare the DisplayDevice for drawing 2D objects.
- For each 2D display list stored by the Scene, call the routine display->render(void *) with the pointer to the display list.
- Update the DisplayDevice after all drawing is complete.
As shown in figure 2, there are many classes derived from Displayable, and actually in fact from Displayable3D. Global Displayable objects of note are LightList, which contains a list of Light objects; Axes, which displays a set of XYZ axes in a corner of the display; Stage, which displays a checkerboard panel to one side of the objects in the Scene; VMDTitle, which displays the VMD title credits and rotating letteres; and ColorList, which is described next. The Light objects each represent one light used to illuminate the objects in a Scene which have defined material characteristics for their surfaces.
Colors in VMD are handled mainly by the ColorList object. This maintains a list of 16 unique colors in the VMD ``colormap'', as well as a color scale of 1024 colors arranged in a selectable pattern of bluegreenred or bluewhitered. For each color there are two versions, a solid color and a semi-transparent color. Each color may be changed through user commands, and for each color there are corresponding material characteristics which are used when solid objects are drawn and the Displayable drawing the solid objects requests that materials be used. The ColorList also maintains several lists of names which are used to specify a color: using the NameList template, a set of color categories are stored in the ColorList, and for each color category there are any number of color objects, which consist of a name and index. For example, in the color category ``Stage'' there are two objects, an ``Even'' object (for the even-numbered stage checkerboard squares) and an ``Odd'' object (for the odd-numbered squares). The index for these objects indicates which of the 16 VMD colors to use to color that square. Displayble objects may request to have new categories created, and to add new color objects to each category. This capability is provided by the ColorUser class, from which each Displayable is derived, thus all objects can automatically access the colors through the functionality of the ColorUser class.
The action of picking graphical objects with a pointer, like clicking on an atom with the mouse, is handled by the Display objects as well. Picking objects requires that VMD know where all the points are in space that may be selected, where the pointer is located when a button is pressed, and what to do once an item is picked. This is managed by objects derived from three `picking' base classes: Pickable, PickList, and PickMode, which are discussed below.
Each Displayable object is derived from the Pickable base class; a Pickable is an object which contains a drawing list with special DispCmd tokens which indicate ``this is a point in space which may be selected''. These picking tokens do not result in anything being drawn on the screen; they are used by the void Displayable::pick(void *) routine to determine if a pointer is over a selectable item. Each picking token contains also an integer tag, which is returned by the picking routine when the pointer is found over an item. When a Displayable wants to put picking tokens in its display list, and then wants to be have its display list be checked for pickable points, it must register with a PickList object (described below). This is done by calling the routine
in the constructor of the Displayable, using the pointer to the Scene in which the Displayable is registered (this is because Scene is derived from PickList). There are several virtual functions which must be provided by an object derived from Pickable, which will be discussed below.
There are two types of pointers which may be used to pick items, 2D or 3D. 2D pointers (i.e. the mouse) report their position in `relative scaled coordinates', that is, the X and Y position of the mouse is provided to the rest of the program as values in the range 0 ... 1, so that the lower-left corner of the graphics window is (0,0), and the upper-right corner is (1,1). The coordinates of 3D pointers, on the other hand, are given to the rest of the program as just the location of the pointer in 'eye' coordinates, i.e. the 3D position after it has been transformed by any internal transformation matrix of the pointer. Each pointing device (the mouse, 3D spatial trackers, etc) must be in a certain picking mode, which determines what action is done when an item is picked; new modes can be easily added to a central source by any object that wants to extend the usability of the picking mechanism. A picking operation consists of three phases:
The effect on objects during these phases may be dependent on which button was pressed (two buttons, left and middle, are assumed), and what the current mode was when the button was pressed. PickMode objects are used to handle the different actions required for different picking modes.
- Start: when a button is pressed by a pointer, a command is issued which checks to see if something is under the current pointer position. If so, a new picking operation is started, and continues until the button (or whatever the pointer is using) is released.
- Moving: As the button is held down, if the start of the picking operation did indeed find something under the pointer, commands are executed as the pointer moves to allow objects to be continually manipulated by the pointer.
- End: When the button is released, some final action may be required, and this is signaled by a command to end the current picking session.
Each picking mode is embodied by a special derivation of the PickMode abstract base class. An example, PickModeQuery, has been added which does the very simple job of just printing out the name of the Pickable object when the picking is ended (and only if the pointer position does not move much between the start and end). Each PickMode simply contains three virtual functions:
- int PickMode::pick_start(DisplayDevice *, Pickable *, int button, int tag, int dim, float *pos)
When a pick is successfully started (which means the tag of the point which was picked has been determined), this routine is called for the PickMode object corresponding to the current pick mode, to let that object perform some special action based on the selection. The item selected is provided, as is the button pressed, the dimension (2 or 3) and position of the pointer, and the DisplayDevice used to find the selected item (this is necessary to allow access to routines which convert 2D screen positions to 3D world coordinates). Finally, the tag of the point in the Pickable that was actually clicked on is provided (more on this later).
- int PickMode::pick_move(same args)
Again, called only for the PickMode object of the current mode for the pointer used; this is called every time the pointer moves to a new position.
- int PickMode::pick_end(same args)
Same as the others, just called when the pointer button is released.
A PickList object contains a list of all the current picking modes which the mouse may be in; these all have unique id's from 0 ... num_modes - 1. The Mouse object (described in a later section) gets this list and adds a submenu to the graphics window pop-up menu. When the mouse is in a picking mode, the cursor changes to a crosshair. The Scene is derived from PickList; it is the PickList which maintains all the coordinating data to manage all the objects which can be picked, and all the different picking modes. The PickList maintains two lists:
The pointer objects call routines in PickList to get the current number of names of picking modes, to check if the current pointer position is over a pickable point (and to find out which one), to tell the program that the pointer is moving while an object is being picked, and to tell the program that the button has been released following a picking operation. Thus, PickList is the ``coordinator'' for all picking operations. PickList contains a routine similar to the draw routine, but which instead checks for picked item and executes the proper action if one is found. The algorithm used is:
- All the Pickable objects which have registered themselves as items which contain points which can be selected with a pointer.
- All the PickMode objects which are used to provide different action capabilities to the same pointer, in an extendible fashion.
- For each Pickable registered, check if it is interested in the current mode. If so, call int Pickable::pick_start(see later). If not, skip the Pickable and go to the next.
2) Then, call int PickMode::pick_start(f)or the PickMode object corresponding to the current pick mode.
To set up a Displayable to operate properly as a useful Pickable, these things must be added to the Displayable (see Axes.C and Axes.h for a good example of how to do these things).
- A version of the virtual routine int Pickable::want_pick_mode(int) must be supplied, which returns TRUE if the given pick mode is one which the item is interested in. If no version of this routine is supplied, the default is to return FALSE, which means the Pickable will never be told when a pick starts, moves, or ends (this may actually be desirable, however, if the PickMode object is to do all the work).
- If the Pickable will need to add new PickMode objects to the PickList (which is the same as the Scene object which the Displayable is added to), or find the index for an existing mode:
- The routine int Pickable::add_pick_mode(char *, int) should be called to add a new mode. The name given is used by this routine to check the PickList if the mode exists already. If so, this returns the current index of that mode without creating a new instance of the proper PickMode object. If the mode has not yet been added, the 2nd arg is a code value given to the virtual function int Pickable::create_pick_mode(int), which will create a new PickMode instance (the particular subclass based on the value of the integer arg). This avoids unnecessary duplication of PickMode objects.
- The index must be saved by the Pickable, and used by want_pick_mode to report if the pointers current pick mode is one the Pickable is interested in.
- In the constructor for the specialized Pickable, the routine int Pickable::register_with_picklist(PickList *) must be called, to tell the PickList (Scene in this case, since Scene is derived from PickList) that the Displayable can be picked.
- Also in the constructor go calls to add_pick_mode, if necessary.
- Pick drawing tokens must be placed in the draw list of the object.
- Finally, if the Displayable will be doing some action based on when it is picked and moved (independent of action performed by PickMode's during this same time), versions of pick_start, pick_move, and pick_end must be supplied. By default, these virtual functions do nothing. Again, see Axes for an example (Axes actually only supplies a version of the pick_move routine, since no action is required by Axes at the start or end).
A key thing that may be needed during the pick_move phase for both Pickable and PickMode objects is the ability to convert a 2D screen coord (the relative scaled coords x and y, valued 0 ... 1) to a 3D world coordinate. The difficult of course is the abiguity in what the Z-axis coordinate should be. The routine
takes a 3D world coordinate (at point A, A3D), and a 2D relative scaled coordinate (the screen position of point B, B2D), and returns the 3D world coordinate for point B (B3D). This works assuming the eye is looking along the Z axis. The coordinate returned is the point where the line formed by the 2D's projection back into 3D space intersects the plane parallel to the XY plane which contains point A (i.e. the point will have the same Z-coordinate as the given point A).
Next: Molecule objects
Up: Program component descriptions
Previous: Utility objects