NASA World Wind Java - Picking notes

Last updated july 29, 2007 by Patrick Murris

Picking at objects in WWJ

In WWJ the WorldWindow, via WorldWindowGlAutoDrawable, continually maintains the identities of the objects under the cursor and the terrain position there. This is determined with every redraw. The picking operation is very fast and does not significantly impact display performance.

To determine what is currently under the cursor, the application can simply call:

WorldWindow.getObjectsAtCursorPosition()

Unique color picking - how it works

To determine which object lies under a screen pixel to be 'picked', WWJ will actualy render the world with unique colors for each object, read the color in the frame buffer under the pick position, and find out which object was drawn with that color. This temporary frame is never displayed - see image below.


Frame buffer at the end of the pick process on terrain

At the end of the pick process, the DrawContext has a PickedObjectList of all the objects - if any, that lie under the pick screen position. One of them is on top and is the picked (or selected) object for this frame.

In turn, at the end of the display process, the WorldWindGLAutoDrawable can call the registered SelectListeners with a reference to the PickedObject in the SelectEvent.

WW display process overview

Here is the overall process for one display frame:

WorldWindGLAutoDrawable.display() {

    // Before rendering
    callRenderingListeners( new RenderingEvent() );   

    SceneController.repaint()

        // Clear global picked object list
        initializeDrawContext();

        // Calls pick() on all layers and ordered renderables
        pick();

        // Calls render() on all layers and ordered renderables
        draw();


    // After rendering
    callRenderingListeners( new RenderingEvent() );

    // If current view position has changed
    callPositionListeners( new PositionEvent() );

    // If current picked/selected object has changed
    callSelectListeners( new SelectEvent() );

}

Using PickSupport

Layers that want to provide picking support must implement the pick() method that is responsible for finding out which of its objects is under the pick position and add it to the global picked object list.

At the end of the picking process that list may contain several objects from several layers. It will be sorted out by the scene controller to compute which one is on top.

The pick() method is essentialy the same as the render() method used to draw, with the additon of a couple steps that the PickSupport object will make very easy.

Code sample for a layer

PickSupport pickSupport = new PickSupport();

pick(DrawContext dc, Point pickPoint) {

    // Clear local pickable object list
    this.pickSupport.clearPickList();

    // GL setup for unique colors rendering (no textures, no blend, no fog...)
    this.pickSupport.beginPicking(dc);


    // For each object

        // Get a unique color
        Color color = dc.getUniquePickColor();
        int colorCode = color.getRGB();

	// Find out the picked lat/lon/alt position under the screen pickPoint
	Position pickPosition = null;
	...

        // Add our object to the local pickable object list 
	// Here we refer to this, the current layer, but it could be finer parts of it 
	// - like icons, or geometry faces.
	// (false = not terrain)
        this.pickSupport.addPickableObject(colorCode, this, pickPosition, false);

	// Set GL color
        gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());


	... draw object ...



    // Restore GL states
    this.pickSupport.endPicking(dc);

    // Find out which local object has been picked 
    // and add it to the global dc.pickedObject list
    pickSupport.resolvePick(dc, pickPoint, this);

}

A very similar and more complete code structure can be seen at work in gov.nasa.worldwind.layers.RenderableLayer.java in the doPick() method override.

Merging render() and pick()

In many situations it may be more appropriate to have one single method to handle both rendering and picking. In that case it is possible to query the current draw context to know whether we are in picking mode and what the pick point is.

if(dc.isPickingMode()) 
{
   Point pickPoint = dc.getPickPoint(); // in screen coordinates

   ... do picking steps here

}

Responding to select events

Here is an anonymous select listener example that responds to left click on the world map (from WWJApplet):

private WorldWindowGLCanvas wwd;
...

// Setup a select listener for the worldmap click-and-go feature
this.wwd.addSelectListener(new SelectListener()
{
    public void selected(SelectEvent event)
    {
        if (event.getEventAction().equals(SelectEvent.LEFT_CLICK))
        {
            if (event.hasObjects())
            {
                if (event.getTopObject() instanceof WorldMapLayer)
                {
                    // Left click on World Map : iterate view to target position
                    Position targetPos = event.getTopPickedObject().getPosition();

                    OrbitView view = (OrbitView)WWJApplet.this.wwd.getView();
                    Globe globe = WWJApplet.this.wwd.getModel().getGlobe();

                    // Use a PanToIterator
                    view.applyStateIterator(FlyToOrbitViewStateIterator.createPanToIterator(
                         view, globe, new LatLon(targetPos.getLatitude(), targetPos.getLongitude()),
                         Angle.ZERO, Angle.ZERO, targetPos.getElevation()));
                 }
             }
         }
     }
});

Notes

Picking currently identifies objects at a single pixel, not a small window of pixels. Plans are to allow specification of pick-window size and multiple simultaneous pick windows.

For more on unique color picking see Object Selection Using the Back Buffer, from the original OpenGL Programming Guide (aka The Red Book)