Picking |
Picking allows users to select and interact with objects in the 3D scene. Picks are usually executed in response to a mouse click, mouse move, key press, or any combination of these. For picking on screen overlays, see the Screen Overlays topic.
The pick method returns information about objects in the scene that are at a specified pixel. The inputs are X and Y coordinates. The origin is the top, left corner of the 3D control. Typically, the input values will be the location of the mouse cursor, in order to pick objects under the cursor. This section explains the collection returned by pick, and the following sections describe how implement different types of picking.
pick returns a collection of PickResults. Each PickResult represents one occurrence of a pick. This includes the object(s) involved in the pick and their position. This is shown in the image below:
If multiple objects are at a pixel, the collection will contain multiple pick results. The first item in the collection will be the top most object. Subsequent items will be "under" the previous item. For example, if the camera is looking straight down at a primitive on the ground and pick is called with coordinates that match the primitive, both the primitive and central body will be returned, as shown below:
List<PickResult> collection = getScene().pick(x, y); if (collection.size() == 2) { Object primitiveObj = collection.get(0).getObjects().get(0); Object centralBodyObj = collection.get(1).getObjects().get(0); if (primitiveObj instanceof Primitive && centralBodyObj instanceof CentralBody) { // primitive and central body underneath were picked Primitive primitive = (Primitive) primitiveObj; CentralBody centralBody = (CentralBody) centralBodyObj; } }
In other cases, the collection will only contain a single pick result. For example, if a model primitive representing a satellite is picked, the central body may not be returned as shown below.
List<PickResult> collection = getScene().pick(x, y); if (collection.size() == 1) { Object modelObj = collection.get(0).getObjects().get(0); if (modelObj instanceof ModelPrimitive) { // Just a model primitive was picked ModelPrimitive model = (ModelPrimitive) modelObj; } }
As shown in the code examples, a pick result contains a collection of objects. In most cases, this collection will only contain one object. One exception is when a picked primitive is in a CompositePrimitive. In this case, both the primitive and its composite will be in the objects collection, as shown in the following image and code example:
List<PickResult> collection = getScene().pick(x, y); if (collection.size() == 2) { List<Object> objects = collection.get(0).getObjects(); if (objects.size() == 2) { Object compositeObj = objects.get(0); Object primitiveObj = objects.get(1); Object centralBodyObj = collection.get(1).getObjects().get(0); if (compositeObj instanceof CompositePrimitive && primitiveObj instanceof Primitive && centralBodyObj instanceof CentralBody) { // A primitive in a composite and the central body // underneath were picked CompositePrimitive composite = (CompositePrimitive) compositeObj; Primitive primitive = (Primitive) primitiveObj; CentralBody centralBody = (CentralBody) centralBodyObj; } } }
For primitives that contain multiple items, like the MarkerBatchPrimitive, TextBatchPrimitive, PointBatchPrimitive, and PolylinePrimitive, it is often useful to know what particular item was picked. Per-item picking can be enabled for these primitives by setting the PerItemPickingEnabled (get / set) property on the primitive to true. When enabled, the object collection will also contain the BatchPrimitiveIndex of the item in the primitive that was picked, as shown in the following image and code example:
List<PickResult> collection = getScene().pick(x, y); if (collection.size() == 2) { List<Object> objects = collection.get(0).getObjects(); if (objects.size() == 3) { Object compositeObj = objects.get(0); Object primitiveObj = objects.get(1); Object indexObj = objects.get(2); Object centralBodyObj = collection.get(1).getObjects().get(0); if (compositeObj instanceof CompositePrimitive && primitiveObj instanceof Primitive && indexObj instanceof BatchPrimitiveIndex && centralBodyObj instanceof CentralBody) { // An item in a primitive in a composite and the central body // underneath were picked CompositePrimitive composite = (CompositePrimitive) compositeObj; Primitive primitive = (Primitive) primitiveObj; BatchPrimitiveIndex index = (BatchPrimitiveIndex) indexObj; CentralBody centralBody = (CentralBody) centralBodyObj; } } }
In normal picking, pick is called as the result of an event, such as a mouse click in the 3D control, to perform an action, such as zooming to a primitive. This is demonstrated by the following example from the HowTo:
// Get a collection of picked objects under the mouse location. // The collection is sorted with the closest object at index zero. List<PickResult> collection = getScene().pick(mouseX, mouseY); if (collection.size() != 0) { List<Object> objects = collection.get(0).getObjects(); Object compositeObj = objects.get(0); // Was a model in our composite picked? if (compositeObj == m_models) { ModelPrimitive model = (ModelPrimitive) objects.get(1); CentralBody centralBody = CentralBodiesFacet.getFromContext().getEarth(); BoundingSphere sphere = model.getBoundingSphere(); double azimuthAngle = Trig.degreesToRadians(-90.0); double elevationAngle = Trig.degreesToRadians(-30.0); PointFixedOffset boundingSphereCenter = new PointFixedOffset(centralBody.getFixedFrame(), sphere.getCenter()); Axes boundingSphereAxes = new AxesEastNorthUp(centralBody, boundingSphereCenter); Cartesian offset = new Cartesian(new AzimuthElevationRange(azimuthAngle, elevationAngle, getScene().getCamera().getDistancePerRadius() * sphere.getRadius())); getScene().getCamera().viewOffset(boundingSphereAxes, boundingSphereCenter, offset); getScene().render(); } }
This code snippet is called in response to a double click event in the 3D control. A pick is executed using the current position of the mouse cursor. If the collection returned is empty, nothing was picked. Otherwise, since we only want to zoom to a model if it is in our composite, the topmost picked object is checked to see if it is the instance we expect. If the topmost picked object was something else, perhaps because only the central body was picked, the zoom will not occur.
In roll-over picking, pick is called in response to the mouse moving across the 3D control. The results of the pick may be used to display the mouse cursor's cartographic position over a globe or to highlight the primitive under the cursor, as shown in the following example from the HowTo:
// Get a collection of picked objects under the mouse location. // The collection is sorted with the closest object at index zero. List<PickResult> collection = getScene().pick(mouseX, mouseY); if (collection.size() != 0) { List<Object> objects = collection.get(0).getObjects(); Object compositeObj = objects.get(0); // Was a model in our composite picked? if (compositeObj == models) { ModelPrimitive model = (ModelPrimitive) objects.get(1); model.setColor(Color.CYAN); if (model != m_selectedModel) { // Unselect previous model if (m_selectedModel != null) { m_selectedModel.setColor(Color.RED); } m_selectedModel = model; getScene().render(); } return; } } // Unselect previous model if (m_selectedModel != null) { m_selectedModel.setColor(Color.RED); m_selectedModel = null; getScene().render(); }
m_selectedModel is a reference to the currently highlighted model, that is, the model currently under the cursor. Initially, it is null. pick is called with the mouse cursor's position in response to a mouse move event.
The model under the cursor is highlighted by setting its color to cyan. The previously selected model is set to red. Note that a model may be unhighlighted because either another model is now under the cursor or no model in the composite in under the mouse cursor. Since redrawing the scene every time the mouse moves may affect performance, the scene is only redrawn (by calling render) if a model is highlighted or unhighlighted.
Drill picking is used to "drill down" to objects underneath the top object. For example, the first time a user double clicks, the top object is picked. If the mouse doesn't move and the scene doesn't change (e.g. primitives aren't added or removed), the second time the user double clicks, the object underneath the top object is picked, and so on.
This is implemented using the pick method. The collection from the first call is saved and the closest object (the object at index 0) is the picked object. The next time a pick occurs (e.g. next double click), if the mouse position and scene did not change, the picked object is now at index 1 in the collection. Typically, the index will roll back to 0 once it goes all the way through the collection.
Rectangular, or rubber band picking, is used to pick objects within a rectangular region. For example, when a 100 by 100 rectangle is picked, all the objects within that rectangle will be returned.
Rectangular picking is provided by the pickRectangular method. The following code sample executes a rectangular pick, and then iterates through all of the objects that were contained within that rectangle:
List<PickResult> collection = getScene().pickRectangular(mouseX - 50, mouseY + 50, mouseX + 50, mouseY - 50); for (PickResult pickResult : collection) { List<Object> objects = pickResult.getObjects(); Object compositeObj = objects.get(0); if (compositeObj == modelsComposite) { // A model in our composite was among the items that were picked ModelPrimitive model = (ModelPrimitive) objects.get(1); } }