/* Copyright (C) 2001, 2007 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.layers; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.render.OrderedRenderable; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.exception.WWRuntimeException; import javax.media.opengl.GL; import java.awt.*; import java.io.InputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import com.sun.opengl.util.texture.Texture; import com.sun.opengl.util.texture.TextureCoords; import com.sun.opengl.util.texture.TextureIO; /** * Renders a crosshair icon in the viewport center or at a specified location. * * @author Patrick Murris * @version $Id: CrosshairLayer.java 5178 2008-04-25 21:51:20Z patrickmurris $ */ public class CrosshairLayer extends AbstractLayer { /** * On window resize, scales the crosshair icon to occupy a constant relative size of the viewport. */ public final static String RESIZE_STRETCH = "gov.nasa.worldwind.CrosshairLayer.ResizeStretch"; /** * On window resize, scales the crosshair icon to occupy a constant relative size of the viewport, but not larger than * the icon's inherent size scaled by the layer's icon scale factor. */ public final static String RESIZE_SHRINK_ONLY = "gov.nasa.worldwind.CrosshairLayer.ResizeShrinkOnly"; /** * Does not modify the crosshair icon size when the window changes size. */ public final static String RESIZE_KEEP_FIXED_SIZE = "gov.nasa.worldwind.CrosshairLayer.ResizeKeepFixedSize"; private String iconFilePath = "images/32x32-crosshair-simple.png"; // TODO: make configurable private double toViewportScale = 1d; // TODO: make configurable private double iconScale = 1d; private String resizeBehavior = RESIZE_SHRINK_ONLY; private int iconWidth; private int iconHeight; private Vec4 locationCenter = null; // Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things. private OrderedIcon orderedImage = new OrderedIcon(); private class OrderedIcon implements OrderedRenderable { public double getDistanceFromEye() { return 0; } public void pick(DrawContext dc, Point pickPoint) { // Not implemented } public void render(DrawContext dc) { CrosshairLayer.this.draw(dc); } } public CrosshairLayer() { this.setOpacity(0.8); // TODO: make configurable } public CrosshairLayer(String iconFilePath) { this.setIconFilePath(iconFilePath); this.setOpacity(0.8); // TODO: make configurable } /** * Returns the layer's current icon file path. * * @return the icon file path */ public String getIconFilePath() { return iconFilePath; } /** * Sets the crosshair icon's image location. The layer first searches for this location in the current Java classpath. * If not found then the specified path is assumed to refer to the local file system. found there then the * * @param iconFilePath the path to the icon's image file */ public void setIconFilePath(String iconFilePath) { if (iconFilePath == null) { String message = Logging.getMessage("nullValue.IconFilePath"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.iconFilePath = iconFilePath; } /** * Returns the layer's compass-to-viewport scale factor. * * @return the crosshair-to-viewport scale factor */ public double getToViewportScale() { return toViewportScale; } /** * Sets the scale factor applied to the viewport size to determine the displayed size of the crosshair icon. This * scale factor is used only when the layer's resize behavior is {@link #RESIZE_STRETCH} or {@link * #RESIZE_SHRINK_ONLY}. The icon's width is adjusted to occupy the proportion of the viewport's width indicated by * this factor. The icon's height is adjusted to maintain the crosshair image's native aspect ratio. * * @param toViewportScale the compass to viewport scale factor */ public void setToViewportScale(double toViewportScale) { this.toViewportScale = toViewportScale; } /** * Returns the icon scale factor. See {@link #setIconScale(double)} for a description of the scale factor. * * @return the current icon scale */ public double getIconScale() { return iconScale; } /** * Sets the scale factor defining the displayed size of the crosshair icon relative to the icon's width and height in * its image file. Values greater than 1 magify the image, values less than one minify it. If the layer's resize * behavior is other than {@link #RESIZE_KEEP_FIXED_SIZE}, the icon's displayed sized is further affected by the * value specified by {@link #setToViewportScale(double)} and the current viewport size. * * @param iconScale the icon scale factor */ public void setIconScale(double iconScale) { this.iconScale = iconScale; } /** * Returns the crosshair icon's resize behavior. * * @return the icon's resize behavior */ public String getResizeBehavior() { return resizeBehavior; } /** * Sets the behavior the layer uses to size the crosshair icon when the viewport size changes, typically when the * World Wind window is resized. If the value is {@link #RESIZE_KEEP_FIXED_SIZE}, the icon size is kept to the size * specified in its image file scaled by the layer's current icon scale. If the value is {@link #RESIZE_STRETCH}, * the icon is resized to have a constant size relative to the current viewport size. If the viewport shrinks the * icon size decreases; if it expands then the icon file enlarges. The relative size is determined by the current * crosshair-to-viewport scale and by the icon's image file size scaled by the current icon scale. If the value is * {@link #RESIZE_SHRINK_ONLY} (the default), icon sizing behaves as for {@link #RESIZE_STRETCH} but the icon will * not grow larger than the size specified in its image file scaled by the current icon scale. * * @param resizeBehavior the desired resize behavior */ public void setResizeBehavior(String resizeBehavior) { this.resizeBehavior = resizeBehavior; } /** * Get the crosshair location inside the viewport. If this location is null, the crosshair is drawn in the * viewport center. * @return the crosshair location inside the viewport. */ public Vec4 getLocationCenter() { return locationCenter; } /** * Set the crosshair location inside the viewport. If this location is null, the crosshair will be drawn in the * viewport center. * @param locationCenter the crosshair location inside the viewport. */ public void setLocationCenter(Vec4 locationCenter) { this.locationCenter = locationCenter; } protected void doRender(DrawContext dc) { dc.addOrderedRenderable(this.orderedImage); } private void draw(DrawContext dc) { if (this.iconFilePath == null) return; GL gl = dc.getGL(); boolean attribsPushed = false; boolean modelviewPushed = false; boolean projectionPushed = false; try { gl.glPushAttrib(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT | GL.GL_ENABLE_BIT | GL.GL_TEXTURE_BIT | GL.GL_TRANSFORM_BIT | GL.GL_VIEWPORT_BIT | GL.GL_CURRENT_BIT); attribsPushed = true; Texture iconTexture = dc.getTextureCache().get(this); if (iconTexture == null) { this.initializeTexture(dc); iconTexture = dc.getTextureCache().get(this); if (iconTexture == null) { // TODO: log warning return; } } gl.glEnable(GL.GL_TEXTURE_2D); iconTexture.bind(); gl.glColor4d(1d, 1d, 1d, this.getOpacity()); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glDisable(GL.GL_DEPTH_TEST); double width = this.getScaledIconWidth(); double height = this.getScaledIconHeight(); // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight) // into the GL projection matrix. java.awt.Rectangle viewport = dc.getView().getViewport(); gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION); gl.glPushMatrix(); projectionPushed = true; gl.glLoadIdentity(); double maxwh = width > height ? width : height; gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); modelviewPushed = true; gl.glLoadIdentity(); double scale = this.computeScale(viewport); Vec4 locationSW = this.computeLocation(viewport, scale); gl.glTranslated((int)locationSW.x, (int)locationSW.y, (int)locationSW.z); gl.glScaled(scale, scale, 1); TextureCoords texCoords = iconTexture.getImageTexCoords(); gl.glScaled(width, height, 1d); dc.drawUnitQuad(texCoords); } finally { if (projectionPushed) { gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); } if (modelviewPushed) { gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); } if (attribsPushed) gl.glPopAttrib(); } } private double computeScale(Rectangle viewport) { if (this.resizeBehavior.equals(RESIZE_SHRINK_ONLY)) { return Math.min(1d, (this.toViewportScale) * viewport.width / this.getScaledIconWidth()); } else if (this.resizeBehavior.equals(RESIZE_STRETCH)) { return (this.toViewportScale) * viewport.width / this.getScaledIconWidth(); } else if (this.resizeBehavior.equals(RESIZE_KEEP_FIXED_SIZE)) { return 1d; } else { return 1d; } } private double getScaledIconWidth() { return this.iconWidth * this.iconScale; } private double getScaledIconHeight() { return this.iconHeight * this.iconScale; } private Vec4 computeLocation(Rectangle viewport, double scale) { double width = this.getScaledIconWidth(); double height = this.getScaledIconHeight(); double scaledWidth = scale * width; double scaledHeight = scale * height; double x; double y; if (this.locationCenter != null) { x = this.locationCenter.x - scaledWidth / 2; y = this.locationCenter.y - scaledHeight / 2; } else // viewport center { x = viewport.getWidth() / 2 - scaledWidth / 2; y = viewport.getHeight() / 2 - scaledHeight / 2; } return new Vec4(x, y, 0); } private void initializeTexture(DrawContext dc) { Texture iconTexture = dc.getTextureCache().get(this); if (iconTexture != null) return; try { InputStream iconStream = this.getClass().getResourceAsStream("/" + this.iconFilePath); if (iconStream == null) { File iconFile = new File(this.iconFilePath); if (iconFile.exists()) { iconStream = new FileInputStream(iconFile); } } iconTexture = TextureIO.newTexture(iconStream, false, null); iconTexture.bind(); this.iconWidth = iconTexture.getWidth(); this.iconHeight = iconTexture.getHeight(); dc.getTextureCache().put(this, iconTexture); } catch (IOException e) { String msg = Logging.getMessage("layers.IOExceptionDuringInitialization"); Logging.logger().severe(msg); throw new WWRuntimeException(msg, e); } 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); // Enable texture anisotropy int[] maxAnisotropy = new int[1]; gl.glGetIntegerv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy, 0); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy[0]); } @Override public String toString() { return "Crosshair"; //return Logging.getMessage("layers.CrosshairLayer.Name"); } }