All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwindx.examples.analytics.AnalyticSurface Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwindx.examples.analytics;

import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import javax.media.opengl.*;
import java.awt.*;
import java.nio.*;
import java.util.*;
import java.util.List;

/**
 * AnalyticSurface represents a connected grid of geographic locations, covering a specified {@link Sector} at a
 * specified base altitude in meters. The number of grid locations is defined by the AnalyticSurface's dimensions. The
 * default dimensions are (10, 10). Callers specify the dimensions by using one of the constructors
 * accepting width and height, or by invoking {@link #setDimensions(int, int)}. Each grid
 * point has the following set of attributes: 
  • Scalar value : the grid point's height relative to the surface's * base altitude, both in meters
  • Color : the grid point's RGBA color components
Callers specify the * attributes at each grid point by invoking {@link #setValues(Iterable)} with an {@link Iterable} of {@link * GridPointAttributes}. Grid points are assigned attributes from this iterable staring at the upper left hand corner, * and proceeding in row-first order across the grid. The iterable should contain at least width * height * values, where width and height are the AnalyticSurface's grid dimensions. If the caller does not specify any * GridPointAttributes, or the caller specified iterable contains too few values, the unassigned grid points are given * default attributes: the default scalar value is 0, and the default color is {@link java.awt.Color#BLACK}. *

*

Surface Altitude

*

* AnalyticSurface's altitude can vary at each grid point. The altitude of each grid point depends on four properties: * the altitude mode, the surface altitude, the vertical scale, and the scalar value from GridPointAttributes. The * following table outlines how the altitude at each grid point is computed for each altitude mode: *

*

*
Altitude ModeGrid Point Altitude
WorldWind.ABSOLUTE * (default)surface altitude + (vertical scale * scalar value from GridPointAttributes)
WorldWind.RELATIVE_TO_GROUNDterrain height at grid point + surface altitude + (vertical scale * * scalar value from GridPointAttributes)
WorldWind.CLAMP_TO_GROUNDterrain height at grid * point
*

* Note that when the altitude mode is WorldWind.CLAMP_TO_GROUND the surface altitude, vertical scale, and the scalar * value from GridPointAttributes are ignored. In this altitude mode only the Sector, dimensions, and color from * GridPointAttributes are used. * * @author dcollins * @version $Id: AnalyticSurface.java 1847 2014-02-18 00:32:16Z dcollins $ */ public class AnalyticSurface implements Renderable, PreRenderable { /** GridPointAttributes defines the properties associated with a single grid point of an AnalyticSurface. */ public interface GridPointAttributes { /** * Returns the scalar value associated with a grid point. By default, AnalyticSurface interprets this value as * the grid point's height relative to the AnalyticSurface's base altitude, both in meters. * * @return the grid point's scalar value. */ double getValue(); /** * Returns the {@link java.awt.Color} associated with a grid point. By default, AnalyticSurface interprets this * Color as the RGBA components of a grid point's RGBA color. * * @return the grid point's RGB color components. */ java.awt.Color getColor(); } protected static final double DEFAULT_ALTITUDE = 0d; /** The default altitude mode. */ protected static final int DEFAULT_ALTITUDE_MODE = WorldWind.ABSOLUTE; protected static final int DEFAULT_DIMENSION = 10; protected static final double DEFAULT_VALUE = 0d; protected static final Color DEFAULT_COLOR = Color.BLACK; protected static final GridPointAttributes DEFAULT_GRID_POINT_ATTRIBUTES = createGridPointAttributes( DEFAULT_VALUE, DEFAULT_COLOR); /** The time period between surface regeneration when altitude mode is relative-to-ground. */ protected static final long RELATIVE_TO_GROUND_REGEN_PERIOD = 2000; protected boolean visible = true; protected Sector sector; protected double altitude; protected int altitudeMode = DEFAULT_ALTITUDE_MODE; protected int width; protected int height; protected Iterable values; protected double verticalScale = 1d; protected AnalyticSurfaceAttributes surfaceAttributes = new AnalyticSurfaceAttributes(); protected Object pickObject; protected Layer clientLayer; // Runtime rendering state. protected double[] extremeValues; protected boolean expired = true; protected boolean updateFailed; protected Object globeStateKey; protected long regenTime; // Computed surface rendering properties. protected Position referencePos; protected Vec4 referencePoint; protected RenderInfo surfaceRenderInfo; protected AnalyticSurfaceObject clampToGroundSurface; protected AnalyticSurfaceObject shadowSurface; protected final PickSupport pickSupport = new PickSupport(); /** * Constructs a new AnalyticSurface with the specified {@link Sector}, base altitude in meters, grid dimensions, and * iterable of GridPointAttributes. The iterable should contain at least with * height non-null * GridPointAttributes. * * @param sector the Sector which defines the surface's geographic region. * @param altitude the base altitude to place the surface at, in meters. * @param width the surface grid width, in number of grid points. * @param height the surface grid height, in number of grid points. * @param iterable the attributes associated with each grid point. * * @throws IllegalArgumentException if the sector is null, if the width is less than 1, if the height is less than * 1, or if the iterable is null. */ public AnalyticSurface(Sector sector, double altitude, int width, int height, Iterable iterable) { if (sector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (width <= 0) { String message = Logging.getMessage("Geom.WidthInvalid", width); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height <= 0) { String message = Logging.getMessage("Geom.HeightInvalid", width); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.sector = sector; this.altitude = altitude; this.width = width; this.height = height; this.values = iterable; this.setExpired(true); } /** * Constructs a new AnalyticSurface with the specified {@link Sector}, base altitude in meters, and grid dimensions. * The new AnalyticSurface has the default {@link GridPointAttributes}. * * @param sector the Sector which defines the surface's geographic region. * @param altitude the base altitude to place the surface at, in meters. * @param width the surface grid width, in number of grid points. * @param height the surface grid height, in number of grid points. * * @throws IllegalArgumentException if the sector is null, if the width is less than 1, or if the height is less * than 1. */ public AnalyticSurface(Sector sector, double altitude, int width, int height) { this(sector, altitude, width, height, createDefaultValues(width * height)); } /** * Constructs a new AnalyticSurface with the specified {@link Sector} and base altitude in meters. The new * AnalyticSurface has default dimensions of (10, 10), and default {@link GridPointAttributes}. * * @param sector the Sector which defines the surface's geographic region. * @param altitude the base altitude to place the surface at, in meters. * * @throws IllegalArgumentException if the sector is null. */ public AnalyticSurface(Sector sector, double altitude) { this(sector, altitude, DEFAULT_DIMENSION, DEFAULT_DIMENSION); } /** * Constructs a new AnalyticSurface with the specified grid dimensions. The new AnalyticSurface is has the default * Sector {@link Sector#EMPTY_SECTOR}, the default altitude of 0 meters, and default {@link GridPointAttributes}. * * @param width the surface grid width, in number of grid points. * @param height the surface grid height, in number of grid points. * * @throws IllegalArgumentException if the sector is null. */ public AnalyticSurface(int width, int height) { this(Sector.EMPTY_SECTOR, DEFAULT_ALTITUDE, width, height); } /** * Constructs a new AnalyticSurface with the default Sector {@link Sector#EMPTY_SECTOR}, the default altitude of 0 * meters, default dimensions of (10, 10), and default {@link GridPointAttributes}. */ public AnalyticSurface() { this(DEFAULT_DIMENSION, DEFAULT_DIMENSION); } /** * Returns true if the surface is visible in the scene, and false otherwise. * * @return true if the surface is visible in the scene, and false otherwise */ public boolean isVisible() { return this.visible; } /** * Sets whether or not the surface is visible in the scene. * * @param visible true to make the surface visible, and false to make it hidden. */ public void setVisible(boolean visible) { this.visible = visible; } /** * Returns the {@link Sector} defining the geographic boundary of this surface. * * @return this surface's geographic boundary, as a Sector. */ public Sector getSector() { return this.sector; } /** * Sets this surface's geographic boundary, as a {@link Sector}. * * @param sector this surface's new geographic boundary, as a Sector. * * @throws IllegalArgumentException if the sector is null. */ public void setSector(Sector sector) { if (sector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.sector = sector; this.setExpired(true); } /** * Returns this surface's base altitude, in meters. * * @return this surface's base altitude, in meters. * * @see #setAltitude(double) */ public double getAltitude() { return altitude; } /** * Sets this surface's base altitude, in meters. See the AnalyticSurface class documentation for information on how * the altitude is interpreted. * * @param altitude the new base altitude, in meters. */ public void setAltitude(double altitude) { this.altitude = altitude; this.setExpired(true); } /** * Returns the surface's altitude mode, one of {@link gov.nasa.worldwind.WorldWind#CLAMP_TO_GROUND}, {@link * gov.nasa.worldwind.WorldWind#RELATIVE_TO_GROUND}, or {@link gov.nasa.worldwind.WorldWind#ABSOLUTE}. * * @return the surface's altitude mode. * * @see #setAltitudeMode(int) */ public int getAltitudeMode() { return altitudeMode; } /** * Specifies the surface's altitude mode, one of {@link gov.nasa.worldwind.WorldWind#CLAMP_TO_GROUND}, {@link * gov.nasa.worldwind.WorldWind#RELATIVE_TO_GROUND}, or {@link gov.nasa.worldwind.WorldWind#ABSOLUTE}. The altitude * mode {@link gov.nasa.worldwind.WorldWind#CONSTANT} is not supported and {@link * gov.nasa.worldwind.WorldWind#ABSOLUTE} is used if specified. See the AnalyticSurface class documentation for * information on how the altitude mode is interpreted. * * @param altitudeMode the surface's altitude mode. */ public void setAltitudeMode(int altitudeMode) { this.altitudeMode = altitudeMode; this.setExpired(true); } /** * Returns the number of horizontal and vertical points composing this surface as an array with two values. The * value at index 0 indicates the grid width, and the value at index 1 indicates the grid height. * * @return the dimensions of this surface's grid. */ public int[] getDimensions() { return new int[] {this.width, this.height}; } /** * Sets the number of horizontal and vertical points composing this surface. * * @param width the new grid width. * @param height the new grid height. * * @throws IllegalArgumentException if either width or heigth are less than 1. */ public void setDimensions(int width, int height) { if (width <= 0) { String message = Logging.getMessage("Geom.WidthInvalid", width); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height <= 0) { String message = Logging.getMessage("Geom.HeightInvalid", height); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.width = width; this.height = height; this.setExpired(true); } /** * Returns the surface's iterable of {@link GridPointAttributes}. See {@link #setValues(Iterable)} for details on * how this iterable is interpreted by AnalyticSurface. * * @return this surface's GridPointAttributes. */ public Iterable getValues() { return this.values; } /** * Sets this surface's iterable of {@link GridPointAttributes}. Grid points are assigned attributes from this * iterable staring at the upper left hand corner, and proceeding in row-first order across the grid. The iterable * should contain at least width * height values, where width and height are the AnalyticSurface's grid * dimensions. If the iterable contains too few values, the unassigned grid points are given default attributes: the * default scalar value is 0, and the default color is {@link java.awt.Color#BLACK}. * * @param iterable the new grid point attributes. * * @throws IllegalArgumentException if the iterable is null. */ public void setValues(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.values = iterable; this.extremeValues = computeExtremeValues(iterable); this.setExpired(true); } /** * Returns the scale applied to the value at each grid point. * * @return the surface's vertical scale coefficient. */ public double getVerticalScale() { return this.verticalScale; } /** * Sets the scale applied to the value at each grid point. Before rendering, this value is applied to each grid * points scalar value, thus increasing or decreasing it's height relative to the surface's base altitude, both in * meters. * * @param scale the surface's vertical scale coefficient. */ public void setVerticalScale(double scale) { this.verticalScale = scale; this.setExpired(true); } /** * Returns a copy of the rendering attributes associated with this surface. Modifying the contents of the returned * reference has no effect on this surface. In order to make an attribute change take effect, invoke {@link * #setSurfaceAttributes(AnalyticSurfaceAttributes)} with the modified attributes. * * @return a copy of this surface's rendering attributes. */ public AnalyticSurfaceAttributes getSurfaceAttributes() { return this.surfaceAttributes.copy(); } /** * Sets the rendering attributes associated with this surface. The caller cannot assume that modifying the attribute * reference after calling setSurfaceAttributes() will have any effect, as the implementation may defensively copy * the attribute reference. In order to make an attribute change take effect, invoke * setSurfaceAttributes(AnalyticSurfaceAttributes) again with the modified attributes. * * @param attributes this surface's new rendering attributes. * * @throws IllegalArgumentException if attributes is null. */ public void setSurfaceAttributes(AnalyticSurfaceAttributes attributes) { if (attributes == null) { String message = Logging.getMessage("nullValue.AttributesIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.surfaceAttributes = attributes.copy(); this.setExpired(true); } /** * Returns the object which is associated with this surface during picking. A null value is permitted and indicates * that the surface itself will be the object returned during picking. * * @return this surface's pick object. */ public Object getPickObject() { return this.pickObject; } /** * Sets the object associated with this surface during picking. A null value is permitted and indicates that the * surface itself will be the object returned during picking. * * @param pickObject the object to associated with this surface during picking. A null value is permitted and * indicates that the surface itself will be the object returned during picking. */ public void setPickObject(Object pickObject) { this.pickObject = pickObject; } /** * Returns the layer associated with this surface during picking. * * @return this surface's pick layer. */ public Layer getClientLayer() { return this.clientLayer; } /** * Sets the layer associated with this surface during picking. A null value is permitted, and indicates that no * layer is associated with this surface. * * @param layer this surface's pick layer. */ public void setClientLayer(Layer layer) { this.clientLayer = layer; } /** * {@inheritDoc} * * @param dc */ public void preRender(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.isVisible()) return; if (!this.intersectsFrustum(dc)) return; if (this.isExpired(dc)) this.update(dc); if (this.isExpired(dc)) return; this.preRenderSurfaceObjects(dc); } /** * {@inheritDoc} * * @param dc the DrawContext to be used */ public void render(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.isVisible()) return; if (!intersectsFrustum(dc)) return; if (this.isExpired(dc)) this.update(dc); if (this.isExpired(dc)) return; this.makeOrderedRenderable(dc); this.drawSurfaceObjects(dc); } /** * Returns this surface's extent in model coordinates. * * @param dc the current DrawContext. * * @return this surface's extent in the specified DrawContext. * * @throws IllegalArgumentException if the DrawContext is null. */ public Extent getExtent(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND) { return Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), this.getSector()); } else { double minAltitude = this.getAltitude(); double maxAltitude = this.getAltitude(); double[] minAndMaxElevations = dc.getGlobe().getMinAndMaxElevations(this.getSector()); if (this.extremeValues != null) { minAltitude = this.getAltitude() + this.getVerticalScale() * this.extremeValues[0]; maxAltitude = this.getAltitude() + this.getVerticalScale() * this.extremeValues[1]; } if (minAndMaxElevations != null) { if (this.getAltitudeMode() == WorldWind.RELATIVE_TO_GROUND) { minAltitude -= minAndMaxElevations[0]; maxAltitude += minAndMaxElevations[1]; } if (minAltitude > minAndMaxElevations[0]) minAltitude = minAndMaxElevations[0]; if (maxAltitude < minAndMaxElevations[1]) maxAltitude = minAndMaxElevations[1]; } return Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), this.getSector(), minAltitude, maxAltitude); } } /** * Test if this AnalyticSurface intersects the specified draw context's frustum. During picking mode, this tests * intersection against all of the draw context's pick frustums. During rendering mode, this tests intersection * against the draw context's viewing frustum. * * @param dc the current draw context. * * @return true if this AnalyticSurface intersects the draw context's frustum; false otherwise. */ protected boolean intersectsFrustum(DrawContext dc) { // A null extent indicates an object which has no location. Extent extent = this.getExtent(dc); if (extent == null) return false; // Test this object's extent against the pick frustum list if (dc.isPickingMode()) return dc.getPickFrustums().intersectsAny(extent); // Test this object's extent against the viewing frustum. return dc.getView().getFrustumInModelCoordinates().intersects(extent); } //**************************************************************// //******************** Attribute Construction ****************// //**************************************************************// /** * Returns the minimum and maximum values in the specified iterable of {@link GridPointAttributes}. Values * equivalent to the specified missingDataSignal are ignored. This returns null if the iterable is * empty or contains only missing values. * * @param iterable the GridPointAttributes to search for the minimum and maximum value. * @param missingDataSignal the number indicating a specific value to ignore. * * @return an array containing the minimum value in index 0 and the maximum value in index 1, or null if the * iterable is empty or contains only missing values. * * @throws IllegalArgumentException if the iterable is null. */ public static double[] computeExtremeValues(Iterable iterable, double missingDataSignal) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } double minValue = Double.MAX_VALUE; double maxValue = -Double.MAX_VALUE; for (GridPointAttributes attr : iterable) { double value = attr.getValue(); if (Double.compare(value, missingDataSignal) == 0) continue; if (minValue > value) minValue = value; if (maxValue < value) maxValue = value; } if (minValue == Double.MAX_VALUE || minValue == -Double.MIN_VALUE) return null; return new double[] {minValue, maxValue}; } /** * Returns the minimum and maximum values in the specified iterable of {@link GridPointAttributes}. Values * equivalent to Double.NaN are ignored. This returns null if the buffer is empty or contains only NaN * values. * * @param iterable the GridPointAttributes to search for the minimum and maximum value. * * @return an array containing the minimum value in index 0 and the maximum value in index 1, or null if the * iterable is empty or contains only NaN values. * * @throws IllegalArgumentException if the iterable is null. */ public static double[] computeExtremeValues(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return computeExtremeValues(iterable, Double.NaN); } /** * Returns a new instance of {@link GridPointAttributes} with the specified value and color. * * @param value the new GridPointAttributes' value. * @param color the new GridPointAttributes' color. * * @return a new GridPointAttributes defined by the specified value and color. */ public static GridPointAttributes createGridPointAttributes(final double value, final java.awt.Color color) { return new AnalyticSurface.GridPointAttributes() { public double getValue() { return value; } public Color getColor() { return color; } }; } /** * Returns a new instance of {@link GridPointAttributes} with a Color computed from the specified value and value * range. The color's RGB components are computed by mapping value's relative position in the range [minValue, * maxValue] to the range of color hues [minHue, maxHue]. The color's Alpha component is * computed by mapping the values's relative position in the range [minValue, minValue + (maxValue - minValue) * / 10] to the range [0, 1]. This has the effect of interpolating hue and alpha based on the * grid point value. * * @param value the new GridPointAttributes' value. * @param minValue the minimum value. * @param maxValue the maximum value. * @param minHue the mimimum color hue, corresponding to the minimum value. * @param maxHue the maximum color hue, corresponding to the maximum value. * * @return a new GridPointAttributes defined by the specified value, value range, and color hue range. */ public static AnalyticSurface.GridPointAttributes createColorGradientAttributes(final double value, double minValue, double maxValue, double minHue, double maxHue) { double hueFactor = WWMath.computeInterpolationFactor(value, minValue, maxValue); Color color = Color.getHSBColor((float) WWMath.mixSmooth(hueFactor, minHue, maxHue), 1f, 1f); double opacity = WWMath.computeInterpolationFactor(value, minValue, minValue + (maxValue - minValue) * 0.1); Color rgbaColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (255 * opacity)); return createGridPointAttributes(value, rgbaColor); } /** * Returns a new iterable populated with the default {@link GridPointAttributes}. The default GridPointAttributes * have a value of 0, and the color {@link java.awt.Color#BLACK}. * * @param count the desired number of GridPointAttributes to return. * * @return an iterable containing count default GridPointAttributes. */ public static Iterable createDefaultValues(int count) { ArrayList list = new ArrayList(count); Collections.fill(list, DEFAULT_GRID_POINT_ATTRIBUTES); return list; } /** * Returns a new iterable populated with {@link GridPointAttributes} computed by invoking {@link * #createColorGradientAttributes(double, double, double, double, double)} for each double value in the speicfied * {@link BufferWrapper}. Values equivalent to the specified missingDataSignal are replaced with the * specified minValue. * * @param values the buffer of values. * @param missingDataSignal the number indicating a specific value to ignore. * @param minValue the minimum value. * @param maxValue the maximum value. * @param minHue the mimimum color hue, corresponding to the minimum value. * @param maxHue the maximum color hue, corresponding to the maximum value. * * @return an iiterable GridPointAttributes defined by the specified buffer of values. */ public static Iterable createColorGradientValues( BufferWrapper values, double missingDataSignal, double minValue, double maxValue, double minHue, double maxHue) { if (values == null) { String message = Logging.getMessage("nullValue.BufferIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } ArrayList attributesList = new ArrayList(); for (int i = 0; i < values.length(); i++) { double value = values.getDouble(i); if (Double.compare(value, missingDataSignal) == 0) value = minValue; attributesList.add(createColorGradientAttributes(value, minValue, maxValue, minHue, maxHue)); } return attributesList; } //**************************************************************// //******************** Surface Rendering *********************// //**************************************************************// protected void makeOrderedRenderable(DrawContext dc) { // Clamp-to-ground analytic surface is drawn entirely by surface objects prepared during // preRenderSurfaceObjects(). if (this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND) return; Extent extent = this.getExtent(dc); double eyeDistance = dc.getView().getEyePoint().distanceTo3(extent.getCenter()) - extent.getRadius(); if (eyeDistance < 1) eyeDistance = 1; dc.addOrderedRenderable(new OrderedSurface(this, eyeDistance)); } protected void drawOrderedRenderable(DrawContext dc) { this.beginDrawing(dc); try { this.doDrawOrderedRenderable(dc); } finally { this.endDrawing(dc); } } protected void doDrawOrderedRenderable(DrawContext dc) { this.bind(dc); // If the outline and interior will be drawn, then draw the outline color, but do not affect the depth // buffer. When the interior is drawn, it will draw on top of these colors, and the outline will be visible // behind the potentially transparent interior. if (this.surfaceAttributes.isDrawOutline() && this.surfaceAttributes.isDrawInterior()) { dc.getGL().glDepthMask(false); this.drawOutline(dc); dc.getGL().glDepthMask(true); } if (this.surfaceAttributes.isDrawInterior()) this.drawInterior(dc); if (this.surfaceAttributes.isDrawOutline()) this.drawOutline(dc); } protected void bind(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL.GL_FLOAT, 0, this.surfaceRenderInfo.cartesianVertexBuffer); if (!dc.isPickingMode()) { gl.glEnableClientState(GL2.GL_NORMAL_ARRAY); gl.glNormalPointer(GL.GL_FLOAT, 0, this.surfaceRenderInfo.cartesianNormalBuffer); gl.glEnableClientState(GL2.GL_COLOR_ARRAY); gl.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, this.surfaceRenderInfo.colorBuffer); } } protected void drawInterior(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. if (!dc.isPickingMode()) { // Bind the shapes vertex colors as the diffuse material parameter. gl.glEnable(GL2.GL_COLOR_MATERIAL); gl.glColorMaterial(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE); this.surfaceAttributes.getInteriorMaterial().apply(gl, GL2.GL_FRONT_AND_BACK, (float) this.surfaceAttributes.getInteriorOpacity()); } gl.glCullFace(GL.GL_FRONT); this.surfaceRenderInfo.drawInterior(dc); gl.glCullFace(GL.GL_BACK); this.surfaceRenderInfo.drawInterior(dc); } protected void drawOutline(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. if (!dc.isPickingMode()) { gl.glEnable(GL.GL_LINE_SMOOTH); // Unbind the shapes vertex colors as the diffuse material parameter. gl.glDisable(GL2.GL_LIGHTING); gl.glDisable(GL2.GL_COLOR_MATERIAL); // Set the outline color. Color color = this.surfaceAttributes.getOutlineMaterial().getDiffuse(); // Convert the floating point opacity from the range [0, 1] to the unsigned byte range [0, 255]. int alpha = (int) (255 * this.surfaceAttributes.getOutlineOpacity() + 0.5); gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(), (byte) alpha); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); } gl.glLineWidth((float) this.surfaceAttributes.getOutlineWidth()); this.surfaceRenderInfo.drawOutline(dc); if (!dc.isPickingMode()) { gl.glEnable(GL2.GL_LIGHTING); gl.glDisable(GL.GL_LINE_SMOOTH); gl.glDisable(GL2.GL_LINE_STIPPLE); gl.glEnableClientState(GL2.GL_COLOR_ARRAY); } } protected void beginDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glPushAttrib( GL2.GL_COLOR_BUFFER_BIT // for alpha test func and ref, blend func | GL2.GL_CURRENT_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_LINE_BIT // for line width | GL2.GL_POLYGON_BIT // for cull face | (!dc.isPickingMode() ? GL2.GL_LIGHTING_BIT : 0) // for lighting. | (!dc.isPickingMode() ? GL2.GL_TRANSFORM_BIT : 0)); // for normalize state. // Enable the alpha test. gl.glEnable(GL2.GL_ALPHA_TEST); gl.glAlphaFunc(GL2.GL_GREATER, 0.0f); gl.glEnable(GL.GL_CULL_FACE); if (dc.isPickingMode()) { Color color = dc.getUniquePickColor(); this.pickSupport.addPickableObject(color.getRGB(), (this.getPickObject() != null) ? this.getPickObject() : this, new Position(this.sector.getCentroid(), this.altitude), false); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); } else { // Enable blending in non-premultiplied color mode. Premultiplied colors don't work with GL fixed // functionality lighting. gl.glEnable(GL.GL_BLEND); OGLUtil.applyBlending(gl, false); // Enable lighting with GL_LIGHT1. gl.glDisable(GL2.GL_COLOR_MATERIAL); gl.glDisable(GL2.GL_LIGHT0); gl.glEnable(GL2.GL_LIGHTING); gl.glEnable(GL2.GL_LIGHT1); gl.glEnable(GL2.GL_NORMALIZE); // Configure the lighting model for two-sided smooth shading. gl.glLightModeli(GL2.GL_LIGHT_MODEL_LOCAL_VIEWER, GL.GL_TRUE); gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_TRUE); gl.glShadeModel(GL2.GL_SMOOTH); // Configure GL_LIGHT1 as a white light eminating from the viewer's eye point. OGLUtil.applyLightingDirectionalFromViewer(gl, GL2.GL_LIGHT1, new Vec4(1.0, 0.5, 1.0).normalize3()); } dc.getView().pushReferenceCenter(dc, this.referencePoint); } protected void endDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. dc.getView().popReferenceCenter(dc); gl.glPopAttrib(); // Restore default GL client vertex array state. gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); } //**************************************************************// //******************** Surface Construction ******************// //**************************************************************// protected boolean isExpired(DrawContext dc) { if (this.expired) return true; if (this.getAltitudeMode() == WorldWind.RELATIVE_TO_GROUND) { if (dc.getFrameTimeStamp() - this.regenTime > RELATIVE_TO_GROUND_REGEN_PERIOD) return true; } if (this.getAltitudeMode() == WorldWind.RELATIVE_TO_GROUND || this.getAltitudeMode() == WorldWind.ABSOLUTE) { Object gsk = dc.getGlobe().getStateKey(dc); if (this.globeStateKey != null ? !this.globeStateKey.equals(gsk) : gsk != null) return true; } return false; } protected void setExpired(boolean expired) { this.expired = expired; if (this.expired) { if (this.clampToGroundSurface != null) this.clampToGroundSurface.markAsModified(); if (this.shadowSurface != null) this.shadowSurface.markAsModified(); } } protected void update(DrawContext dc) { if (this.updateFailed) return; try { this.doUpdate(dc); this.setExpired(false); this.globeStateKey = dc.getGlobe().getStateKey(dc); this.regenTime = dc.getFrameTimeStamp(); } catch (Exception e) { String message = Logging.getMessage("generic.ExceptionWhileUpdating", this); Logging.logger().log(java.util.logging.Level.SEVERE, message, e); this.updateFailed = true; } } protected void doUpdate(DrawContext dc) { this.referencePos = new Position(this.sector.getCentroid(), this.altitude); this.referencePoint = dc.getGlobe().computePointFromPosition(this.referencePos); if (this.surfaceRenderInfo == null || this.surfaceRenderInfo.getGridWidth() != this.width || this.surfaceRenderInfo.getGridHeight() != this.height) { this.surfaceRenderInfo = new RenderInfo(this.width, this.height); } this.updateSurfacePoints(dc, this.surfaceRenderInfo); this.updateSurfaceNormals(this.surfaceRenderInfo); } protected void updateSurfacePoints(DrawContext dc, RenderInfo outRenderInfo) { Iterator iter = this.values.iterator(); double latStep = -this.sector.getDeltaLatDegrees() / (double) (this.height - 1); double lonStep = this.sector.getDeltaLonDegrees() / (double) (this.width - 1); double lat = this.sector.getMaxLatitude().degrees; for (int y = 0; y < this.height; y++) { double lon = this.sector.getMinLongitude().degrees; for (int x = 0; x < this.width; x++) { GridPointAttributes attr = iter.hasNext() ? iter.next() : null; this.updateNextSurfacePoint(dc, Angle.fromDegrees(lat), Angle.fromDegrees(lon), attr, outRenderInfo); lon += lonStep; } lat += latStep; } outRenderInfo.cartesianVertexBuffer.rewind(); outRenderInfo.geographicVertexBuffer.rewind(); outRenderInfo.colorBuffer.rewind(); outRenderInfo.shadowColorBuffer.rewind(); } protected void updateNextSurfacePoint(DrawContext dc, Angle lat, Angle lon, GridPointAttributes attr, RenderInfo outRenderInfo) { Color color = (attr != null) ? attr.getColor() : DEFAULT_COLOR; // Convert the floating point opacity from the range [0, 1] to the unsigned byte range [0, 255]. int alpha = (int) (color.getAlpha() * this.surfaceAttributes.getInteriorOpacity() + 0.5); outRenderInfo.colorBuffer.put((byte) color.getRed()); outRenderInfo.colorBuffer.put((byte) color.getGreen()); outRenderInfo.colorBuffer.put((byte) color.getBlue()); outRenderInfo.colorBuffer.put((byte) alpha); // We need geographic vertices if the surface's altitude mode is clamp-to-ground, or if we're drawing the // surface's shadow. if (this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND || this.surfaceAttributes.isDrawShadow()) { outRenderInfo.geographicVertexBuffer.put((float) (lon.degrees - this.referencePos.getLongitude().degrees)); outRenderInfo.geographicVertexBuffer.put((float) (lat.degrees - this.referencePos.getLatitude().degrees)); outRenderInfo.geographicVertexBuffer.put(1); } // We need cartesian vertices if the surface's altitude mode is absolute or relative-to-ground. if (this.getAltitudeMode() != WorldWind.CLAMP_TO_GROUND) // WorldWind.ABSOLUTE or WorldWind.RELATIVE_TO_GROUND { Vec4 point = this.computeSurfacePoint(dc, lat, lon, (attr != null ? attr.getValue() : DEFAULT_VALUE)); outRenderInfo.cartesianVertexBuffer.put((float) (point.x - this.referencePoint.x)); outRenderInfo.cartesianVertexBuffer.put((float) (point.y - this.referencePoint.y)); outRenderInfo.cartesianVertexBuffer.put((float) (point.z - this.referencePoint.z)); } // We need shadow colors if the surface's shadow is enabled. if (this.surfaceAttributes.isDrawShadow()) { // Convert the floating point opacity from the range [0, 1] to the unsigned byte range [0, 255]. int shadowAlpha = (int) (alpha * this.surfaceAttributes.getShadowOpacity() + 0.5); outRenderInfo.shadowColorBuffer.put((byte) 0); outRenderInfo.shadowColorBuffer.put((byte) 0); outRenderInfo.shadowColorBuffer.put((byte) 0); outRenderInfo.shadowColorBuffer.put((byte) shadowAlpha); } } protected Vec4 computeSurfacePoint(DrawContext dc, Angle lat, Angle lon, double value) { double offset = this.altitude + this.verticalScale * value; if (this.getAltitudeMode() == WorldWind.RELATIVE_TO_GROUND) { return dc.computeTerrainPoint(lat, lon, dc.getVerticalExaggeration() * offset); } else // WorldWind.ABSOLUTE { return dc.getGlobe().computePointFromPosition(lat, lon, offset); } } protected void updateSurfaceNormals(RenderInfo outRenderInfo) { WWMath.computeNormalsForIndexedTriangleStrip(outRenderInfo.interiorIndexBuffer, outRenderInfo.cartesianVertexBuffer, outRenderInfo.cartesianNormalBuffer); } //**************************************************************// //******************** Internal Support Classes **************// //**************************************************************// protected static class OrderedSurface implements OrderedRenderable { protected final AnalyticSurface surface; protected final double eyeDistance; public OrderedSurface(AnalyticSurface surface, double eyeDistance) { this.surface = surface; this.eyeDistance = eyeDistance; } public double getDistanceFromEye() { return this.eyeDistance; } public void pick(DrawContext dc, Point pickPoint) { this.surface.pickSupport.beginPicking(dc); try { this.render(dc); } finally { this.surface.pickSupport.endPicking(dc); this.surface.pickSupport.resolvePick(dc, dc.getPickPoint(), this.surface.getClientLayer()); } } public void render(DrawContext dc) { this.surface.drawOrderedRenderable(dc); } } protected static class RenderInfo { protected final int gridWidth; protected final int gridHeight; protected final IntBuffer interiorIndexBuffer; protected final IntBuffer outlineIndexBuffer; protected final FloatBuffer cartesianVertexBuffer; protected final FloatBuffer cartesianNormalBuffer; protected final FloatBuffer geographicVertexBuffer; protected final ByteBuffer colorBuffer; protected final ByteBuffer shadowColorBuffer; public RenderInfo(int gridWidth, int gridHeight) { int numVertices = gridWidth * gridHeight; this.gridWidth = gridWidth; this.gridHeight = gridHeight; this.interiorIndexBuffer = WWMath.computeIndicesForGridInterior(gridWidth, gridHeight); this.outlineIndexBuffer = WWMath.computeIndicesForGridOutline(gridWidth, gridHeight); this.cartesianVertexBuffer = Buffers.newDirectFloatBuffer(3 * numVertices); this.cartesianNormalBuffer = Buffers.newDirectFloatBuffer(3 * numVertices); this.geographicVertexBuffer = Buffers.newDirectFloatBuffer(3 * numVertices); this.colorBuffer = Buffers.newDirectByteBuffer(4 * numVertices); this.shadowColorBuffer = Buffers.newDirectByteBuffer(4 * numVertices); } public int getGridWidth() { return this.gridWidth; } public int getGridHeight() { return this.gridHeight; } public int getNumVertices() { return this.gridWidth * this.gridHeight; } public void drawInterior(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glDrawElements(GL.GL_TRIANGLE_STRIP, this.interiorIndexBuffer.remaining(), GL.GL_UNSIGNED_INT, this.interiorIndexBuffer); } public void drawOutline(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glDrawElements(GL.GL_LINE_LOOP, this.outlineIndexBuffer.remaining(), GL.GL_UNSIGNED_INT, this.outlineIndexBuffer); } } //**************************************************************// //******************** Surface Objects ***********************// //**************************************************************// protected void preRenderSurfaceObjects(DrawContext dc) { if (this.surfaceAttributes.isDrawShadow()) { if (this.shadowSurface == null) this.shadowSurface = this.createShadowSurface(); this.shadowSurface.preRender(dc); } if (this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND) { if (this.clampToGroundSurface == null) this.clampToGroundSurface = this.createClampToGroundSurface(); this.clampToGroundSurface.preRender(dc); } } protected void drawSurfaceObjects(DrawContext dc) { if (this.surfaceAttributes.isDrawShadow()) { if (!dc.isPickingMode()) this.shadowSurface.render(dc); } if (this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND) { this.clampToGroundSurface.render(dc); } } protected AnalyticSurfaceObject createClampToGroundSurface() { return new ClampToGroundSurface(this); } protected AnalyticSurfaceObject createShadowSurface() { return new ShadowSurface(this); } protected static class AnalyticSurfaceObject extends AbstractSurfaceObject { protected AnalyticSurface analyticSurface; public AnalyticSurfaceObject(AnalyticSurface analyticSurface) { this.analyticSurface = analyticSurface; } public void markAsModified() { super.updateModifiedTime(); super.clearCaches(); } public List getSectors(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return Arrays.asList(this.analyticSurface.sector); } protected void drawGeographic(DrawContext dc, SurfaceTileDrawContext sdc) { this.beginDrawing(dc); try { this.doDrawGeographic(dc, sdc); } finally { this.endDrawing(dc); } } protected void beginDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glPushAttrib( GL2.GL_COLOR_BUFFER_BIT // for alpha func and ref, blend func | GL2.GL_CURRENT_BIT // for current RGBA color | GL2.GL_DEPTH_BUFFER_BIT // for depth test disable | GL2.GL_LINE_BIT); // for line width, line smooth gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPushMatrix(); gl.glEnable(GL.GL_BLEND); OGLUtil.applyBlending(gl, false); gl.glDisable(GL.GL_DEPTH_TEST); if (!dc.isPickingMode()) { gl.glEnable(GL.GL_LINE_SMOOTH); } } protected void endDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glPopMatrix(); gl.glPopAttrib(); // Restore default GL client vertex array state. gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); } protected void doDrawGeographic(DrawContext dc, SurfaceTileDrawContext sdc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. Matrix modelview = sdc.getModelviewMatrix(this.analyticSurface.referencePos); gl.glMultMatrixd(modelview.toArray(new double[16], 0, false), 0); this.bind(dc); if (this.analyticSurface.surfaceAttributes.isDrawInterior()) this.drawInterior(dc); if (this.analyticSurface.surfaceAttributes.isDrawOutline()) this.drawOutline(dc); } protected void bind(DrawContext dc) { } protected void drawInterior(DrawContext dc) { this.analyticSurface.surfaceRenderInfo.drawInterior(dc); } protected void drawOutline(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glLineWidth((float) this.analyticSurface.surfaceAttributes.getOutlineWidth()); this.analyticSurface.surfaceRenderInfo.drawOutline(dc); } } protected static class ClampToGroundSurface extends AnalyticSurfaceObject { public ClampToGroundSurface(AnalyticSurface analyticSurface) { super(analyticSurface); } @Override protected void bind(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL.GL_FLOAT, 0, this.analyticSurface.surfaceRenderInfo.geographicVertexBuffer); if (!dc.isPickingMode()) { gl.glEnableClientState(GL2.GL_COLOR_ARRAY); gl.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, this.analyticSurface.surfaceRenderInfo.colorBuffer); } } protected void drawOutline(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. if (!dc.isPickingMode()) { // Set the outline color. Color color = this.analyticSurface.surfaceAttributes.getOutlineMaterial().getDiffuse(); // Convert the floating point opacity from the range [0, 1] to the unsigned byte range [0, 255]. int alpha = (int) (255 * this.analyticSurface.surfaceAttributes.getOutlineOpacity() + 0.5); gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(), (byte) alpha); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); } super.drawOutline(dc); if (!dc.isPickingMode()) { gl.glEnableClientState(GL2.GL_COLOR_ARRAY); } } } protected static class ShadowSurface extends AnalyticSurfaceObject { public ShadowSurface(AnalyticSurface analyticSurface) { super(analyticSurface); } @Override protected void buildPickRepresentation(DrawContext dc) { // The analytic surface's shadow is not drawn during picking. Suppress any attempt to create a pick // representation for the shadow to eliminate unnecessary overhead. } @Override protected void bind(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL.GL_FLOAT, 0, this.analyticSurface.surfaceRenderInfo.geographicVertexBuffer); if (!dc.isPickingMode()) { gl.glEnableClientState(GL2.GL_COLOR_ARRAY); gl.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, this.analyticSurface.surfaceRenderInfo.shadowColorBuffer); } } protected void drawOutline(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. if (!dc.isPickingMode()) { // Convert the floating point opacity from the range [0, 1] to the unsigned byte range [0, 255]. int alpha = (int) (255 * this.analyticSurface.surfaceAttributes.getOutlineOpacity() * this.analyticSurface.surfaceAttributes.getShadowOpacity() + 0.5); gl.glColor4ub((byte) 0, (byte) 0, (byte) 0, (byte) alpha); gl.glDisableClientState(GL2.GL_COLOR_ARRAY); } super.drawOutline(dc); if (!dc.isPickingMode()) { gl.glEnableClientState(GL2.GL_COLOR_ARRAY); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy