/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.render; import com.sun.opengl.util.j2d.TextRenderer; import com.sun.opengl.util.texture.*; import gov.nasa.worldwind.Locatable; import gov.nasa.worldwind.exception.WWRuntimeException; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.SectorGeometryList; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.pick.PickSupport; import gov.nasa.worldwind.util.Logging; import javax.media.opengl.GL; import java.awt.*; import java.awt.image.*; import java.util.*; import java.util.logging.Level; /** * @author tag * @version $Id: IconRenderer.java 5168 2008-04-24 21:32:51Z dcollins $ */ public class IconRenderer { private Pedestal pedestal; private PickSupport pickSupport = new PickSupport(); private HashMap<Font, ToolTipRenderer> toolTipRenderers = new HashMap<Font, ToolTipRenderer>(); public IconRenderer() { } public Pedestal getPedestal() { return pedestal; } public void setPedestal(Pedestal pedestal) { this.pedestal = pedestal; } private static boolean isIconValid(WWIcon icon, boolean checkPosition) { if (icon == null || icon.getImageSource() == null) return false; //noinspection RedundantIfStatement if (checkPosition && icon.getPosition() == null) return false; return true; } public void pick(DrawContext dc, Iterable<WWIcon> icons, java.awt.Point pickPoint, Layer layer) { this.drawMany(dc, icons); } public void pick(DrawContext dc, WWIcon icon, Vec4 iconPoint, java.awt.Point pickPoint, Layer layer) { if (!isIconValid(icon, false)) return; this.drawOne(dc, icon, iconPoint); } public void render(DrawContext dc, Iterable<WWIcon> icons) { this.drawMany(dc, icons); } public void render(DrawContext dc, WWIcon icon, Vec4 iconPoint) { if (!isIconValid(icon, false)) return; this.drawOne(dc, icon, iconPoint); } private void drawMany(DrawContext dc, Iterable<WWIcon> icons) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (dc.getVisibleSector() == null) return; SectorGeometryList geos = dc.getSurfaceGeometry(); //noinspection RedundantIfStatement if (geos == null) return; if (icons == null) { String msg = Logging.getMessage("nullValue.IconIterator"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Iterator<WWIcon> iterator = icons.iterator(); if (!iterator.hasNext()) return; while (iterator.hasNext()) { WWIcon icon = iterator.next(); if (!isIconValid(icon, true)) continue; if (!icon.isVisible()) continue; // Determine Cartesian position from the surface geometry if the icon is near the surface, // otherwise draw it from the globe. Position pos = icon.getPosition(); Vec4 iconPoint = null; if (pos.getElevation() < dc.getGlobe().getMaxElevation()) iconPoint = dc.getSurfaceGeometry().getSurfacePoint(icon.getPosition()); if (iconPoint == null) iconPoint = dc.getGlobe().computePointFromPosition(icon.getPosition()); // The icons aren't drawn here, but added to the ordered queue to be drawn back-to-front. double eyeDistance = icon.isAlwaysOnTop() ? 0 : dc.getView().getEyePoint().distanceTo3(iconPoint); dc.addOrderedRenderable(new OrderedIcon(icon, iconPoint, eyeDistance)); if (icon.isShowToolTip()) this.addToolTip(dc, icon, iconPoint); } } private void drawOne(DrawContext dc, WWIcon icon, Vec4 iconPoint) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (dc.getVisibleSector() == null) return; SectorGeometryList geos = dc.getSurfaceGeometry(); //noinspection RedundantIfStatement if (geos == null) return; if (!icon.isVisible()) return; if (iconPoint == null) { Angle lat = icon.getPosition().getLatitude(); Angle lon = icon.getPosition().getLongitude(); if (!dc.getVisibleSector().contains(lat, lon)) return; iconPoint = dc.getSurfaceGeometry().getSurfacePoint(lat, lon, icon.getPosition().getElevation()); if (iconPoint == null) return; } if (!dc.getView().getFrustumInModelCoordinates().contains(iconPoint)) return; double horizon = dc.getView().computeHorizonDistance(); double eyeDistance = icon.isAlwaysOnTop() ? 0 : dc.getView().getEyePoint().distanceTo3(iconPoint); if (eyeDistance > horizon) return; // The icon isn't drawn here, but added to the ordered queue to be drawn back-to-front. dc.addOrderedRenderable(new OrderedIcon(icon, iconPoint, eyeDistance)); if (icon.isShowToolTip()) this.addToolTip(dc, icon, iconPoint); } private void addToolTip(DrawContext dc, WWIcon icon, Vec4 iconPoint) { if (icon.getToolTipFont() == null && icon.getToolTipText() == null) return; final Vec4 screenPoint = dc.getView().project(iconPoint); if (screenPoint == null) return; OrderedText tip = new OrderedText(icon.getToolTipText(), icon.getToolTipFont(), screenPoint, icon.getToolTipTextColor(), 0d); dc.addOrderedRenderable(tip); } private class OrderedText implements OrderedRenderable { Font font; String text; Vec4 point; double eyeDistance; java.awt.Point pickPoint; Layer layer; java.awt.Color color; OrderedText(String text, Font font, Vec4 point, java.awt.Color color, double eyeDistance) { this.text = text; this.font = font; this.point = point; this.eyeDistance = eyeDistance; this.color = color; } OrderedText(String text, Font font, Vec4 point, java.awt.Point pickPoint, Layer layer, double eyeDistance) { this.text = text; this.font = font; this.point = point; this.eyeDistance = eyeDistance; this.pickPoint = pickPoint; this.layer = layer; } public double getDistanceFromEye() { return this.eyeDistance; } public void render(DrawContext dc) { ToolTipRenderer tr = IconRenderer.this.toolTipRenderers.get(this.font); if (tr == null) { if (this.font != null) { TextRenderer textRenderer = new TextRenderer(this.font, true, true); textRenderer.setUseVertexArrays(false); tr = new ToolTipRenderer(textRenderer); } else { tr = new ToolTipRenderer(); } IconRenderer.this.toolTipRenderers.put(this.font, tr); } Rectangle vp = dc.getView().getViewport(); tr.setForeground(this.color); tr.setUseSystemLookAndFeel(this.color == null); tr.beginRendering(vp.width, vp.height); tr.draw(this.text, (int) point.x, (int) point.y); tr.endRendering(); } public void pick(DrawContext dc, java.awt.Point pickPoint) { } } private class OrderedIcon implements OrderedRenderable, Locatable { WWIcon icon; Vec4 point; double eyeDistance; java.awt.Point pickPoint; Layer layer; OrderedIcon(WWIcon icon, Vec4 point, double eyeDistance) { this.icon = icon; this.point = point; this.eyeDistance = eyeDistance; } OrderedIcon(WWIcon icon, Vec4 point, java.awt.Point pickPoint, Layer layer, double eyeDistance) { this.icon = icon; this.point = point; this.eyeDistance = eyeDistance; this.pickPoint = pickPoint; this.layer = layer; } public double getDistanceFromEye() { return this.eyeDistance; } public Position getPosition() { return this.icon.getPosition(); } public void render(DrawContext dc) { IconRenderer.this.beginDrawIcons(dc); try { IconRenderer.this.drawIcon(dc, this); // Draw as many as we can in a batch to save ogl state switching. while (dc.getOrderedRenderables().peek() instanceof OrderedIcon) { OrderedIcon oi = (OrderedIcon) dc.getOrderedRenderables().poll(); IconRenderer.this.drawIcon(dc, oi); } } catch (WWRuntimeException e) { Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingIcon", e); } catch (Exception e) { Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingIcon", e); } finally { IconRenderer.this.endDrawIcons(dc); } } public void pick(DrawContext dc, java.awt.Point pickPoint) { IconRenderer.this.pickSupport.clearPickList(); IconRenderer.this.beginDrawIcons(dc); try { IconRenderer.this.drawIcon(dc, this); // TODO: Restore the batching below, but ensure that icons are individually selectable // // Draw as many as we can in a batch to save ogl state switching. // while (dc.getOrderedRenderables().peek() instanceof OrderedIcon) // { // IconRenderer.this.drawIcon(dc, (OrderedIcon) dc.getOrderedRenderables().poll()); // } } catch (WWRuntimeException e) { Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingIcon", e); } catch (Exception e) { Logging.logger().log(Level.SEVERE, "generic.ExceptionWhilePickingIcon", e); } finally { IconRenderer.this.endDrawIcons(dc); IconRenderer.this.pickSupport.resolvePick(dc, pickPoint, layer); IconRenderer.this.pickSupport.clearPickList(); // to ensure entries can be garbage collected } } } private void beginDrawIcons(DrawContext dc) { GL gl = dc.getGL(); int attributeMask = GL.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func | GL.GL_TRANSFORM_BIT // for modelview and perspective | GL.GL_VIEWPORT_BIT // for depth range | GL.GL_CURRENT_BIT // for current color | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend | GL.GL_TEXTURE_BIT // for texture env | GL.GL_DEPTH_BUFFER_BIT // for depth func | GL.GL_ENABLE_BIT; // for enable/disable changes gl.glPushAttrib(attributeMask); // Apply the depth buffer but don't change it. gl.glEnable(GL.GL_DEPTH_TEST); gl.glDepthMask(false); // Suppress any fully transparent image pixels gl.glEnable(GL.GL_ALPHA_TEST); gl.glAlphaFunc(GL.GL_GREATER, 0.001f); // Load a parallel projection with dimensions (viewportWidth, viewportHeight) int[] viewport = new int[4]; gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glOrtho(0d, viewport[2], 0d, viewport[3], -1d, 1d); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPushMatrix(); if (dc.isPickingMode()) { this.pickSupport.beginPicking(dc); // Set up to replace the non-transparent texture colors with the single pick color. gl.glEnable(GL.GL_TEXTURE_2D); gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE); gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS); gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_REPLACE); } else { gl.glEnable(GL.GL_TEXTURE_2D); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA); } } private void endDrawIcons(DrawContext dc) { if (dc.isPickingMode()) this.pickSupport.endPicking(dc); GL gl = dc.getGL(); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPopMatrix(); gl.glPopAttrib(); } private Vec4 drawIcon(DrawContext dc, OrderedIcon uIcon) { if (uIcon.point == null) { String msg = Logging.getMessage("nullValue.PointIsNull"); Logging.logger().severe(msg); return null; } WWIcon icon = uIcon.icon; final Vec4 screenPoint = dc.getView().project(uIcon.point); if (screenPoint == null) return null; Texture iconTexture = dc.getTextureCache().get(icon.getImageSource()); if (iconTexture == null) iconTexture = this.initializeTexture(dc, icon.getImageSource()); double pedestalScale; double pedestalSpacing; Texture pedestalTexture = null; if (pedestal != null) { pedestalScale = this.pedestal.getScale(); pedestalSpacing = pedestal.getSpacingPixels(); pedestalTexture = dc.getTextureCache().get(pedestal.getPath()); // TODO: copy 'n paste bug? if (pedestalTexture == null) pedestalTexture = this.initializeTexture(dc, pedestal.getImageSource()); } else { pedestalScale = 0d; pedestalSpacing = 0d; } javax.media.opengl.GL gl = dc.getGL(); this.setDepthFunc(dc, uIcon, screenPoint); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); Dimension size = icon.getSize(); double width = size != null ? size.getWidth() : iconTexture.getWidth(); double height = size != null ? size.getHeight() : iconTexture.getHeight(); gl.glTranslated(screenPoint.x - width / 2, screenPoint.y + (pedestalScale * height) + pedestalSpacing, 0d); if (icon.isHighlighted()) { double heightDelta = this.pedestal != null ? 0 : height / 2; // expand only above the pedestal gl.glTranslated(width / 2, heightDelta, 0); gl.glScaled(icon.getHighlightScale(), icon.getHighlightScale(), icon.getHighlightScale()); gl.glTranslated(-width / 2, -heightDelta, 0); } if (dc.isPickingMode()) { java.awt.Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); this.pickSupport.addPickableObject(colorCode, icon, uIcon.getPosition(), false); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); } if (icon.getBackgroundImage() != null) this.applyBackground(dc, icon, screenPoint, width, height, pedestalSpacing, pedestalScale); iconTexture.bind(); TextureCoords texCoords = iconTexture.getImageTexCoords(); gl.glScaled(width, height, 1d); dc.drawUnitQuad(texCoords); if (pedestalTexture != null) { gl.glLoadIdentity(); gl.glTranslated(screenPoint.x - (pedestalScale * (width / 2)), screenPoint.y, 0d); gl.glScaled(width * pedestalScale, height * pedestalScale, 1d); pedestalTexture.bind(); texCoords = pedestalTexture.getImageTexCoords(); dc.drawUnitQuad(texCoords); } return screenPoint; } private void applyBackground(DrawContext dc, WWIcon icon, Vec4 screenPoint, double width, double height, double pedestalSpacing, double pedestalScale) { javax.media.opengl.GL gl = dc.getGL(); Object backgroundImage; Texture backgroundTexture; double backgroundScale; backgroundImage = icon.getBackgroundImage(); backgroundScale = icon.getBackgroundScale(); backgroundTexture = dc.getTextureCache().get(backgroundImage); if (backgroundTexture == null) backgroundTexture = this.initializeTexture(dc, backgroundImage); if (backgroundTexture != null) { backgroundTexture.bind(); TextureCoords texCoords = backgroundTexture.getImageTexCoords(); gl.glPushMatrix(); gl.glLoadIdentity(); double bgwidth = backgroundScale * width; double bgheight = backgroundScale * height; // Offset the background for the highlighted scale. //if (icon.isHighlighted()) //{ // gl.glTranslated(0d, height * (icon.getHighlightScale() - 1) / 2, 0d); //} // Offset the background for the pedestal height. gl.glTranslated(0d, (pedestalScale * height) + pedestalSpacing, 0d); // Place the background centered behind the icon. gl.glTranslated(screenPoint.x - bgwidth / 2, screenPoint.y - (bgheight - height) / 2, 0d); // Scale to the background image dimension. gl.glScaled(bgwidth, bgheight, 1d); dc.drawUnitQuad(texCoords); gl.glPopMatrix(); } } private void setDepthFunc(DrawContext dc, OrderedIcon uIcon, Vec4 screenPoint) { GL gl = dc.getGL(); if (uIcon.icon.isAlwaysOnTop()) { gl.glDepthFunc(GL.GL_ALWAYS); return; } 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); } } private Texture initializeTexture(DrawContext dc, Object imageSource) { try { Texture iconTexture = null; if (imageSource instanceof String) { String path = (String) imageSource; java.io.InputStream iconStream = this.getClass().getResourceAsStream("/" + path); if (iconStream == null) { java.io.File iconFile = new java.io.File(path); if (iconFile.exists()) { iconStream = new java.io.FileInputStream(iconFile); } } iconTexture = TextureIO.newTexture(iconStream, true, null); } else if (imageSource instanceof BufferedImage) { iconTexture = TextureIO.newTexture((BufferedImage) imageSource, true); } else { // TODO: Log case of unknown image-source type. } if (iconTexture == null) { // TODO: Log case. return null; } // Icons with the same path are assumed to be identical textures, so key the texture id off the path. dc.getTextureCache().put(imageSource, iconTexture); iconTexture.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 iconTexture; } catch (java.io.IOException e) { String msg = Logging.getMessage("generic.IOExceptionDuringTextureInitialization"); Logging.logger().log(Level.SEVERE, msg, e); throw new WWRuntimeException(msg, e); } } @Override public String toString() { return Logging.getMessage("layers.IconLayer.Name"); } }