diff --git a/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java b/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java index 8b7100fb6..59e6ecbd8 100644 --- a/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java +++ b/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java @@ -30,6 +30,8 @@ import gov.nasa.worldwind.BasicWorldWindowController; import gov.nasa.worldwind.PickedObject; import gov.nasa.worldwind.PickedObjectList; +import gov.nasa.worldwind.WorldWind; +import gov.nasa.worldwind.geom.Offset; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.layer.RenderableLayer; import gov.nasa.worldwind.render.ImageSource; @@ -140,9 +142,11 @@ public PlaceLevelOfDetailSelector(Resources resources, Place place) { * @param rc * @param placemark The placemark needing a level of detail selection * @param cameraDistance The distance from the placemark to the camera (meters) + * + * @return if placemark should display or skip its rendering */ @Override - public void selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance) { + public boolean selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance) { boolean highlighted = placemark.isHighlighted(); boolean highlightChanged = this.lastHighlightState != highlighted; @@ -211,7 +215,13 @@ public void selectLevelOfDetail(RenderContext rc, Placemark placemark, double ca this.lastHighlightState = highlighted; // Update the placemark's attributes bundle - placemark.setAttributes(this.attributes); + if (this.attributes != null) { + this.attributes.setDrawLabel(highlighted || cameraDistance <= LEVEL_1_DISTANCE); + placemark.setAttributes(this.attributes); + return true; // Placemark visible + } else { + return false; // Placemark invisible + } } protected static PlacemarkAttributes getPlacemarkAttributes(Resources resources, Place place) { @@ -273,6 +283,7 @@ protected static PlacemarkAttributes createPlacemarkAttributes(Resources resourc //IconBitmapFactory factory = new IconBitmapFactory(resources, resourceId); //placemarkAttributes.setImageSource(ImageSource.fromBitmapFactory(factory)).setImageScale(scale); placemarkAttributes.setImageSource(ImageSource.fromResource(resourceId)).setImageScale(scale).setMinimumImageScale(0.5); + placemarkAttributes.getLabelAttributes().setTextOffset(new Offset(WorldWind.OFFSET_PIXELS, -24, WorldWind.OFFSET_FRACTION, 0.0)); return placemarkAttributes; } } @@ -560,6 +571,8 @@ private void createPlaceIcons() { placemark.setLevelOfDetailSelector(new PlaceLevelOfDetailSelector(getResources(), place)); placemark.setEyeDistanceScaling(true); placemark.setEyeDistanceScalingThreshold(PlaceLevelOfDetailSelector.LEVEL_1_DISTANCE); + placemark.setLabel(place.name); + placemark.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); // On a background thread, we can add Placemarks to a RenderableLayer that is // NOT attached to the WorldWindow. If the layer was attached to the WorldWindow diff --git a/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksMilStd2525Activity.java b/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksMilStd2525Activity.java index 7476bc4db..03085c066 100644 --- a/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksMilStd2525Activity.java +++ b/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksMilStd2525Activity.java @@ -86,6 +86,7 @@ protected void onPostExecute(Void notUsed) { Placemark drone = new Placemark( Position.fromDegrees(32.4520, 63.44553, 3000), MilStd2525.getPlacemarkAttributes("SFAPMFQM--GIUSA", modifiers, null)); + drone.getAttributes().setDrawLeader(true); symbolLayer.addRenderable(drone); @@ -96,6 +97,7 @@ protected void onPostExecute(Void notUsed) { Placemark launcher = new Placemark( Position.fromDegrees(32.4014, 63.3894, 0), MilStd2525.getPlacemarkAttributes("SHGXUCFRMS----G", modifiers, null)); + launcher.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); symbolLayer.addRenderable(launcher); @@ -109,6 +111,7 @@ protected void onPostExecute(Void notUsed) { Placemark machineGun = new Placemark( Position.fromDegrees(32.3902, 63.4161, 0), MilStd2525.getPlacemarkAttributes("SFGPEWRH--MTUSG", modifiers, null)); + machineGun.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); symbolLayer.addRenderable(machineGun); diff --git a/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525.java b/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525.java index daf23c2df..b5369f1d6 100644 --- a/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525.java +++ b/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525.java @@ -10,7 +10,6 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; -import android.graphics.Rect; import android.graphics.Typeface; import android.os.Handler; import android.os.Looper; @@ -266,10 +265,11 @@ public Bitmap createBitmap() { // placement as the offset may change depending on the level of detail, for instance, the absence or // presence of text modifiers. Point centerPoint = imageInfo.getCenterPoint(); // The center of the core symbol - Rect bounds = imageInfo.getImageBounds(); // The extents of the image, including text modifiers + //Rect bounds = imageInfo.getImageBounds(); // The extents of the image, including text modifiers this.placemarkOffset = new Offset( WorldWind.OFFSET_PIXELS, centerPoint.x, // x offset - WorldWind.OFFSET_PIXELS, bounds.height() - centerPoint.y); // y offset converted to lower-left origin + // Use billboarding or lollipopping to prevent icon clipping by terrain as described in MIL-STD-2525C APPENDIX F.5.1.1.2 + WorldWind.OFFSET_PIXELS, 0/*bounds.height() - centerPoint.y*/); // y offset converted to lower-left origin // Apply the placemark offset to the attributes on the main thread. This is necessary to synchronize write // access to placemarkAttributes from the thread that invokes this BitmapFactory and read access from the diff --git a/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525LevelOfDetailSelector.java b/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525LevelOfDetailSelector.java index e94a9d799..da100130a 100644 --- a/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525LevelOfDetailSelector.java +++ b/worldwind-examples/src/main/java/gov/nasa/worldwindx/milstd2525/MilStd2525LevelOfDetailSelector.java @@ -59,9 +59,11 @@ public static void setNearThreshold(double nearThreshold) { * @param rc The current render contents * @param placemark The placemark needing a level of detail selection * @param cameraDistance The distance from the placemark to the camera (meters) + * + * @return if placemark should display or skip its rendering */ @Override - public void selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance) { + public boolean selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance) { if (!(placemark instanceof MilStd2525Placemark)) { throw new IllegalArgumentException( Logger.logMessage(Logger.ERROR, "MilStd2525LevelOfDetailSelector", "selectLevelOfDetail", @@ -108,5 +110,7 @@ public void selectLevelOfDetail(RenderContext rc, Placemark placemark, double ca if (this.placemarkAttributes != null) { milStdPlacemark.setAttributes(this.placemarkAttributes); } + + return true; // Placemark is always visible } } diff --git a/worldwind/src/main/java/gov/nasa/worldwind/shape/Label.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/Label.java index cf6f4ad7a..89308f6b8 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Label.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Label.java @@ -445,10 +445,11 @@ protected void makeDrawable(RenderContext rc) { // origin at the text's bottom-left corner and axes that extend up and to the right from the origin point. int w = texture.getWidth(); int h = texture.getHeight(); + double s = this.activeAttributes.scale; this.activeAttributes.textOffset.offsetForSize(w, h, renderData.offset); renderData.unitSquareTransform.setTranslation( - renderData.screenPlacePoint.x - renderData.offset.x, - renderData.screenPlacePoint.y - renderData.offset.y, + renderData.screenPlacePoint.x - renderData.offset.x * s, + renderData.screenPlacePoint.y - renderData.offset.y * s, renderData.screenPlacePoint.z); // Apply the label's rotation according to its rotation value and orientation mode. The rotation is applied @@ -462,7 +463,7 @@ protected void makeDrawable(RenderContext rc) { } // Apply the label's translation and scale according to its text size. - renderData.unitSquareTransform.multiplyByScale(w, h, 1); + renderData.unitSquareTransform.multiplyByScale(w * s, h * s, 1); WWMath.boundingRectForUnitSquare(renderData.unitSquareTransform, renderData.screenBounds); if (!rc.frustum.intersectsViewport(renderData.screenBounds)) { diff --git a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java index b0621e23f..6aa9aa2b7 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -49,8 +49,10 @@ public interface LevelOfDetailSelector { * @param rc The current render context * @param placemark The placemark needing a level of detail selection * @param cameraDistance The distance from the placemark to the camera (meters) + * + * @return if placemark should display or skip its rendering */ - void selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance); + boolean selectLevelOfDetail(RenderContext rc, Placemark placemark, double cameraDistance); } /** @@ -60,19 +62,23 @@ public interface LevelOfDetailSelector { */ protected static final double DEFAULT_EYE_DISTANCE_SCALING_THRESHOLD = 1e6; - protected static final double DEFAULT_DEPTH_OFFSET = -0.1; + protected static final double DEFAULT_DEPTH_OFFSET = -0.03; + + private static final Vec3 placePoint = new Vec3(); - private static Vec3 placePoint = new Vec3(); + private static final Vec3 screenPlacePoint = new Vec3(); - private static Vec3 screenPlacePoint = new Vec3(); + private static final Vec3 groundPoint = new Vec3(); - private static Vec3 groundPoint = new Vec3(); + private static final Vec2 offset = new Vec2(); - private static Vec2 offset = new Vec2(); + private static final Matrix4 imageTransform = new Matrix4(); - private static Matrix4 unitSquareTransform = new Matrix4(); + private static final Matrix4 labelTransform = new Matrix4(); - private static Viewport screenBounds = new Viewport(); + private static final Viewport imageBounds = new Viewport(); + + private static final Viewport labelBounds = new Viewport(); /** * The placemark's geographic position. @@ -105,6 +111,11 @@ public interface LevelOfDetailSelector { */ protected Texture activeTexture; + /** + * The texture associated with the label attributes, or null if the attributes specify no image. + */ + protected Texture labelTexture; + /** * The picked object ID associated with the placemark during the current render pass. */ @@ -115,8 +126,7 @@ public interface LevelOfDetailSelector { /** * The label text to draw near the placemark. */ - // TODO: implement label property -// protected String label; + protected String label; /** * Determines whether the normal or highlighted attibutes should be used. @@ -210,7 +220,7 @@ public Placemark(Position position, PlacemarkAttributes attributes, String name) this.setPosition(position); this.setAltitudeMode(WorldWind.ABSOLUTE); this.setDisplayName(name == null || name.isEmpty() ? "Placemark" : name); - // this.setLabel(name); // TODO: call setLabel(name) + // this.setLabel(name); // Do not use display name as label by default this.attributes = attributes; this.eyeDistanceScaling = false; this.eyeDistanceScalingThreshold = DEFAULT_EYE_DISTANCE_SCALING_THRESHOLD; @@ -257,10 +267,9 @@ public static Placemark createWithImage(Position position, ImageSource imageSour * * @return A new Placemark with a PlacemarkAttributes bundle containing TextAttributes. */ - // TODO: implement createWithImageAndLabel factory method -// public static Placemark createWithImageAndLabel(Position position, ImageSource imageSource, String label) { -// return new Placemark(position, PlacemarkAttributes.createWithImage(imageSource), label); -// } + public static Placemark createWithImageAndLabel(Position position, ImageSource imageSource, String label) { + return new Placemark(position, PlacemarkAttributes.createWithImage(imageSource), label); + } /** * Gets this placemark's geographic position. @@ -385,15 +394,15 @@ public Placemark setLevelOfDetailSelector(LevelOfDetailSelector levelOfDetailSel this.levelOfDetailSelector = levelOfDetailSelector; return this; } - /** + + /* * Gets the text used to label this placemark on the globe. * * @return The text used to label a placemark on the globe when labels are enabled */ - // TODO: implement getLabel() -// public String getLabel() { -// return label; -// } + public String getLabel() { + return label; + } /** * Sets the text used for this placemark's label on the globe. @@ -402,11 +411,10 @@ public Placemark setLevelOfDetailSelector(LevelOfDetailSelector levelOfDetailSel * * @return This placemark */ - // TODO: implement setLabel() -// public Placemark setLabel(String label) { -// this.label = label; -// return this; -// } + public Placemark setLabel(String label) { + this.label = label; + return this; + } /** * Indicates whether this placemark's size is reduced at higher eye distances. If true, this placemark's size is @@ -684,8 +692,9 @@ protected void doRender(RenderContext rc) { // Compute a screen depth offset appropriate for the current viewing parameters. double depthOffset = 0; - if (this.cameraDistance < rc.horizonDistance) { - depthOffset = DEFAULT_DEPTH_OFFSET; + double absTilt = Math.abs( rc.camera.tilt ); + if (this.cameraDistance < rc.horizonDistance && absTilt <= 90) { + depthOffset = ( 1 - absTilt / 90 ) * DEFAULT_DEPTH_OFFSET; } // Project the placemark's model point to screen coordinates, using the screen depth offset to push the screen @@ -695,8 +704,9 @@ protected void doRender(RenderContext rc) { } // Allow the placemark to adjust the level of detail based on distance to the camera - if (this.levelOfDetailSelector != null) { - this.levelOfDetailSelector.selectLevelOfDetail(rc, this, this.cameraDistance); + if (this.levelOfDetailSelector != null + && !this.levelOfDetailSelector.selectLevelOfDetail(rc, this, this.cameraDistance)) { + return; // skip rendering } // Determine the attributes to use for the current render pass. @@ -748,17 +758,118 @@ protected void doRender(RenderContext rc) { // Compute the placemark icon's active texture. this.determineActiveTexture(rc); + // Compute an camera-position proximity scaling factor, so that distant placemarks can be scaled smaller than + // nearer placemarks. + double visibilityScale = this.isEyeDistanceScaling() ? + Math.max(this.activeAttributes.minimumImageScale, Math.min(1, this.getEyeDistanceScalingThreshold() / this.cameraDistance)) : 1; + + // Initialize the unit square transform to the identity matrix. + imageTransform.setToIdentity(); + + // Apply the icon's translation and scale according to the image size, image offset and image scale. The image + // offset is defined with its origin at the image's bottom-left corner and axes that extend up and to the right + // from the origin point. When the placemark has no active texture the image scale defines the image size and no + // other scaling is applied. + double offsetX, offsetY, scaleX, scaleY; + if (this.activeTexture != null) { + int w = this.activeTexture.getWidth(); + int h = this.activeTexture.getHeight(); + double s = this.activeAttributes.imageScale * visibilityScale; + this.activeAttributes.imageOffset.offsetForSize(w, h, offset); + offsetX = offset.x * s; + offsetY = offset.y * s; + scaleX = w * s; + scaleY = h * s; + } else { + // This branch serves both non-textured attributes and also textures that haven't been loaded yet. + // We set the size for non-loaded textures to the typical size of a contemporary "small" icon (24px) + double size = this.activeAttributes.imageSource != null ? 24 : this.activeAttributes.imageScale; + size *= visibilityScale; + this.activeAttributes.imageOffset.offsetForSize(size, size, offset); + offsetX = offset.x; + offsetY = offset.y; + scaleX = scaleY = size; + } + + // Position image on screen + imageTransform.multiplyByTranslation( + screenPlacePoint.x, + screenPlacePoint.y, + screenPlacePoint.z); + + // Divide Z by 2^24 to prevent texture clipping when tilting (where 24 is depth buffer bit size). + // Doing so will limit depth range to (diagonal length)/2^24 and make its value within 0..1 range. + imageTransform.multiplyByScale(1, 1, 1d / (1 << 24) ); + + // Perform the tilt so that the image tilts back from its base into the view volume + if (this.imageTilt != 0) { + double actualTilt = this.imageTiltReference == WorldWind.RELATIVE_TO_GLOBE ? + rc.camera.tilt + this.imageTilt : this.imageTilt; + imageTransform.multiplyByRotation(-1, 0, 0, actualTilt); + } + + // Perform image rotation + if (this.imageRotation != 0) { + double actualRotation = this.imageRotationReference == WorldWind.RELATIVE_TO_GLOBE ? + rc.camera.heading - this.imageRotation : -this.imageRotation; + imageTransform.multiplyByRotation(0, 0, 1, actualRotation); + } + + // Apply pivot translation + imageTransform.multiplyByTranslation(-offsetX, -offsetY, 0); + + // Apply scale + imageTransform.multiplyByScale(scaleX, scaleY, 1); + // If the placemark's icon is visible, enqueue a drawable icon for processing on the OpenGL thread. - WWMath.boundingRectForUnitSquare(unitSquareTransform, screenBounds); - if (rc.frustum.intersectsViewport(screenBounds)) { + WWMath.boundingRectForUnitSquare(imageTransform, imageBounds); + if (rc.frustum.intersectsViewport(imageBounds)) { Pool pool = rc.getDrawablePool(DrawableScreenTexture.class); DrawableScreenTexture drawable = DrawableScreenTexture.obtain(pool); this.prepareDrawableIcon(rc, drawable); rc.offerShapeDrawable(drawable, this.cameraDistance); } + // If there's a label, perform these same operations for the label texture. + if (this.mustDrawLabel(rc)) { + // Render the label's texture when the label's position is in the frustum. If the label's position is outside + // the frustum we don't do anything. This ensures that label textures are rendered only as necessary. + this.labelTexture = rc.getText(this.label, this.activeAttributes.labelAttributes); + if (this.labelTexture == null && rc.frustum.containsPoint(placePoint)) { + this.labelTexture = rc.renderText(this.label, this.activeAttributes.labelAttributes); + } + + if (this.labelTexture != null) { + // Compute an camera-position proximity scaling factor, so that distant placemarks can be scaled smaller than + // nearer placemarks. + visibilityScale = this.isEyeDistanceScaling() ? + Math.max(this.activeAttributes.minimumImageScale, Math.min(1, this.getEyeDistanceScalingLabelThreshold() / this.cameraDistance)) : 1; + + int w = this.labelTexture.getWidth(); + int h = this.labelTexture.getHeight(); + double s = this.activeAttributes.labelAttributes.scale * visibilityScale; + this.activeAttributes.labelAttributes.textOffset.offsetForSize(w, h, offset); + + labelTransform.setTranslation( + screenPlacePoint.x - offset.x * s, + screenPlacePoint.y - offset.y * s, + screenPlacePoint.z); + + labelTransform.setScale(w * s, h * s, 1); + + WWMath.boundingRectForUnitSquare(labelTransform, labelBounds); + if (rc.frustum.intersectsViewport(labelBounds)) { + Pool pool = rc.getDrawablePool(DrawableScreenTexture.class); + DrawableScreenTexture drawable = DrawableScreenTexture.obtain(pool); + this.prepareDrawableLabel(rc, drawable); + rc.offerShapeDrawable(drawable, this.cameraDistance); + } + } + } + // Release references to objects stored in the render resource cache. this.activeTexture = null; + this.labelTexture = null; // Enqueue a picked object that associates the placemark's icon and leader with its picked object ID. if (rc.pickMode && rc.drawableCount() != drawableCount) { @@ -785,7 +896,6 @@ protected void determineActiveAttributes(RenderContext rc) { * @param rc the current render context */ protected void determineActiveTexture(RenderContext rc) { - // TODO: Refactor! if (this.activeAttributes.imageSource != null) { // Earlier in doRender(), an attempt was made to 'get' the activeTexture from the cache. // If was not found in the cache we need to retrieve a texture from the image source. @@ -795,61 +905,6 @@ protected void determineActiveTexture(RenderContext rc) { } else { this.activeTexture = null; // there is no imageSource; draw a simple colored square } - - // Compute an camera-position proximity scaling factor, so that distant placemarks can be scaled smaller than - // nearer placemarks. - double visibilityScale = this.isEyeDistanceScaling() ? - Math.max(this.activeAttributes.minimumImageScale, Math.min(1, this.getEyeDistanceScalingThreshold() / this.cameraDistance)) : 1; - - // Initialize the unit square transform to the identity matrix. - unitSquareTransform.setToIdentity(); - - // Apply the icon's translation and scale according to the image size, image offset and image scale. The image - // offset is defined with its origin at the image's bottom-left corner and axes that extend up and to the right - // from the origin point. When the placemark has no active texture the image scale defines the image size and no - // other scaling is applied. - if (this.activeTexture != null) { - int w = this.activeTexture.getWidth(); - int h = this.activeTexture.getHeight(); - double s = this.activeAttributes.imageScale * visibilityScale; - this.activeAttributes.imageOffset.offsetForSize(w, h, offset); - - unitSquareTransform.multiplyByTranslation( - screenPlacePoint.x - offset.x * s, - screenPlacePoint.y - offset.y * s, - screenPlacePoint.z); - - unitSquareTransform.multiplyByScale(w * s, h * s, 1); - } else { - // This branch serves both non-textured attributes and also textures that haven't been loaded yet. - // We set the size for non-loaded textures to the typical size of a contemporary "small" icon (24px) - double size = this.activeAttributes.imageSource != null ? 24 : this.activeAttributes.imageScale; - size *= visibilityScale; - this.activeAttributes.imageOffset.offsetForSize(size, size, offset); - - unitSquareTransform.multiplyByTranslation( - screenPlacePoint.x - offset.x, - screenPlacePoint.y - offset.y, - screenPlacePoint.z); - - unitSquareTransform.multiplyByScale(size, size, 1); - } - - // ... perform image rotation - if (this.imageRotation != 0) { - double rotation = this.imageRotationReference == WorldWind.RELATIVE_TO_GLOBE ? - rc.camera.heading - this.imageRotation : -this.imageRotation; - unitSquareTransform.multiplyByTranslation(0.5, 0.5, 0); - unitSquareTransform.multiplyByRotation(0, 0, 1, rotation); - unitSquareTransform.multiplyByTranslation(-0.5, -0.5, 0); - } - - // ... and perform the tilt so that the image tilts back from its base into the view volume. - if (this.imageTilt != 0) { - double tilt = this.imageTiltReference == WorldWind.RELATIVE_TO_GLOBE ? - rc.camera.tilt + this.imageTilt : this.imageTilt; - unitSquareTransform.multiplyByRotation(-1, 0, 0, tilt); - } } /** @@ -867,7 +922,7 @@ protected void prepareDrawableIcon(RenderContext rc, DrawableScreenTexture drawa } // Use the plaemark's unit square transform matrix. - drawable.unitSquareTransform.set(unitSquareTransform); + drawable.unitSquareTransform.set(imageTransform); // Configure the drawable according to the placemark's active attributes. Use a color appropriate for the pick // mode. When picking use a unique color associated with the picked object ID. Use the texture associated with @@ -878,6 +933,37 @@ protected void prepareDrawableIcon(RenderContext rc, DrawableScreenTexture drawa drawable.enableDepthTest = this.activeAttributes.depthTest; } + /** + * Prepares this placemark's label for processing in a subsequent drawing pass. Implementations must be + * careful not to leak resources from Placemark into the Drawable. + * + * @param rc the current render context + * @param drawable the Drawable to be prepared + */ + protected void prepareDrawableLabel(RenderContext rc, DrawableScreenTexture drawable) { + // Use the basic GLSL program to draw the placemark's label. + drawable.program = (BasicShaderProgram) rc.getShaderProgram(BasicShaderProgram.KEY); + if (drawable.program == null) { + drawable.program = (BasicShaderProgram) rc.putShaderProgram(BasicShaderProgram.KEY, new BasicShaderProgram(rc.resources)); + } + + // Use the label's unit square transform matrix. + drawable.unitSquareTransform.set(labelTransform); + + // Configure the drawable according to the active label attributes. Use a color appropriate for the pick mode. When + // picking use a unique color associated with the picked object ID. Use the texture associated with the active + // attributes' text image and its associated tex coord transform. The text texture includes the appropriate + // color for drawing, specifying white for normal drawing ensures the color multiplication in the shader results + // in the texture's color. + if (rc.pickMode) { + drawable.color.set(this.pickColor); + } else { + drawable.color.set(1, 1, 1, 1); + } + drawable.texture = this.labelTexture; + drawable.enableDepthTest = this.activeAttributes.labelAttributes.isEnableDepthTest(); + } + /** * Prepares this placemark's leader for drawing in a subsequent drawing pass. Implementations must be careful not to * leak resources from Placemark into the Drawable. @@ -916,13 +1002,10 @@ protected void prepareDrawableLeader(RenderContext rc, DrawableLines drawable) { * * @return True if there is a valid label and label attributes. */ - protected boolean mustDrawLabel(RenderContext rc) { - return false; - // TODO: implement mustDrawLabel() -// return this.label != null -// && !this.label.isEmpty() -// && this.activeAttributes.labelAttributes != null; + return this.activeAttributes.drawLabel + && this.label != null && !this.label.isEmpty() + && this.activeAttributes.labelAttributes != null; } /** diff --git a/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java index 0ee42c2c3..cfc6e69a2 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java @@ -25,6 +25,8 @@ public class PlacemarkAttributes { protected double minimumImageScale; + protected boolean drawLabel; + protected boolean drawLeader; protected boolean depthTest; @@ -43,6 +45,7 @@ public PlacemarkAttributes() { this.imageOffset = Offset.center(); this.imageScale = 1; this.minimumImageScale = 0; + this.drawLabel = true; this.drawLeader = false; this.depthTest = true; this.labelAttributes = new TextAttributes(); @@ -68,6 +71,7 @@ public PlacemarkAttributes(PlacemarkAttributes attributes) { this.imageOffset = new Offset(attributes.imageOffset); this.imageScale = attributes.imageScale; this.minimumImageScale = attributes.minimumImageScale; + this.drawLabel = attributes.drawLabel; this.drawLeader = attributes.drawLeader; this.depthTest = attributes.depthTest; this.labelAttributes = attributes.labelAttributes != null ? new TextAttributes(attributes.labelAttributes) : null; @@ -85,6 +89,7 @@ public PlacemarkAttributes set(PlacemarkAttributes attributes) { this.imageOffset.set(attributes.imageOffset); this.imageScale = attributes.imageScale; this.minimumImageScale = attributes.minimumImageScale; + this.drawLabel = attributes.drawLabel; this.drawLeader = attributes.drawLeader; this.depthTest = attributes.depthTest; @@ -135,6 +140,7 @@ public boolean equals(Object o) { && this.imageOffset.equals(that.imageOffset) && this.imageScale == that.imageScale && this.minimumImageScale == that.minimumImageScale + && this.drawLabel == that.drawLabel && this.drawLeader == that.drawLeader && this.depthTest == that.depthTest && ((this.labelAttributes == null) ? (that.labelAttributes == null) : this.labelAttributes.equals(that.labelAttributes)) @@ -152,6 +158,7 @@ public int hashCode() { result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(this.minimumImageScale); result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + (this.drawLabel ? 1 : 0); result = 31 * result + (this.drawLeader ? 1 : 0); result = 31 * result + (this.depthTest ? 1 : 0); result = 31 * result + (this.labelAttributes != null ? this.labelAttributes.hashCode() : 0); @@ -263,6 +270,23 @@ public PlacemarkAttributes setMinimumImageScale(double minimumImageScale) { return this; } + /** + * Returns whether to draw a placemark's label. + */ + public boolean isDrawLabel() { + return drawLabel; + } + + /** + * Sets whether to draw a placemark's label. + * + * @param drawLabel The new draw label setting. + */ + public PlacemarkAttributes setDrawLabel(boolean drawLabel) { + this.drawLabel = drawLabel; + return this; + } + /** * Returns whether to draw a line from the placemark's geographic position to the ground. */ @@ -306,7 +330,7 @@ public PlacemarkAttributes setDepthTest(boolean depthTest) { /** * Returns the attributes to apply to the placemark's label, if any. If null, the placemark's label is not drawn. */ - public Object getLabelAttributes() { + public TextAttributes getLabelAttributes() { return labelAttributes; } diff --git a/worldwind/src/main/java/gov/nasa/worldwind/shape/TextAttributes.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/TextAttributes.java index 4d291b861..5e326a6fd 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/TextAttributes.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/TextAttributes.java @@ -32,6 +32,8 @@ public class TextAttributes { protected float outlineWidth; + protected double scale; + public TextAttributes() { this.textColor = new Color(1, 1, 1, 1); this.textOffset = Offset.bottomCenter(); @@ -41,6 +43,7 @@ public TextAttributes() { this.outlineColor = new Color(0, 0, 0, 1); this.enableDepthTest = true; this.outlineWidth = 3; + this.scale = 1; } public TextAttributes(TextAttributes attributes) { @@ -57,6 +60,7 @@ public TextAttributes(TextAttributes attributes) { this.enableOutline = attributes.enableOutline; this.enableDepthTest = attributes.enableDepthTest; this.outlineWidth = attributes.outlineWidth; + this.scale = attributes.scale; } public TextAttributes set(TextAttributes attributes) { @@ -73,6 +77,7 @@ public TextAttributes set(TextAttributes attributes) { this.outlineColor.set(attributes.outlineColor); this.enableDepthTest = attributes.enableDepthTest; this.outlineWidth = attributes.outlineWidth; + this.scale = attributes.scale; return this; } @@ -94,7 +99,8 @@ public boolean equals(Object o) { && this.enableOutline == that.enableOutline && this.outlineColor.equals(that.outlineColor) && this.enableDepthTest == that.enableDepthTest - && this.outlineWidth == that.outlineWidth; + && this.outlineWidth == that.outlineWidth + && this.scale == that.scale; } @Override @@ -107,6 +113,8 @@ public int hashCode() { result = 31 * result + this.outlineColor.hashCode(); result = 31 * result + (this.enableDepthTest ? 1 : 0); result = 31 * result + (this.outlineWidth != +0.0f ? Float.floatToIntBits(this.outlineWidth) : 0); + long temp = Double.doubleToLongBits(this.scale); + result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } @@ -196,4 +204,13 @@ public TextAttributes setOutlineWidth(float lineWidth) { this.outlineWidth = lineWidth; return this; } + + public double getScale() { + return this.scale; + } + + public TextAttributes setScale(double scale) { + this.scale = scale; + return this; + } }