From 6c4342caa5efc592f4ecd3cca3e9a68ac02edb6f Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sun, 27 Mar 2022 16:05:18 +0300 Subject: [PATCH 1/8] Fix Placemark rotation and tilt (https://github.com/WorldWindEarth/WorldWindAndroid/issues/31): 1) Change order of unitSquareTransform matrix operations to fix image stretching on rotation and apply correct pivot point. 2) Normalize unitSquareTransform matrix Z-range to prevent texture clipping on tilting. --- .../gov/nasa/worldwind/shape/Placemark.java | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) 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..967214062 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -808,48 +808,56 @@ protected void determineActiveTexture(RenderContext rc) { // 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); - - unitSquareTransform.multiplyByTranslation( - screenPlacePoint.x - offset.x * s, - screenPlacePoint.y - offset.y * s, - screenPlacePoint.z); - - unitSquareTransform.multiplyByScale(w * s, h * s, 1); + 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; + } - unitSquareTransform.multiplyByTranslation( - screenPlacePoint.x - offset.x, - screenPlacePoint.y - offset.y, + // Position image on screen + unitSquareTransform.multiplyByTranslation( + screenPlacePoint.x, + screenPlacePoint.y, screenPlacePoint.z); - unitSquareTransform.multiplyByScale(size, size, 1); + // 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. + unitSquareTransform.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 tilt = this.imageTiltReference == WorldWind.RELATIVE_TO_GLOBE ? + rc.camera.tilt + this.imageTilt : this.imageTilt; + unitSquareTransform.multiplyByRotation(-1, 0, 0, tilt); } - // ... perform image rotation + // 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); + rc.camera.heading - this.imageRotation : -this.imageRotation; 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); - } + // Apply pivot translation + unitSquareTransform.multiplyByTranslation(-offsetX, -offsetY, 0); + + // Apply scale + unitSquareTransform.multiplyByScale(scaleX, scaleY, 1); } /** From 49080c015b062ccb7434a1b6fa7dd03668350630 Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sun, 27 Mar 2022 16:16:06 +0300 Subject: [PATCH 2/8] Change Placemark default depth offset from -0.1 to -0.015 to prevent texture from protruding through the terrain. Fix placemarks altitude mode in PlacemarksMilStd2525Activity to be correctly rendered on the top of the surface. Use billboarding approach of placemarks rendering to prevent clipping by terrain as described in MIL-STD-2525C APPENDIX F.5.1.1.2. --- .../gov/nasa/worldwindx/PlacemarksMilStd2525Activity.java | 3 +++ .../java/gov/nasa/worldwindx/milstd2525/MilStd2525.java | 3 ++- .../src/main/java/gov/nasa/worldwind/shape/Placemark.java | 7 ++++--- 3 files changed, 9 insertions(+), 4 deletions(-) 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..c1b960cf7 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 @@ -269,7 +269,8 @@ public Bitmap createBitmap() { 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/src/main/java/gov/nasa/worldwind/shape/Placemark.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java index 967214062..e531ca8ec 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -60,7 +60,7 @@ 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 Vec3 placePoint = new Vec3(); @@ -684,8 +684,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 From 9377485d43ceef83578cb5242648b6c8031c655f Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Tue, 29 Mar 2022 22:22:42 +0300 Subject: [PATCH 3/8] Implement missed Placemark label functionality. --- .../worldwindx/PlacemarksDemoActivity.java | 5 + .../nasa/worldwind/render/RenderContext.java | 2 +- .../gov/nasa/worldwind/shape/Placemark.java | 239 +++++++++++------- .../worldwind/shape/PlacemarkAttributes.java | 2 +- 4 files changed, 159 insertions(+), 89 deletions(-) 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..bd75409d3 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; @@ -273,6 +275,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 +563,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/src/main/java/gov/nasa/worldwind/render/RenderContext.java b/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java index 6c29df0c6..2fcf20b9f 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java @@ -432,7 +432,7 @@ public Texture getText(String text, TextAttributes attributes) { } public Texture renderText(String text, TextAttributes attributes) { - TextCacheKey key = new TextCacheKey().set(text, attributes); + TextCacheKey key = this.scratchTextCacheKey.set(text, attributes); Texture texture = null; if (text != null && attributes != null) { 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 e531ca8ec..74f91d75f 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -62,17 +62,21 @@ public interface LevelOfDetailSelector { protected static final double DEFAULT_DEPTH_OFFSET = -0.03; - private static Vec3 placePoint = new Vec3(); + private static final Vec3 placePoint = new Vec3(); - private static Vec3 screenPlacePoint = new Vec3(); + private static final Vec3 screenPlacePoint = new Vec3(); - private static Vec3 groundPoint = new Vec3(); + private static final Vec3 groundPoint = new Vec3(); - private static Vec2 offset = new Vec2(); + private static final Vec2 offset = new Vec2(); - private static Matrix4 unitSquareTransform = new Matrix4(); + private static final Matrix4 imageTransform = new Matrix4(); - private static Viewport screenBounds = new Viewport(); + private static final Matrix4 labelTransform = new Matrix4(); + + private static final Viewport imageBounds = new Viewport(); + + private static final Viewport labelBounds = new Viewport(); /** * The placemark's geographic position. @@ -105,6 +109,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 +124,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 +218,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 +265,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 +392,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 +409,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 @@ -749,61 +755,13 @@ protected void doRender(RenderContext rc) { // Compute the placemark icon's active texture. this.determineActiveTexture(rc); - // 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)) { - Pool pool = rc.getDrawablePool(DrawableScreenTexture.class); - DrawableScreenTexture drawable = DrawableScreenTexture.obtain(pool); - this.prepareDrawableIcon(rc, drawable); - rc.offerShapeDrawable(drawable, this.cameraDistance); - } - - // Release references to objects stored in the render resource cache. - this.activeTexture = null; - - // Enqueue a picked object that associates the placemark's icon and leader with its picked object ID. - if (rc.pickMode && rc.drawableCount() != drawableCount) { - rc.offerPickedObject(PickedObject.fromRenderable(this.pickedObjectId, this, rc.currentLayer)); - } - } - - /** - * Determines the placemark attributes to use for the current render pass. - * - * @param rc the current render context - */ - protected void determineActiveAttributes(RenderContext rc) { - if (this.highlighted && this.highlightAttributes != null) { - this.activeAttributes = this.highlightAttributes; - } else { - this.activeAttributes = this.attributes; - } - } - - /** - * Determines the image texture and unit square transform to use for the current render pass. - * - * @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. - if (this.activeTexture == null) { - this.activeTexture = rc.retrieveTexture(this.activeAttributes.imageSource, null); // puts retrieved textures in the cache - } - } 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(); + 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 @@ -831,34 +789,113 @@ protected void determineActiveTexture(RenderContext rc) { } // Position image on screen - unitSquareTransform.multiplyByTranslation( + 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. - unitSquareTransform.multiplyByScale(1, 1, 1d / (1 << 24) ); + 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 tilt = this.imageTiltReference == WorldWind.RELATIVE_TO_GLOBE ? + double actualTilt = this.imageTiltReference == WorldWind.RELATIVE_TO_GLOBE ? rc.camera.tilt + this.imageTilt : this.imageTilt; - unitSquareTransform.multiplyByRotation(-1, 0, 0, tilt); + imageTransform.multiplyByRotation(-1, 0, 0, actualTilt); } // Perform image rotation if (this.imageRotation != 0) { - double rotation = this.imageRotationReference == WorldWind.RELATIVE_TO_GLOBE ? + double actualRotation = this.imageRotationReference == WorldWind.RELATIVE_TO_GLOBE ? rc.camera.heading - this.imageRotation : -this.imageRotation; - unitSquareTransform.multiplyByRotation(0, 0, 1, rotation); + imageTransform.multiplyByRotation(0, 0, 1, actualRotation); } // Apply pivot translation - unitSquareTransform.multiplyByTranslation(-offsetX, -offsetY, 0); + imageTransform.multiplyByTranslation(-offsetX, -offsetY, 0); // Apply scale - unitSquareTransform.multiplyByScale(scaleX, scaleY, 1); + imageTransform.multiplyByScale(scaleX, scaleY, 1); + + // If the placemark's icon is visible, enqueue a drawable icon for processing on the OpenGL thread. + 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) { + int w = this.labelTexture.getWidth(); + int h = this.labelTexture.getHeight(); + this.activeAttributes.labelAttributes.textOffset.offsetForSize(w, h, offset); + + labelTransform.setTranslation( + screenPlacePoint.x - offset.x, + screenPlacePoint.y - offset.y, + screenPlacePoint.z); + + labelTransform.setScale(w, h, 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) { + rc.offerPickedObject(PickedObject.fromRenderable(this.pickedObjectId, this, rc.currentLayer)); + } + } + + /** + * Determines the placemark attributes to use for the current render pass. + * + * @param rc the current render context + */ + protected void determineActiveAttributes(RenderContext rc) { + if (this.highlighted && this.highlightAttributes != null) { + this.activeAttributes = this.highlightAttributes; + } else { + this.activeAttributes = this.attributes; + } + } + + /** + * Determines the image texture and unit square transform to use for the current render pass. + * + * @param rc the current render context + */ + protected void determineActiveTexture(RenderContext rc) { + 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. + if (this.activeTexture == null) { + this.activeTexture = rc.retrieveTexture(this.activeAttributes.imageSource, null); // puts retrieved textures in the cache + } + } else { + this.activeTexture = null; // there is no imageSource; draw a simple colored square + } } /** @@ -876,7 +913,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 @@ -887,6 +924,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. @@ -925,13 +993,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.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..e7877bfad 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/PlacemarkAttributes.java @@ -306,7 +306,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; } From 1129ef0a821c0d6d5964179544fe7290aaa60b3d Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sat, 23 Apr 2022 12:53:47 +0300 Subject: [PATCH 4/8] Add possibility to control Placemark label visibility via PlacemarkAttributes. --- .../gov/nasa/worldwind/shape/Placemark.java | 4 ++-- .../worldwind/shape/PlacemarkAttributes.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) 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 74f91d75f..611ce4c7b 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -994,8 +994,8 @@ protected void prepareDrawableLeader(RenderContext rc, DrawableLines drawable) { * @return True if there is a valid label and label attributes. */ protected boolean mustDrawLabel(RenderContext rc) { - return this.label != null - && !this.label.isEmpty() + 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 e7877bfad..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. */ From e70807bbcd17de4dd47136052089f7fd7e454195 Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sat, 23 Apr 2022 12:56:54 +0300 Subject: [PATCH 5/8] Add possibility to control Placemark visibility from LevelOfDetailsSelector via return parameter instead of nulling PlacamerkAttributes. --- .../gov/nasa/worldwindx/PlacemarksDemoActivity.java | 12 ++++++++++-- .../milstd2525/MilStd2525LevelOfDetailSelector.java | 6 +++++- .../java/gov/nasa/worldwind/shape/Placemark.java | 9 ++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) 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 bd75409d3..59e6ecbd8 100644 --- a/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java +++ b/worldwind-examples/src/main/java/gov/nasa/worldwindx/PlacemarksDemoActivity.java @@ -142,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; @@ -213,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) { 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/Placemark.java b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java index 611ce4c7b..2a141bc0c 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); } /** @@ -702,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. From fdbed02d78d0e1ab702cf004d628358624fc3f9a Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Tue, 26 Apr 2022 10:44:25 +0300 Subject: [PATCH 6/8] Implement label scaling. --- .../java/gov/nasa/worldwind/shape/Label.java | 7 ++++--- .../gov/nasa/worldwind/shape/Placemark.java | 12 +++++++++--- .../nasa/worldwind/shape/TextAttributes.java | 19 ++++++++++++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) 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 2a141bc0c..6aa9aa2b7 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/shape/Placemark.java @@ -840,16 +840,22 @@ protected void doRender(RenderContext rc) { } 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, - screenPlacePoint.y - offset.y, + screenPlacePoint.x - offset.x * s, + screenPlacePoint.y - offset.y * s, screenPlacePoint.z); - labelTransform.setScale(w, h, 1); + labelTransform.setScale(w * s, h * s, 1); WWMath.boundingRectForUnitSquare(labelTransform, labelBounds); if (rc.frustum.intersectsViewport(labelBounds)) { 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; + } } From 313d90c8cb5685c987a7aa0e3acd31fd6d5df2cd Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Sun, 17 Jul 2022 14:09:51 +0300 Subject: [PATCH 7/8] Fix text rendering issue introduced in commit 9377485d --- .../src/main/java/gov/nasa/worldwind/render/RenderContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java b/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java index 2fcf20b9f..6c29df0c6 100644 --- a/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java +++ b/worldwind/src/main/java/gov/nasa/worldwind/render/RenderContext.java @@ -432,7 +432,7 @@ public Texture getText(String text, TextAttributes attributes) { } public Texture renderText(String text, TextAttributes attributes) { - TextCacheKey key = this.scratchTextCacheKey.set(text, attributes); + TextCacheKey key = new TextCacheKey().set(text, attributes); Texture texture = null; if (text != null && attributes != null) { From ec7d2489f7665645fdcb9abd9de0aa0a14efca46 Mon Sep 17 00:00:00 2001 From: Eugene Maksymenko Date: Mon, 25 Jul 2022 21:02:03 +0300 Subject: [PATCH 8/8] Remove unused bounds attribute and related Rect import. --- .../main/java/gov/nasa/worldwindx/milstd2525/MilStd2525.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 c1b960cf7..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,7 +265,7 @@ 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 // Use billboarding or lollipopping to prevent icon clipping by terrain as described in MIL-STD-2525C APPENDIX F.5.1.1.2