diff --git a/src/gov/nasa/worldwind/render/SurfaceText.java b/src/gov/nasa/worldwind/render/SurfaceText.java index 3b8be4d20c..6369ee6af6 100644 --- a/src/gov/nasa/worldwind/render/SurfaceText.java +++ b/src/gov/nasa/worldwind/render/SurfaceText.java @@ -42,6 +42,8 @@ public class SurfaceText extends AbstractSurfaceObject implements GeographicText protected CharSequence text; /** Location at which to draw the text. */ protected Position location; + /** The angle of text rotation from the true north (clockwise). */ + protected Angle heading = Angle.ZERO; /** The height of the text in meters. */ protected double textSizeInMeters = DEFAULT_TEXT_SIZE_IN_METERS; /** Dragging Support */ @@ -153,6 +155,30 @@ public void setPosition(Position position) this.onShapeChanged(); } + /** {@inheritDoc} */ + public Angle getHeading() + { + return this.heading; + } + + /** + * {@inheritDoc} + *

+ * The angle of text rotation from the true north (clockwise) + */ + public void setHeading(Angle heading) + { + if (heading == null) + { + String message = Logging.getMessage("nullValue.HeadingIsNull"); + Logging.logger().severe(message); + throw new IllegalArgumentException(message); + } + + this.heading = heading; + this.onShapeChanged(); + } + /** {@inheritDoc} */ public Font getFont() { @@ -394,13 +420,6 @@ protected void drawGeographic(DrawContext dc, SurfaceTileDrawContext sdc) protected void drawText(DrawContext dc) { TextRenderer tr = this.getTextRenderer(dc); - - Point2D point = this.getOffset().computeOffset(this.textBounds.getWidth(), this.textBounds.getHeight(), null, - null); - - int x = (int) point.getX(); - int y = (int) point.getY(); - try { tr.begin3DRendering(); @@ -409,9 +428,9 @@ protected void drawText(DrawContext dc) CharSequence text = this.getText(); tr.setColor(bgColor); - tr.draw(text, x + 1, y - 1); + tr.draw(text, 1, -1); tr.setColor(this.getColor()); - tr.draw(text, x, y); + tr.draw(text, 0, 0); } finally { @@ -461,6 +480,24 @@ protected void applyDrawTransform(DrawContext dc, SurfaceTileDrawContext sdc) // Apply the scaling factor to draw the text at the correct geographic size gl.glScaled(this.scale, this.scale, 1d); + + double widthInPixels = this.textBounds.getWidth(); + double heightInPixels = this.textBounds.getHeight(); + + Point2D textDimensions = getRotatedTextDimensions(); + double rotatedPixelHeight = textDimensions.getY(); + double rotatedPixelWidth = textDimensions.getX(); + + Point2D textOffset = getOffset().computeOffset(rotatedPixelWidth, rotatedPixelHeight, null, null); + + // Move to offset position. + gl.glTranslated(rotatedPixelWidth / 2.0 + textOffset.getX(), rotatedPixelHeight / 2.0 + textOffset.getY(), 0); + + // Apply rotation angle from text center. + gl.glRotated(-this.heading.degrees, 0, 0, 1); + + // Move to text center. + gl.glTranslated(-widthInPixels / 2.0, -heightInPixels / 2.0, 0); } /** @@ -523,6 +560,54 @@ protected Color computeBackgroundColor(Color color) else return new Color(1, 1, 1, 0.7f); } + + private Point2D getRotatedTextDimensions() + { + double widthInPixels = this.textBounds.getWidth(); + double heightInPixels = this.textBounds.getHeight(); + + Angle rotation = Angle.normalizedLongitude(this.heading); + double ct = Math.cos(rotation.radians); + double st = Math.sin(rotation.radians); + + double hct = heightInPixels * ct; + double wct = widthInPixels * ct; + double hst = heightInPixels * st; + double wst = widthInPixels * st; + + if (rotation.degrees > 0) + { + if (rotation.degrees < 90) + { + // 0 < theta < 90 + heightInPixels = hct + wst; + widthInPixels = wct + hst; + } + else + { + // 90 <= theta <= 180 + heightInPixels = wst - hct; + widthInPixels = hst - wct; + } + } + else + { + if (rotation.degrees > -90 ) + { + // -90 < theta <= 0 + heightInPixels = hct - wst; + widthInPixels = wct - hst; + } + else + { + // -180 <= theta <= -90 + heightInPixels = -(hct + wst); + widthInPixels = -(wct + hst); + } + } + + return new Point2D.Double(widthInPixels, heightInPixels); + } /** * Compute the sector covered by this surface text. @@ -536,30 +621,32 @@ protected Sector[] computeSector(DrawContext dc) // Compute text extent depending on distance from eye Globe globe = dc.getGlobe(); - double widthInPixels = this.textBounds.getWidth(); - double heightInPixels = this.textBounds.getHeight(); - - double heightInMeters = this.textSizeInMeters; + Point2D textDimensions = getRotatedTextDimensions(); + double heightInPixels = textDimensions.getY(); + double widthInPixels = textDimensions.getX(); + + double heightFactor = heightInPixels / this.textBounds.getHeight(); + double heightInMeters = heightFactor * this.textSizeInMeters; double widthInMeters = heightInMeters * (widthInPixels / heightInPixels); - + double radius = globe.getRadius(); double heightInRadians = heightInMeters / radius; double widthInRadians = widthInMeters / radius; - // Compute the offset from the reference position. Convert pixels to meters based on the geographic size - // of the text. - Point2D point = this.getOffset().computeOffset(widthInPixels, heightInPixels, null, null); + // Compute the offset from the reference position. + // Convert pixels to meters based on the geographic size of the text. + Point2D textOffset = getOffset().computeOffset(widthInPixels, heightInPixels, null, null); double metersPerPixel = heightInMeters / heightInPixels; - double dxRadians = (point.getX() * metersPerPixel) / radius; - double dyRadians = (point.getY() * metersPerPixel) / radius; - + double dxRadians = (textOffset.getX() * metersPerPixel) / radius; + double dyRadians = (textOffset.getY() * metersPerPixel) / radius; + double minLat = this.location.latitude.addRadians(dyRadians).degrees; double maxLat = this.location.latitude.addRadians(dyRadians + heightInRadians).degrees; double minLon = this.location.longitude.addRadians(dxRadians).degrees; double maxLon = this.location.longitude.addRadians(dxRadians + widthInRadians).degrees; - + this.drawLocation = LatLon.fromDegrees(minLat, minLon); if (maxLon > 180) { diff --git a/testFunctional/gov/nasa/worldwind/render/SurfaceTextTest.java b/testFunctional/gov/nasa/worldwind/render/SurfaceTextTest.java new file mode 100644 index 0000000000..b9c915a2b6 --- /dev/null +++ b/testFunctional/gov/nasa/worldwind/render/SurfaceTextTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 United States Government as represented by the Administrator of the + * National Aeronautics and Space Administration. + * All Rights Reserved. + */ + +package gov.nasa.worldwind.render; + +import gov.nasa.worldwind.Configuration; +import gov.nasa.worldwind.WorldWind; +import gov.nasa.worldwind.avlist.AVKey; +import gov.nasa.worldwind.geom.Angle; +import gov.nasa.worldwind.geom.Position; +import gov.nasa.worldwind.layers.RenderableLayer; +import gov.nasa.worldwindx.examples.ApplicationTemplate; + +/** + * Test of {@link SurfaceText} rotation and offset calculations. This test creates + * several SurfaceText objects with different rotation and offset configurations. + * The SurfaceText objects can be visually inspected to confirm that the rotations + * and placements are correctly calculated, and that all of them are located + * within their respective bounding-sectors. + * + * @author pabercrombie + * @version $Id: SurfaceTextTest.java 1171 2013-02-11 21:45:02Z dcollins $ + */ +public class SurfaceTextTest extends ApplicationTemplate +{ + private static Position center = Position.fromDegrees(38.9345, -120.1670, 50000); + + public static class AppFrame extends ApplicationTemplate.AppFrame + { + public AppFrame() + { + super(true, true, false); + + RenderableLayer layer = new RenderableLayer(); + + PointPlacemarkAttributes attributes = new PointPlacemarkAttributes(); + attributes.setLabelColor("ffffffff"); + attributes.setLineColor("ff0000ff"); + attributes.setUsePointAsDefaultImage(true); + attributes.setScale(5d); + + int j = 0; + for (double x = -1.0; x <= 0.0; x += 0.5, j++) + { + for (double y = -1.0; y <= 0.0; y += 0.5, j++) + { + for (int i = 0; i <= 12; i++) + { + double latitude = center.latitude.degrees + ((j - 4) / 5.0); + double longitude = center.longitude.degrees + ((i - 6) / 5.0); + Position position = Position.fromDegrees(latitude, longitude, 0); + + SurfaceText surfaceText = new SurfaceText("Test Label Description", position); + surfaceText.setDrawBoundingSectors(true); + surfaceText.setHeading(Angle.fromDegrees(i * 30)); + surfaceText.setOffset(Offset.fromFraction(x, y)); + layer.addRenderable(surfaceText); + + PointPlacemark placemark = new PointPlacemark(position); + placemark.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); + placemark.setAttributes(attributes); + layer.addRenderable(placemark); + } + } + } + + this.getWwd().getModel().getLayers().add(layer); + } + } + + public static void main(String[] args) + { + Configuration.setValue(AVKey.INITIAL_LATITUDE, center.latitude.degrees); + Configuration.setValue(AVKey.INITIAL_LONGITUDE, center.longitude.degrees); + Configuration.setValue(AVKey.INITIAL_ALTITUDE, center.elevation); + + ApplicationTemplate.start("Surface Text Test", AppFrame.class); + } +}