/* Copyright (C) 2001, 2006, 2007 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.View; import gov.nasa.worldwind.pick.*; import gov.nasa.worldwind.exception.WWRuntimeException; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.RestorableSupport; import gov.nasa.worldwind.geom.*; import com.sun.opengl.util.j2d.TextRenderer; import com.sun.opengl.util.texture.Texture; import com.sun.opengl.util.texture.TextureIO; import javax.media.opengl.GL; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.geom.Rectangle2D; import java.nio.DoubleBuffer; import java.util.logging.Level; /** * An {@link Annotation} represent a text label and its rendering attributes. Annotations must be attached either to * a globe <code>Position</code> ({@link GlobeAnnotation}) or a viewport <code>Point</code> (ScreenAnnotation). * <p/> * <pre> * GlobaAnnotation ga = new GlobeAnnotation("Lat-Lon zero", Position.fromDegrees(0, 0, 0))); * ScreenAnnotation sa = new ScreenAnnotation("Message...", new Point(10,10)); * </pre> * <p> * Each Annotation refers to an {@link AnnotationAttributes} object which defines how the text will be rendered. * </p> * Rendering attributes allow to set: * <ul> * <li>the size of the bounding rectangle into which the text will be displayed</li> * <li>its frame shape, border color, width and stippling pattern</li> * <li>the text font, size, style and color</li> * <li>the background color or image</li> * <li>how much an annotation scales and fades with distance</li> * </ul> * <pre> * ga.getAttributes().setTextColor(Color.WHITE); * ga.getAttributes().setFont(Font.decode("Arial-BOLD-24"); * ... * </pre> * <p/> * Annotations are usually handled by an {@link gov.nasa.worldwind.layers.AnnotationLayer}. Although they also implement the {@link Renderable} * and {@link Pickable} interfaces and thus can be handled by a {@link gov.nasa.worldwind.layers.RenderableLayer} too. * <p/> * <pre> * AnnotationLayer layer = new AnnotationLayer(); * layer.addAnnotation(new GlobeAnnotation("Text...", Position.fromDegrees(0, 0, 0))); * </pre> * <p/> * Each Annotation starts its life with a fresh attribute set that can be altered to produce the desired effect. * However, <code>AnnotationAttributes</code> can be set and shared between annotations allowing to control the rendering attributes * of many annotations from a single <code>AnnotationAttributes</code> object. * <p/> * <pre> * AnnotationAttributes attr = new AnnotationAttributes(); * attr.setTextColor(Color.WHITE); * attr.setFont(Font.decode("Arial-BOLD-24"); * ga.setAttributes(attr); * </pre> * <p/> * In the above example changing the text color of the attributes set will affect all annotations refering it. However, * changing the text color of one of those annotations will also affect all others since it will in fact change the * common attributes set. * <p> * To use an attributes object only as default values for a serie of annotations use: * </p> * <pre> * ga.getAttributes()setDefaults(attr); * </pre> * <p/> * which can also be done in the Annotation constructor: * <p/> * <pre> * GlobeAnnotation ga = new GlobeAnnotation(text, position, attr); * </pre> * <p/> * Finer control over attributes inheritence can be achieved using default or fallback attributes set. * <p> * Most attributes can be set to a 'use default' value which is minus one for numeric values and <code>null</code> for attributes * refering objects (colors, dimensions, insets..). In such a case the value of an attribute will be that of the * default attribute set. New annotations have all their attributes set to use default values. * </p> * <p/> * Each <code>AnnotationAttributes</code> object points to a default static attributes set which is the fallback source for * attributes with <code>null</code> or <code>-1</code> values. This default attributes set can be set to any attributes object other than the * static one. * <p/> * <pre> * AnnotationAttributes geoFeature = new AnnotationAttributes(); * geoFeature.setFrameShape(FrameFactory.SHAPE_ELLIPSE); * geoFeature.setInsets(new Insets(12, 12, 12, 12)); * <p/> * AnnotationAttributes waterBody = new AnnotationAttributes(); * waterBody.setTextColor(Color.BLUE); * waterBoby.setDefaults(geoFeature); * <p/> * AnnotationAttributes mountain = new AnnotationAttributes(); * mountain.setTextColor(Color.GREEN); * mountain.setDefaults(geoFeature); * <p/> * layer.addAnnotation(new GlobeAnnotation("Spirit Lake", Position.fromDegrees(46.26, -122.15), waterBody); * layer.addAnnotation(new GlobeAnnotation("Mt St-Helens", Position.fromDegrees(46.20, -122.19), mountain); * </pre> * <p/> * In the above example all geographic features have an ellipse shape, water bodies and mountains use that attributes * set has defaults and have their own text colors. They are in turn used as defaults by the two annotations. Mount * Saint Helens attributes could be changed without affecting other mountains. However, changes on the geoFeatures * attributes would affect all mountains and lakes. * * @author Patrick Murris * @version $Id$ * @see AnnotationAttributes * @see AnnotationRenderer */ public abstract class AbstractAnnotation implements Annotation { protected String text; protected AnnotationAttributes attributes = new AnnotationAttributes(); private boolean alwaysOnTop = false; protected RenderInfo renderInfo; protected Object delegateOwner; abstract protected void doDraw(DrawContext dc); private class RenderInfo { // Reference values private final String text; private final Dimension size; private final Insets insets; private final Font font; private final double borderWidth; private final Texture texture; private final String shape; private final String leader; private final int cornerRadius; private final Point offset; private final String adjustWidthToText; // Cached values private final String drawText; private final Rectangle textBounds; private final DoubleBuffer verts; private final DoubleBuffer coords; public RenderInfo(String text, Dimension size, Insets insets, Font font, double borderWidth, Texture texture, String shape, String leader, int cornerRadius, Point offset, String adjustWidthToText, String drawText, Rectangle textBounds, DoubleBuffer verts, DoubleBuffer coords) { // Key values this.text = text; this.size = size; this.insets = insets; this.font = font; this.borderWidth = borderWidth; this.texture = texture; this.shape = shape; this.leader = leader; this.cornerRadius = cornerRadius; this.offset = offset; this.adjustWidthToText = adjustWidthToText; // Cached values this.drawText = drawText; this.textBounds = textBounds; this.verts = verts; this.coords = coords; } public String getDrawText() { return this.drawText; } public Rectangle getTextBounds() { return this.textBounds; } public DoubleBuffer getVertices() { return this.verts; } public DoubleBuffer getTextureCoordinates() { return this.coords; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RenderInfo ri = (RenderInfo) o; // Compare key values if (text == null ^ ri.text == null) return false; if (text != null && ri.text != null) if (!text.equals(ri.text)) return false; if (size == null ^ ri.size == null) return false; if (size != null && ri.size != null) if (!size.equals(ri.size)) return false; if (insets == null ^ ri.insets == null) return false; if (insets != null && ri.insets != null) if (!insets.equals(ri.insets)) return false; if (font == null ^ ri.font == null) return false; if (font != null && ri.font != null) if (!font.equals(ri.font)) return false; if (borderWidth != ri.borderWidth) return false; if (texture == null ^ ri.texture == null) return false; if (texture != null && ri.texture != null) if (!texture.equals(ri.texture)) return false; if (shape == null ^ ri.shape == null) return false; if (shape != null && ri.shape != null) if (!shape.equals(ri.shape)) return false; if (leader == null ^ ri.leader == null) return false; if (leader != null && ri.leader != null) if (!leader.equals(ri.leader)) return false; if (cornerRadius != ri.cornerRadius) return false; if (offset == null ^ ri.offset == null) return false; if (offset != null && ri.offset != null) if (offset.distance(ri.offset) != 0) return false; if (adjustWidthToText == null ^ ri.adjustWidthToText == null) return false; if (adjustWidthToText != null && ri.adjustWidthToText != null) if (!adjustWidthToText.equals(ri.adjustWidthToText)) return false; return true; } } public Object getDelegateOwner() { return delegateOwner; } public void setDelegateOwner(Object delegateOwner) { this.delegateOwner = delegateOwner; } public String getText() { return this.text; } public void setText(String text) { if (text == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.text = text; } public AnnotationAttributes getAttributes() { return this.attributes; } public void setAttributes(AnnotationAttributes attrs) { if (attrs == null) { String message = Logging.getMessage("nullValue.AnnotationAttributesIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.attributes = attrs; } /** * Render the annotation. Called as a Renderable. * * @param dc the current DrawContext. */ public void render(DrawContext dc) { dc.getAnnotationRenderer().render(dc, this, null); } /** * Draw the annotation. Called as an Annotation by an AnnotationRenderer while batch rendering. * The gl context is ready to draw with the model view set as an identity ortho projection. * * @param dc the current DrawContext. */ public void draw(DrawContext dc) { this.doDraw(dc); } /** * Pick at the annotation. Called as a Pickable. * * @param dc the current DrawContext. */ public void pick(DrawContext dc, Point pickPoint) { dc.getAnnotationRenderer().pick(dc, this, null, pickPoint, null); } public void dispose() { } protected TextRenderer getTextRenderer(DrawContext dc, Font font) { TextRenderer tr = dc.getTextRendererCache().get(font); if (tr == null) { tr = new TextRenderer(font, true, true); tr.setUseVertexArrays(false); dc.getTextRendererCache().add(font, tr); } return tr; } protected RenderInfo getRenderInfo(DrawContext dc, Annotation annotation) { return this.renderInfo; } protected void cacheRenderInfo(Annotation annotation, RenderInfo renderInfo) { this.renderInfo = renderInfo; } public PickSupport getPickSupport() { return this.pickSupport; } public void setPickSupport(PickSupport pickSupport) { if (pickSupport == null) { String message = Logging.getMessage("nullValue.PickSupportIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.pickSupport = pickSupport; } public boolean isAlwaysOnTop() { return alwaysOnTop; } public void setAlwaysOnTop(boolean alwaysOnTop) { this.alwaysOnTop = alwaysOnTop; } // -- Rendering ------------------------------------------------------------------------ private float[] compArray; private Dimension minSize = new Dimension(20, 20); private PickSupport pickSupport; /** * Values used for drawing. * <p> * Should be used by post drawing code. * </p> */ protected double scaleFactor; // Scale factor used for distance and highlight - 1 = no change protected double alphaFactor; // Opacity factor - 1 = fully opaque, 0 = transparent protected Rectangle drawRectangle; // Viewport rectangle where annotation content was drawn protected Rectangle freeRectangle; // Free space inside the draw rectangle, below the text /** * Draws an annotation at a screen point. Current GL state has ortho identity model * view active with origin at the screen point. * * @param dc the current DrawContext. * @param screenPoint the annotation position projected location on the viewport. * @param pickPosition the <code>Position</code> that will be associated with any <code>PickedObject</code> * produced during picking. */ protected void drawAnnotation(DrawContext dc, Point screenPoint, double drawScale, double drawAlpha, Position pickPosition) { // Get TextRenderer TextRenderer tr = getTextRenderer(dc, attributes.getFont()); MultiLineTextRenderer mltr = new MultiLineTextRenderer(tr); mltr.setLineSpacing(-2); // Tighten lines together a bit mltr.setTextAlign(attributes.getTextAlign()); // Get texture if any Texture annotationTexture = null; if (attributes.getImageSource() != null) { annotationTexture = dc.getTextureCache().get(attributes.getImageSource()); if (annotationTexture == null) annotationTexture = initializeTexture(dc, this); } // Get colors Color textColor = attributes.getTextColor(); Color backColor = attributes.getBackgroundColor(); Color borderColor = attributes.getBorderColor(); // Get shape, insets, cornerRadius, offset and border width String shape = attributes.getFrameShape(); String leader = attributes.getLeader(); String adjustWidthToText = attributes.getAdjustWidthToText(); Insets insets = attributes.getInsets(); double borderWidth = attributes.getBorderWidth(); Point offset = attributes.getDrawOffset(); int cornerRadius = attributes.getCornerRadius(); // Scaling and fading factors this.scaleFactor = attributes.getScale() * drawScale; this.alphaFactor = attributes.getOpacity() * drawAlpha; // Highlight if (attributes.isHighlighted()) { // Factor in highlight scale and remove transparency if highlighted this.scaleFactor *= attributes.getHighlightScale(); this.alphaFactor = 1; } // Find call out preferred dimension Dimension size = attributes.getSize(); // Clamp size size.setSize(Math.max(size.getWidth(), minSize.getWidth()), size.getHeight() > 0 ? Math.max(size.getHeight(), minSize.getHeight()) : 0); // Draw area dimension - TODO: factor in border width Dimension drawSize = new Dimension( (int) size.getWidth() - insets.left - insets.right, size.getHeight() > 0 ? Math.max((int) size.getHeight() - insets.top - insets.bottom, 1) : 0); // Render values String wrappedText; // Draw text Rectangle2D textBounds; // Rendered text bounds Double width, height; // Final callout dimension DoubleBuffer verts = null; // Callout shape vertices DoubleBuffer coords = null; // Callout shape texture coordinates // Get render info and values RenderInfo oldRI = getRenderInfo(dc, this); RenderInfo newRI = new RenderInfo(getText(), size, insets, attributes.getFont(), borderWidth, annotationTexture, shape, leader, cornerRadius, offset, adjustWidthToText, null, null, null, null); if (newRI.equals(oldRI)) { // Use old render info wrappedText = oldRI.getDrawText(); textBounds = oldRI.getTextBounds(); verts = oldRI.getVertices(); coords = oldRI.getTextureCoordinates(); if (adjustWidthToText.equals(Annotation.SIZE_FIT_TEXT) && getText().length() > 0) width = textBounds.getWidth() + insets.left + insets.right; else width = size.getWidth(); height = size.getHeight() > 0 ? size.getHeight() : textBounds.getHeight() + insets.top + insets.bottom; // Compute placement offset //offset = computeOffset(new Point((int)screenPoint.x, (int)screenPoint.y), new Dimension((int)width, (int)height)); } else { // Compute new render info if (getText().length() > 0) { // Wrap text to max available width if (MultiLineTextRenderer.containsHTML(getText())) { // Simple HTML wrappedText = MultiLineTextRenderer.processLineBreaksHTML(getText()); wrappedText = mltr.wrapHTML(wrappedText, drawSize, dc.getTextRendererCache()); textBounds = mltr.getBoundsHTML(wrappedText, dc.getTextRendererCache()); } else { // Regular text wrappedText = mltr.wrap(getText(), drawSize); textBounds = mltr.getBounds(wrappedText); } } else { // No text wrappedText = ""; textBounds = new Rectangle(0, 0, 0, 0); } // Final call out dimension - use size height if not zero and size width if no text if (adjustWidthToText.equals(Annotation.SIZE_FIT_TEXT) && getText().length() > 0) width = textBounds.getWidth() + insets.left + insets.right; else width = size.getWidth(); height = size.getHeight() > 0 ? size.getHeight() : textBounds.getHeight() + insets.top + insets.bottom; // Compute placement offset //offset = computeOffset(new Point((int)screenPoint.x, (int)screenPoint.y), new Dimension((int)width, (int)height)); // Get shape vertices and texture coordinates if (!shape.equals(FrameFactory.SHAPE_NONE)) { Point shapeLeaderOffset = new Point((int) (width / 2 - offset.x), -offset.y); verts = attributes.getLeader().equals(FrameFactory.LEADER_TRIANGLE) ? FrameFactory.createShapeWithLeaderBuffer(shape, width, height, shapeLeaderOffset, cornerRadius) : FrameFactory.createShapeBuffer(shape, width, height, cornerRadius); if (annotationTexture != null && verts != null) coords = FrameFactory.getTextureCoordinates(verts, width, height, annotationTexture.getWidth(), annotationTexture.getHeight()); else coords = null; } // Save render info newRI = new RenderInfo(getText(), size, insets, attributes.getFont(), borderWidth, annotationTexture, shape, leader, cornerRadius, offset, adjustWidthToText, wrappedText, (Rectangle) textBounds, verts, coords); cacheRenderInfo(this, newRI); } // Update current draw rectangle and free rectangle this.drawRectangle = new Rectangle((int) (screenPoint.x + offset.x - width / 2 + insets.left), (int) (screenPoint.y + offset.y + insets.bottom), (int) (width - insets.left - insets.right - 1), (int) (height - insets.bottom - insets.top - 1)); this.freeRectangle = new Rectangle(drawRectangle.x, drawRectangle.y, drawRectangle.width, drawRectangle.height - (int) textBounds.getHeight()); // Apply scale factor and translate to callout lower left corner GL gl = dc.getGL(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); // Translate to screenpoint gl.glTranslated(screenPoint.x, screenPoint.y, 0d); gl.glScaled(this.scaleFactor, this.scaleFactor, 1d); gl.glTranslated(-width / 2 + offset.x, offset.y, 0d); if (dc.isPickingMode()) { // Picking Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); this.pickSupport.addPickableObject(colorCode, this.delegateOwner != null ? this.delegateOwner : this, pickPosition, false); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); } else { // Set background color setDrawColor(dc, new Color(backColor.getRed(), backColor.getGreen(), backColor.getBlue(), (int) ((float) backColor.getAlpha() * this.alphaFactor))); } // Draw callout background color if (verts != null && (backColor.getAlpha() > 10 || dc.isPickingMode())) { gl.glDisable(GL.GL_TEXTURE_2D); FrameFactory.drawBuffer(dc, GL.GL_TRIANGLE_FAN, verts); } // Draw callout texture if (!dc.isPickingMode() && annotationTexture != null && verts != null) { annotationTexture.bind(); // Texture repeat and transform if (attributes.getImageRepeat().equals(Annotation.IMAGE_REPEAT_X) || attributes.getImageRepeat().equals(Annotation.IMAGE_REPEAT_XY)) annotationTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT); else annotationTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_BORDER); if (attributes.getImageRepeat().equals(Annotation.IMAGE_REPEAT_Y) || attributes.getImageRepeat().equals(Annotation.IMAGE_REPEAT_XY)) annotationTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT); else annotationTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_BORDER); gl.glMatrixMode(GL.GL_TEXTURE); gl.glLoadIdentity(); gl.glScaled(1 / attributes.getImageScale(), 1 / attributes.getImageScale(), 1d); if (attributes.getImageOffset() != null) gl.glTranslated(-(double) attributes.getImageOffset().x / annotationTexture.getWidth(), -(double) attributes.getImageOffset().y / annotationTexture.getHeight(), 0d); gl.glEnable(GL.GL_TEXTURE_2D); // Set opacity byte textureOpacity = (byte) (attributes.getImageOpacity() * this.alphaFactor * 255); gl.glColor4ub(textureOpacity, textureOpacity, textureOpacity, textureOpacity); // Draw FrameFactory.drawBuffer(dc, GL.GL_TRIANGLE_FAN, verts, coords); gl.glDisable(GL.GL_TEXTURE_2D); gl.glLoadIdentity(); gl.glMatrixMode(GL.GL_MODELVIEW); } // Draw call out border if (!dc.isPickingMode() && borderWidth > 0 && verts != null) { // Set border color, line width and stipple if any gl.glLineWidth((float) (borderWidth * this.scaleFactor)); setDrawColor(dc, new Color(borderColor.getRed(), borderColor.getGreen(), borderColor.getBlue(), (int) ((float) borderColor.getAlpha() * alphaFactor))); if (attributes.getBorderStippleFactor() > 0) { gl.glEnable(GL.GL_LINE_STIPPLE); gl.glLineStipple(attributes.getBorderStippleFactor(), attributes.getBorderStipplePattern()); } else { gl.glDisable(GL.GL_LINE_STIPPLE); } if (attributes.getAntiAliasHint() == Annotation.ANTIALIAS_NICEST) { gl.glEnable(GL.GL_LINE_SMOOTH); gl.glHint(GL.GL_LINE_SMOOTH_HINT, attributes.getAntiAliasHint()); } else gl.glDisable(GL.GL_LINE_SMOOTH); // Draw FrameFactory.drawBuffer(dc, GL.GL_LINE_STRIP, verts); gl.glLineWidth(1f); // reset line width } // Draw multi-line text double baseLineOffset = textBounds.getMinY() / 6; // Max line height / 6 gl.glLoadIdentity(); // Translate and scale to screen point gl.glTranslated(screenPoint.x, screenPoint.y, 0d); gl.glScaled(this.scaleFactor, this.scaleFactor, 1d); // Skip if no text if (getText().length() == 0) return; // Compute text draw start point - depending on text align //Point drawPointBottom = new Point((int)( - width / 2 + insets.left + offset.x), // (int)( insets.bottom + offset.y + baseLineOffset + textBounds.getHeight()) + 1); Point drawPoint = new Point((int) (-width / 2 + insets.left + offset.x), (int) (offset.y + insets.bottom + (drawSize.getHeight() > 0 ? drawSize.getHeight() : textBounds.getHeight()) + baseLineOffset) + 2); if (attributes.getTextAlign() == MultiLineTextRenderer.ALIGN_CENTER) drawPoint.setLocation(offset.x + (insets.left - insets.right) / 2, drawPoint.y); if (attributes.getTextAlign() == MultiLineTextRenderer.ALIGN_RIGHT) drawPoint.setLocation((int) (width / 2 - insets.right + offset.x), drawPoint.y); // Draw if (MultiLineTextRenderer.containsHTML(getText())) { // Simple html if (!dc.isPickingMode()) { mltr.setTextColor(new Color(textColor.getRed(), textColor.getGreen(), textColor.getBlue(), (int) ((float) textColor.getAlpha() * this.alphaFactor))); mltr.drawHTML(wrappedText, drawPoint.x, drawPoint.y, dc.getTextRendererCache()); } else { // Draw text with unique colors for each word - only if cursor is inside our draw rectangle if (dc.getPickPoint() != null) if (getRectangleInViewportCoordinates(dc, this.drawRectangle, screenPoint, scaleFactor) .contains(dc.getPickPoint())) mltr.pickHTML(wrappedText, drawPoint.x, drawPoint.y, dc.getTextRendererCache(), dc, this.pickSupport, this.delegateOwner != null ? this.delegateOwner : this, pickPosition); } } else { // Regular text if (!dc.isPickingMode()) { mltr.setTextColor(new Color(textColor.getRed(), textColor.getGreen(), textColor.getBlue(), (int) (textColor.getAlpha() * this.alphaFactor))); mltr.setBackColor(new Color(backColor.getRed(), backColor.getGreen(), backColor.getBlue(), (int) (backColor.getAlpha() * this.alphaFactor))); mltr.getTextRenderer().begin3DRendering(); mltr.draw(wrappedText, drawPoint.x, drawPoint.y, (int) textBounds.getMinY(), attributes.getEffect()); mltr.getTextRenderer().end3DRendering(); } else { // Draw text with unique colors for each word - only if cursor is inside our draw rectangle if (dc.getPickPoint() != null) if (getRectangleInViewportCoordinates(dc, this.drawRectangle, screenPoint, scaleFactor) .contains(dc.getPickPoint())) mltr.pick(wrappedText, drawPoint.x, drawPoint.y, (int) textBounds.getMinY(), dc, this.pickSupport, this.delegateOwner != null ? this.delegateOwner : this, pickPosition); } } } // -- end drawAnnotation() -------------------------------------------------------------- // Compute distance from eye to the position in the middle of the screen protected double computeLookAtDistance(DrawContext dc) { View view = dc.getView(); // Get point in the middle of the screen Position groundPos = view.computePositionFromScreenPoint( view.getViewport().getWidth() / 2, view.getViewport().getHeight() / 2); // Return eye altitude if no point found if (groundPos == null) return view.getEyePosition().getElevation(); return view.getEyePoint().distanceTo3(dc.getGlobe().computePointFromPosition(groundPos)); } // Compute viewport rectangle coordinates after scaling from screen point. protected Rectangle getRectangleInViewportCoordinates(DrawContext dc, Rectangle r, Point screenPoint, double scaleFactor) { Rectangle r2 = new Rectangle(r); r2.translate(-screenPoint.x, -screenPoint.y); r2.setRect(r2.x * scaleFactor, r2.y * scaleFactor, r2.width * scaleFactor, r2.height * scaleFactor); r2.translate(screenPoint.x, screenPoint.y); return new Rectangle(r2.x, dc.getView().getViewport().height - r2.y - r2.height, r2.width, r2.height); } // Initialize texture protected Texture initializeTexture(DrawContext dc, Annotation annotation) { try { Texture annotationTexture = null; Object imageSource = annotation.getAttributes().getImageSource(); if (imageSource instanceof String) { String path = (String) imageSource; java.io.InputStream textureStream = this.getClass().getResourceAsStream("/" + path); if (textureStream == null) { java.io.File textureFile = new java.io.File(path); if (textureFile.exists()) { textureStream = new java.io.FileInputStream(textureFile); } } annotationTexture = TextureIO.newTexture(textureStream, true, null); } else if (imageSource instanceof BufferedImage) { annotationTexture = TextureIO.newTexture((BufferedImage) imageSource, true); } else { // TODO: Log case of unknown image-source type. } if (annotationTexture == null) { // TODO: Log case. return null; } // Annotations with the same path are assumed to be identical textures, so key the texture id off the path. dc.getTextureCache().put(imageSource, annotationTexture); annotationTexture.bind(); GL gl = dc.getGL(); gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE); return annotationTexture; } catch (java.io.IOException e) { String msg = Logging.getMessage("generic.IOExceptionDuringTextureInitialization"); Logging.logger().log(Level.SEVERE, msg, e); throw new WWRuntimeException(msg, e); } } // Set draw color using pre multipied alpha rgb values. protected void setDrawColor(DrawContext dc, float r, float g, float b, float a) { GL gl = dc.getGL(); dc.getGL().glColor4f(r * a, g * a, b * a, a); } // Set draw color using pre multipied alpha rgb values. protected void setDrawColor(DrawContext dc, Color color) { if (this.compArray == null) this.compArray = new float[4]; color.getRGBComponents(this.compArray); this.setDrawColor(dc, this.compArray[0], this.compArray[1], this.compArray[2], this.compArray[3]); } protected void setDepthFunc(DrawContext dc, Vec4 screenPoint) { GL gl = dc.getGL(); Position eyePos = dc.getView().getEyePosition(); if (eyePos == null) { gl.glDepthFunc(GL.GL_ALWAYS); return; } double altitude = eyePos.getElevation(); if (altitude < (dc.getGlobe().getMaxElevation() * dc.getVerticalExaggeration())) { double depth = screenPoint.z - (8d * 0.00048875809d); depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth); gl.glDepthFunc(GL.GL_LESS); gl.glDepthRange(depth, depth); } else if (screenPoint.z >= 1d) { gl.glDepthFunc(GL.GL_EQUAL); gl.glDepthRange(1d, 1d); } else { gl.glDepthFunc(GL.GL_ALWAYS); } } /** * Returns an XML state document String describing the public attributes of this AbstractAnnotation. * * @return XML state document string describing this AbstractAnnotation. */ public String getRestorableState() { RestorableSupport restorableSupport = null; // This should never be the case, but we check to be thorough. if (this.attributes != null) { // Allow AnnotationAttributes to define it's restorable state, if any. String attributesStateInXml = this.attributes.getRestorableState(); if (attributesStateInXml != null) { try { restorableSupport = RestorableSupport.parse(attributesStateInXml); } catch (Exception e) { // Parsing the document specified by the superclass failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", attributesStateInXml); Logging.logger().severe(message); } } } // Create our own state document from scratch. if (restorableSupport == null) restorableSupport = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (restorableSupport == null) return null; // Escape the text property when saving it to preserve markup characters. if (this.text != null) restorableSupport.addStateValueAsString("text", this.text, true); restorableSupport.addStateValueAsBoolean("alwaysOnTop", this.alwaysOnTop); return restorableSupport.getStateAsXml(); } /** * Restores publicly settable attribute values found in the specified XML state document String. The * document specified by <code>stateInXml</code> must be a well formed XML document String, or this will throw an * IllegalArgumentException. Unknown structures in <code>stateInXml</code> are benign, because they will * simply be ignored. * * @param stateInXml an XML document String describing an AbstractAnnotation. * @throws IllegalArgumentException If <code>stateInXml</code> is null, or if <code>stateInXml</code> is not * a well formed XML document String. */ public void restoreState(String stateInXml) { if (stateInXml == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } RestorableSupport restorableSupport; try { restorableSupport = RestorableSupport.parse(stateInXml); } catch (Exception e) { // Parsing the document specified by stateInXml failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml); Logging.logger().severe(message); throw new IllegalArgumentException(message, e); } AnnotationAttributes attribs = this.attributes; // Annotation's attributes should not be null. Therefore we assign it a new one as a fallback. if (attribs == null) attribs = new AnnotationAttributes(); // Restore any AnnotationAttributes state found in "stateInXml". attribs.restoreState(stateInXml); setAttributes(attribs); // No special processing is required to restore the escaped text property. String textState = restorableSupport.getStateValueAsString("text"); if (textState != null) setText(textState); Boolean booleanState = restorableSupport.getStateValueAsBoolean("alwaysOnTop"); if (booleanState != null) setAlwaysOnTop(booleanState); } }