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()
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.
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() ); }
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.
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.
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 }
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())); } } } } });
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)