2 Rendering

2.7 Object Selection

Some viewer implementations provide support for the interactive selection of renderable components within the viewer via mouse-based selection. The results of such selection are then conveyed back to the application through a selection event mechanism, as discussed in Section 2.7.2.

In order to be selectable, a renderable should implement the interface IsSelectable, which extends IsRenderable with the following three additional methods:

   boolean isSelectable();
   int numSelectionQueriesNeeded();
   void getSelection (
      LinkedList<Object> list, int qid);

The method isSelectable() should return true if the component is in fact selectable. Unless the component manages its own selection behavior (as described in Section 2.7.1), numSelectionQueriesNeeded() should return -1 and getSelection() should do nothing.

Whether or not a viewer supports selection can be queried by the method

boolean hasSelection()

which is also exposed in the Renderer interface. Selection is done using an internal selection repaint (whose results are not seen in the viewer display) for which the viewer creates a special selection frustum which is a sub-frustum of the current view. The selection process involves identifying the selectables that are completely or partially rendered within this selection frustum.

Left-clicking in the view window will create a selection frustum defined by a small (typically 5x5) sub-window centered on the current mouse position. This type of selection is usually handled to produce single selection of the most prominant selectable in the frustum.

Left-dragging in the view window will create a selection frustum defined by the drag box. This type of selection is usually handleed to produce multiple selection of all the selectables in the frustum.

Whether or not the current repaint step is a selection repaint can be determined within a render() method by calling the Renderer method isSelecting().

Within OpenGL-based viewers, selection is implemented in several different ways. If the selection mode requires all objects in the selection frustum, regardless of whether they are clipped by the depth buffer, then OpenGL occlusion queries are used. If only visible objects which have passed the depth test are desired, then a color-based selection scheme is used instead, where each object is rendered with a unique color to an off-screen buffer.

It is possible to restrict selection to specific types of renderables. This can be done by setting a selection filter in the viewer, using the methods

void setSelectionFilter (ViewerSelectionFilter filter);
ViewerSelectionFilter getSelectionFilter ();

where ViewerSelectionFilter is an interface that implements a single method, isSelectable(selectable) that returns true if a particular selectable object should in fact be selected. Limiting rendering in this way allows components to be selected that might otherwise be hidden by non-selectable components in the foreground.

2.7.1 Implementing custom selection

By default, if the isSelectable() and numSelectionQueriesNeeded() methods of a selectable return true and -1, respectively, then selection will be possible for that object based on whether any portion of it is rendered in the selection frustum. No other programming work needs to be done.

However, in some cases it may be desirable for a selectable to mange it’s own selection. A common reason for doing this is that the selectable contains subcomponents which are themselves selectable. Another reason might be that only certain parts of what a component renders should be used to indicate selection.

A selectable manages its own selection by adding custom selection code within its render() method. This typically consists of surrounding the “selectable” parts of the rendering with a selection query block which is associated with an integer identifier. Selection query blocks can be invoked using the Renderer methods

void beginSelectionQuery (int qid);
void endSelectionQuery ();

For example, suppose we have a component which renders in three stages (A, B, and C), and we only want the component to be selected if the rendering for stage A or C appears in the selection frustum. Then we surround the rendering of stages A and C with selection queries:

import maspack.render.*;
...
   void render (Renderer renderer, int flags) {
      ...
      int qidA = 0;  // selection query for stage A
      int qidC = 1;  // selection query for stage C
      if (renderer.isSelecting()) {
         renderer.beginSelectionQuery (qidA);
      }
      ... render stage A ...
      if (renderer.isSelecting()) {
         renderer.endSelectionQuery ();
      }
      ... render stage B ...
      if (renderer.isSelecting()) {
         renderer.beginSelectionQuery (qidC);
      }
      ... render stage C ...
      if (renderer.isSelecting()) {
         renderer.endSelectionQuery ();
      }
   }

It is not strictly necessary to conditionalize calls to beginSelectionQuery() and endSelectionQuery() (or beginSubSelection() and endSubSelection(), described below) on renderer.isSelecting(). That’s because if the renderer is not in selection mode, then these calls simply do nothing. However, conditionalizing the calls may be useful for code clarity or efficiency.

It is also necessary to indicate to the renderer how many selection queries we need, and what should be selected in response to a particular query. This is done by creating appropriate declarations for numSelectionQueriesNeeded() and getSelection() in the IsSelectable implementation. For the above example, those declarations would look like this:

   int numSelectionQueriesNeeded() {
      return 2;
   }
   void getSelection (LinkedList<Object> list, qid) {
      list.add (this);  // place this component on the ’selection’list
   }

The query index supplied to beginSelectionQuery() should be in the range 0 to numq-1, where numq is the value returned by numSelectionQueriesNeeded(). There is no need to use all requested selection queries, but a given query index should not be used more than once. When rendering associated with a particular query appears in the selection frustum, the system will (later) call getSelection() with qid set to the query index to determine what exactly has been selected. The selectable answers this by adding the selected component to the list argument. Typically only one item (the selected component) is added to the list, but other information can be placed there as well, if an application’s selection handler (Section 2.7.2) is prepared for it.

A component’s getSelection() method will be called for each selection query whose associated render fragment appears in the selection frustum. If a component is associated with multiple queries (as in the above example), then its getSelection() may be called multiple times.

Note that the use of beginSelectionQuery(qid) and endSelectionQuery() is conceptually similar to surrounding the render code with glLoadName(id) and glLoadName(-1), as is done when implementing selection in legacy OpenGL using the GL_SELECT rendering mode.

As another example, imagine that a selectable class Foo contains a list of selectable components, each of which may be selected individually. The “easy” way to handle this is for Foo to hand each component to the RenderList in it’s prerender() method (Section 2.2.2):

   void prerender (RenderList list) {
      for (IsSelectable s : components) {
         list.addIfVisible (s);
      }
   }

Rendering and selection of each component is then handled by the renderer.

However, if for some reason (efficiency, perhaps) it is necessary for Foo to render the components inside its own render() method, then it must also take care of their selection. This can be done by requesting and issuing selection queries for each one:

import java.util.LinkedList;
import java.util.List;
import maspack.render.*;
...
   List<IsSelectable> components; // list of selectable components
   int numSelectionQueriesNeeded() {
      // need one selection query for each component
      return components.size();
   }
   void render (Renderer renderer, int flags) {
      int qid = 0; // id for selection query
      for (IsSelectable s : components) {
         if (renderer.isSelecting()) {
            // only render components that are actually selectable ...
            if (renderer.isSelectable(s)) {
               renderer.beginSelectionQuery (qid);
               ... render component ...
               renderer.endSelectionQuery ();
            }
            qid++;
         }
         else {
            ... render component ...
         }
      }
   }
   void getSelection (LinkedList<Object> list, int qid) {
      // place the selected component onto the list
      list.add (components.get(qid));
   }

Note that a call to the Renderer method isSelectable(s) is used to determine which selectable components should actually be rendered when a selection render is being performed. This method returns true if s.isSelectable() returns true and if s is allowed by any selection filter that is currently active in the viewer.

In some cases, some of the selectable components within a class may normally be rendered at once using a single render object. However, when a selection render is performed, each such component must be rendered using a separate draw operation surrounded by the appropriate calls to begin/endSelectionQuery(). This can be done either by rendering each component using its own render method, or by rendering primitive subsets of the render object as described in Section 2.4.8. If the latter is done using a FeatureIndexArray, then the feature number can be used to store the selection query ID. For example, in the example of Listing 9, the render() method can be rewritten to normally draw all the squares at once (using the default color) with a single call to draw(RenderObject), or, when selecting, to render each square separately within a selection query block:

   public void render (Renderer renderer, int flags) {
      if (myRob == null) {
         // create render object and feature index array on demand
         myFidxs = new FeatureIndexArray();
         myRob = createRenderObj (myFidxs);
      }
      renderer.setLineWidth (4);
      if (renderer.isSelecting()) {
         // render each square separately surrounded by a selection query
         for (int fidx=0; fidx<3; fidx++) {
            renderer.beginSelectionQuery (myFidxs.getFeature(fidx));
            drawSquare (renderer, myRob, myFidxs, fidx);
            renderer.endSelectionQuery();
         }
      }
      else {
         // render all squares at once (using the default color)
         renderer.draw (myRob);
      }
   }

Finally, what if some of the components in the above example wish to manage their own selection? This can be detected if a component’s numSelectionQueriesNeeded() method return a non-negative value. In that case, Foo can let the component manage its selection by calling its render() method, surrounded with calls to beginSubSelection() and endSubSelection(), instead of beginSelectionQuery(int) and endSelectionQuery(), as in

   void render (Renderer renderer, int flags) {
      int qid = 0; // id for selection query
      for (IsSelectable s : components) {
         if (renderer.isSelecting()) {
            int numq = s.numSelectionQueriesNeeded();
            if (numq >= 0) {
               // s is managing its own selection
               if (renderer.isSelectable(s)) {
                  renderer.beginSubSelection (s, qid);
                  s.render (renderer, flags);
                  renderer.endSubSelection ();
               }
               // update qid by number of queries requested by s
               qid += numq;
            }
            else {
               if (renderer.isSelectable(s)) {
                  renderer.beginSelectionQuery (qid);
                  s.render (renderer, flags);
                  renderer.endSelectionQuery ();
               }
               qid++;
            }
         }
         else {
            s.render (renderer, flags);
         }
      }
   }

The call to beginSubSelection() sets internal information in the renderer so that within the render() method for s, query indices in the range [0, numq-1] correspond to indices in the range [qid, qid+numq-1] as seen outside the render() method.

In addition, Foo must also add the number of selection queries required by its components to the value returned by its own numSelectionQueriesNeeded() method:

   int numSelectionQueriesNeeded() {
      // compute total number of queries required:
      int total = 0;
      for (IsSelectable s : components) {
         int numq = s.numSelectionQueriesNeeded();
         total += (numq >= 0 ? numq : 1);
      }
      return total;
   }

Finally, in its getSelection() method, Foo must delegate to components managing their own selection by calling their own getSelection() method. When doing this, it is necessary to offset the query index passed to the component’s getSelection() method by the base query index for that component, since as indicated above, query indices seen within a component are in the range [0, numq-1]:

   void getSelection (LinkedList<Object> list, int qid) {
      // find component with the matching qid
      int qi = 0;
      for (IsSelectable s : components) {
         int numq = s.numSelectionQueriesNeeded();
         if (numq >= 0) {
            // See if qid is in the range of queries managed by s.
            if (qid >= qi && qid < qi+numq) {
               s.getSelection (list, qid-qi); // offset the query index
               return;
            }
            qi += numq;
         }
         else if (qi == qid) {
            list.add (s);
            return;
         }
      }
   }

2.7.2 Selection Events

Components selected by the viewer are indicated to the application via a selection listener mechanism, in which the application registers instances of ViewerSelectionListener with the viewer using the methods

   void addSelectionListener (ViewerSelectionListener l);
   void removeSelectionListener (ViewerSelectionListener l);
   ViewerSelectionListener[] getSelectionListeners();

The listener implements one method with the signature

   void itemsSelected (ViewerSelectionEvent e);

from which information about the selection can be obtained via a ViewerSelectionEvent. This provides information about all the queries for which selection occured the methods

   int numSelectedQueries();
   int getFlags();
   int getModifiersEx();
   LinkedList<Object>[] getSelectedObjects();

numSelectedQueries() returns the number of queries that resulted in a selection, getModifiersEx() returns the extended keyboard modifiers that were in play when the selection was requested, and getFlags() returns information flags about the selection (such as whether it was a DRAG selection or MULTIPLE selection is desired).

Information about the selected components is returned by getSelectedObjects(), which provides an array (of length numSelectedQueries()) of object lists for each selected query. Each object list is the result of the call to getSelection() for that selection query. As indicated in Section 2.7.1, each object list typically contains a single selected component, but may contain other information if the selection handler is prepared for it.

The array provided by getSelectedObjects() is ordered so that results for the most visible selectables appear first, so if the handler wishes to select only a single component, it should look at the beginning of the list. Also, if the rendering for a single component is associated with multiple selection queries, mutiple results may returned for that component.