/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.curtain; import gov.nasa.worldwind.View; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.avlist.AVList; import gov.nasa.worldwind.avlist.AVListImpl; import gov.nasa.worldwind.geom.Extent; import gov.nasa.worldwind.geom.LatLon; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.geom.Sector; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.layers.AbstractLayer; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.render.Renderable; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.OGLTextRenderer; import gov.nasa.worldwind.util.PerformanceStatistic; import gov.nasa.worldwind.util.WWXML; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.concurrent.PriorityBlockingQueue; import javax.media.opengl.GL2; import javax.xml.xpath.XPath; import org.w3c.dom.Element; import au.gov.ga.earthsci.worldwind.common.layers.Bounded; import au.gov.ga.earthsci.worldwind.common.layers.Bounds; import au.gov.ga.earthsci.worldwind.common.util.AVKeyMore; import com.jogamp.opengl.util.awt.TextRenderer; /** * {@link Layer} which renders a textured surface along a horizontal line (ie a * curtain). * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public abstract class TiledCurtainLayer extends AbstractLayer implements Bounded { //TODO where should this live protected CurtainTileRenderer renderer = new CurtainTileRenderer(); protected Bounds bounds; protected Path path; protected double curtainTop = 0; protected double curtainBottom = -10000; protected boolean followTerrain = false; //TODO how do we do this? protected int subsegments = 1; // Infrastructure protected static final LevelComparer levelComparer = new LevelComparer(); protected final CurtainLevelSet levels; protected List<CurtainTextureTile> topLevels; protected boolean forceLevelZeroLoads = false; protected boolean levelZeroLoaded = false; protected boolean retainLevelZeroTiles = false; protected String tileCountName; protected double detailHintOrigin = 2.8; protected double detailHint = 0; protected boolean useMipMaps = true; protected boolean useTransparentTextures = false; protected List<String> supportedImageFormats = new ArrayList<String>(); protected String textureFormat; // Diagnostic flags protected boolean drawTileBoundaries = false; protected boolean drawTileIDs = false; protected boolean drawBoundingVolumes = false; // Stuff computed each frame protected List<CurtainTextureTile> currentTiles = new ArrayList<CurtainTextureTile>(); protected CurtainTextureTile currentResourceTile; protected boolean atMaxResolution = false; protected PriorityBlockingQueue<Runnable> requestQ = new PriorityBlockingQueue<Runnable>(200); abstract protected void requestTexture(DrawContext dc, CurtainTextureTile tile); abstract protected void forceTextureLoad(CurtainTextureTile tile); public TiledCurtainLayer(CurtainLevelSet levelSet) { if (levelSet == null) { String message = Logging.getMessage("nullValue.LevelSetIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.levels = new CurtainLevelSet(levelSet); // the caller's levelSet may change internally, so we copy it. this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise. this.tileCountName = this.getName() + " Tiles"; } @Override public Object setValue(String key, Object value) { // Offer it to the level set if (this.getLevels() != null) { this.getLevels().setValue(key, value); } return super.setValue(key, value); } @Override public Object getValue(String key) { Object value = super.getValue(key); return value != null ? value : this.getLevels().getValue(key); // see if the level set has it } @Override public void setName(String name) { super.setName(name); this.tileCountName = this.getName() + " Tiles"; } public Path getPath() { return path; } public void setPath(Path path) { this.path = path; bounds = null; } public double getCurtainTop() { return curtainTop; } public void setCurtainTop(double curtainTop) { this.curtainTop = curtainTop; bounds = null; } public double getCurtainBottom() { return curtainBottom; } public void setCurtainBottom(double curtainBottom) { this.curtainBottom = curtainBottom; bounds = null; } @Override public boolean isFollowTerrain() { return followTerrain; } public void setFollowTerrain(boolean followTerrain) { this.followTerrain = followTerrain; } public int getSubsegments() { return subsegments; } public void setSubsegments(int subsegments) { this.subsegments = subsegments; } public boolean isForceLevelZeroLoads() { return this.forceLevelZeroLoads; } public void setForceLevelZeroLoads(boolean forceLevelZeroLoads) { this.forceLevelZeroLoads = forceLevelZeroLoads; } public boolean isRetainLevelZeroTiles() { return retainLevelZeroTiles; } public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles) { this.retainLevelZeroTiles = retainLevelZeroTiles; } public boolean isDrawTileIDs() { return drawTileIDs; } public void setDrawTileIDs(boolean drawTileIDs) { this.drawTileIDs = drawTileIDs; } public boolean isDrawTileBoundaries() { return drawTileBoundaries; } public void setDrawTileBoundaries(boolean drawTileBoundaries) { this.drawTileBoundaries = drawTileBoundaries; } public boolean isDrawBoundingVolumes() { return drawBoundingVolumes; } public void setDrawBoundingVolumes(boolean drawBoundingVolumes) { this.drawBoundingVolumes = drawBoundingVolumes; } /** * Indicates the layer's detail hint, which is described in * {@link #setDetailHint(double)}. * * @return the detail hint * * @see #setDetailHint(double) */ public double getDetailHint() { return this.detailHint; } /** * Modifies the default relationship of image resolution to screen * resolution as the viewing altitude changes. Values greater than 0 cause * imagery to appear at higher resolution at greater altitudes than normal, * but at an increased performance cost. Values less than 0 decrease the * default resolution at any given altitude. The default value is 0. Values * typically range between -0.5 and 0.5. * <p/> * Note: The resolution-to-height relationship is defined by a scale factor * that specifies the approximate size of discernable lengths in the image * relative to eye distance. The scale is specified as a power of 10. A * value of 3, for example, specifies that 1 meter on the surface should be * distinguishable from an altitude of 10^3 meters (1000 meters). The * default scale is 1/10^2.8, (1 over 10 raised to the power 2.8). The * detail hint specifies deviations from that default. A detail hint of 0.2 * specifies a scale of 1/1000, i.e., 1/10^(2.8 + .2) = 1/10^3. Scales much * larger than 3 typically cause the applied resolution to be higher than * discernable for the altitude. Such scales significantly decrease * performance. * * @param detailHint * the degree to modify the default relationship of image * resolution to screen resolution with changing view altitudes. * Values greater than 1 increase the resolution. Values less * than zero decrease the resolution. The default value is 0. */ public void setDetailHint(double detailHint) { this.detailHint = detailHint; } protected CurtainLevelSet getLevels() { return levels; } protected PriorityBlockingQueue<Runnable> getRequestQ() { return requestQ; } @Override public boolean isMultiResolution() { return this.getLevels() != null && this.getLevels().getNumLevels() > 1; } @Override public boolean isAtMaxResolution() { return this.atMaxResolution; } /** * Returns the format used to store images in texture memory, or null if * images are stored in their native format. * * @return the texture image format; null if images are stored in their * native format. * * @see {@link #setTextureFormat(String)} */ public String getTextureFormat() { return this.textureFormat; } /** * Specifies the format used to store images in texture memory, or null to * store images in their native format. Suppported texture formats are as * follows: * <ul> * <li><code>image/dds</code> - Stores images in the compressed DDS format. * If the image is already in DDS format it's stored as-is.</li> * </ul> * * @param textureFormat * the texture image format; null to store images in their native * format. */ public void setTextureFormat(String textureFormat) { this.textureFormat = textureFormat; } public boolean isUseMipMaps() { return useMipMaps; } public void setUseMipMaps(boolean useMipMaps) { this.useMipMaps = useMipMaps; } public boolean isUseTransparentTextures() { return this.useTransparentTextures; } public void setUseTransparentTextures(boolean useTransparentTextures) { this.useTransparentTextures = useTransparentTextures; } /** * Specifies the time of the layer's most recent dataset update, beyond * which cached data is invalid. If greater than zero, the layer ignores and * eliminates any in-memory or on-disk cached data older than the time * specified, and requests new information from the data source. If zero, * the default, the layer applies any expiry times associated with its * individual levels, but only for on-disk cached data. In-memory cached * data is expired only when the expiry time is specified with this method * and is greater than zero. This method also overwrites the expiry times of * the layer's individual levels if the value specified to the method is * greater than zero. * * @param expiryTime * the expiry time of any cached data, expressed as a number of * milliseconds beyond the epoch. The default expiry time is * zero. * * @see System#currentTimeMillis() for a description of milliseconds beyond * the epoch. */ @Override public void setExpiryTime(long expiryTime) // Override this method to use intrinsic level-specific expiry times { super.setExpiryTime(expiryTime); if (expiryTime > 0) { this.levels.setExpiryTime(expiryTime); // remove this in sub-class to use level-specific expiry times } } public List<CurtainTextureTile> getTopLevels() { if (this.topLevels == null) { this.createTopLevelTiles(); } return topLevels; } protected void createTopLevelTiles() { CurtainLevel level = levels.getFirstLevel(); int rowCount = level.getRowCount(); int colCount = level.getColumnCount(); this.topLevels = new ArrayList<CurtainTextureTile>(rowCount * colCount); for (int row = 0; row < rowCount; row++) { for (int col = 0; col < colCount; col++) { Segment segment = level.computeSegmentForRowColumn(row, col); CurtainTextureTile tile = createCurtainTextureTile(segment, level, row, col); this.topLevels.add(tile); } } } protected CurtainTextureTile createCurtainTextureTile(Segment segment, CurtainLevel level, int row, int col) { return new CurtainTextureTile(segment, level, row, col); } protected void loadAllTopLevelTextures(DrawContext dc) { for (CurtainTextureTile tile : this.getTopLevels()) { if (!tile.isTextureInMemory(dc.getTextureCache())) { this.forceTextureLoad(tile); } } this.levelZeroLoaded = true; } @Override public Bounds getBounds() { if (bounds == null) { Sector sector = path == null ? null : path.getBoundingSector(); bounds = sector == null ? null : Bounds.fromSector(sector, curtainBottom, curtainTop); } return bounds; } // ============== Tile Assembly ======================= // // ============== Tile Assembly ======================= // // ============== Tile Assembly ======================= // protected void assembleTiles(DrawContext dc) { this.currentTiles.clear(); for (CurtainTextureTile tile : this.getTopLevels()) { if (this.isTileVisible(dc, tile)) { this.currentResourceTile = null; this.addTileOrDescendants(dc, tile); } } } protected void addTileOrDescendants(DrawContext dc, CurtainTextureTile tile) { if (this.meetsRenderCriteria(dc, tile)) { this.addTile(dc, tile); return; } // The incoming tile does not meet the rendering criteria, so it must be subdivided and those // subdivisions tested against the criteria. // All tiles that meet the selection criteria are drawn, but some of those tiles will not have // textures associated with them either because their texture isn't loaded yet or because they // are finer grain than the layer has textures for. In these cases the tiles use the texture of // the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile. // A texture transform is applied during rendering to align the sector's texture coordinates with the // appropriate region of the ancestor's texture. CurtainTextureTile ancestorResource = null; try { // TODO: Revise this to reflect that the parent layer is only requested while the algorithm continues // to search for the layer matching the criteria. // At this point the tile does not meet the render criteria but it may have its texture in memory. // If so, register this tile as the resource tile. If not, then this tile will be the next level // below a tile with texture in memory. So to provide progressive resolution increase, add this tile // to the draw list. That will cause the tile to be drawn using its parent tile's texture, and it will // cause it's texture to be requested. At some future call to this method the tile's texture will be in // memory, it will not meet the render criteria, but will serve as the parent to a tile that goes // through this same process as this method recurses. The result of all this is that a tile isn't rendered // with its own texture unless all its parents have their textures loaded. In addition to causing // progressive resolution increase, this ensures that the parents are available as the user zooms out, and // therefore the layer remains visible until the user is zoomed out to the point the layer is no longer // active. if (tile.isTextureInMemory(dc.getTextureCache()) || tile.getLevelNumber() == 0) { ancestorResource = this.currentResourceTile; this.currentResourceTile = tile; } else if (!tile.getLevel().isEmpty()) { // this.addTile(dc, tile); // return; // Issue a request for the parent before descending to the children. // if (tile.getLevelNumber() < this.levels.getNumLevels()) // { // // Request only tiles with data associated at this level // if (!this.levels.isResourceAbsent(tile)) // this.requestTexture(dc, tile); // } } CurtainTextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1)); for (CurtainTextureTile child : subTiles) { if (this.isTileVisible(dc, child)) { this.addTileOrDescendants(dc, child); } } } finally { if (ancestorResource != null) // Pop this tile as the currentResource ancestor { this.currentResourceTile = ancestorResource; } } } protected void addTile(DrawContext dc, CurtainTextureTile tile) { tile.setFallbackTile(null); if (tile.isTextureInMemory(dc.getTextureCache())) { this.addTileToCurrent(tile); return; } // Level 0 loads may be forced if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !tile.isTextureInMemory(dc.getTextureCache())) { this.forceTextureLoad(tile); if (tile.isTextureInMemory(dc.getTextureCache())) { this.addTileToCurrent(tile); return; } } // Tile's texture isn't available, so request it if (tile.getLevelNumber() < this.levels.getNumLevels()) { // Request only tiles with data associated at this level if (!this.levels.isResourceAbsent(tile)) { this.requestTexture(dc, tile); } } // Set up to use the currentResource tile's texture if (this.currentResourceTile != null) { if (this.currentResourceTile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !this.currentResourceTile.isTextureInMemory(dc.getTextureCache()) && !this.currentResourceTile.isTextureInMemory(dc.getTextureCache())) { this.forceTextureLoad(this.currentResourceTile); } if (this.currentResourceTile.isTextureInMemory(dc.getTextureCache())) { tile.setFallbackTile(currentResourceTile); this.addTileToCurrent(tile); } } } protected void addTileToCurrent(CurtainTextureTile tile) { this.currentTiles.add(tile); } protected boolean isTileVisible(DrawContext dc, CurtainTextureTile tile) { Segment segment = tile.getSegment(); Extent extent = path.getSegmentExtent(dc, segment, curtainTop, curtainBottom, subsegments, followTerrain); // TODO: Fix this - see https://github.com/ga-m3dv/ga-worldwind-suite/issues/73 //this is the workaround mentioned in the issue text (instead of the above): //Sector pathBoundingSector = getBounds().toSector(); //Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), pathBoundingSector); return extent.intersects(dc.getView().getFrustumInModelCoordinates()); } protected boolean meetsRenderCriteria(DrawContext dc, CurtainTextureTile tile) { return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile); } protected double getDetailFactor() { return this.detailHintOrigin + this.getDetailHint(); } protected boolean needToSplit(DrawContext dc, CurtainTextureTile tile) { Vec4[] points = path.getPointsInSegment(dc, tile.getSegment(), curtainTop, curtainBottom, subsegments, followTerrain); Vec4 centerPoint = path.getSegmentCenterPoint(dc, tile.getSegment(), curtainTop, curtainBottom, followTerrain); View view = dc.getView(); Vec4 eyePoint = view.getEyePoint(); double texelSize = tile.getLevel().getTexelSize(); double minDistance = eyePoint.distanceTo3(centerPoint); double cellHeight = centerPoint.getLength3() * texelSize; for (Vec4 point : points) { double distance = eyePoint.distanceTo3(point); if (distance < minDistance) { minDistance = distance; cellHeight = point.getLength3() * texelSize; } } // Split when the cell height (length of a texel) becomes greater than the specified fraction of the eye // distance. The fraction is specified as a power of 10. For example, a detail factor of 3 means split when the // cell height becomes more than one thousandth of the eye distance. Another way to say it is, use the current // tile if its cell height is less than the specified fraction of the eye distance. // // NOTE: It's tempting to instead compare a screen pixel size to the texel size, but that calculation is // window-size dependent and results in selecting an excessive number of tiles when the window is large. return cellHeight > minDistance * Math.pow(10, -this.getDetailFactor()); } // ============== Rendering ======================= // // ============== Rendering ======================= // // ============== Rendering ======================= // @Override public void render(DrawContext dc) { //this.atMaxResolution = this.atMaxLevel(dc); //TODO calculate super.render(dc); } @Override protected final void doRender(DrawContext dc) { if (this.forceLevelZeroLoads && !this.levelZeroLoaded) { this.loadAllTopLevelTextures(dc); } if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1) { return; } //TODO add tile boundary rendering to the curtain tile renderer //dc.getGeographicSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines); draw(dc); } protected void draw(DrawContext dc) { this.assembleTiles(dc); // Determine the tiles to draw. if (this.currentTiles.size() >= 1) { if (this.getScreenCredit() != null) { dc.addScreenCredit(this.getScreenCredit()); } CurtainTextureTile[] sortedTiles = new CurtainTextureTile[this.currentTiles.size()]; sortedTiles = this.currentTiles.toArray(sortedTiles); Arrays.sort(sortedTiles, levelComparer); GL2 gl = dc.getGL().getGL2(); if (this.isUseTransparentTextures() || this.getOpacity() < 1) { gl.glPushAttrib(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_POLYGON_BIT | GL2.GL_CURRENT_BIT); this.setBlendingFunction(dc); } else { gl.glPushAttrib(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_POLYGON_BIT); } gl.glPolygonMode(GL2.GL_FRONT, GL2.GL_FILL); gl.glEnable(GL2.GL_CULL_FACE); gl.glCullFace(GL2.GL_BACK); dc.setPerFrameStatistic(PerformanceStatistic.IMAGE_TILE_COUNT, this.tileCountName, this.currentTiles.size()); renderer.renderTiles(dc, this.currentTiles, getPath(), getCurtainTop(), getCurtainBottom(), getSubsegments(), isFollowTerrain()); gl.glPopAttrib(); if (this.drawTileIDs) { this.drawTileIDs(dc, this.currentTiles); } if (this.drawBoundingVolumes) { this.drawBoundingVolumes(dc, this.currentTiles); } // Check texture expiration. Memory-cached textures are checked for expiration only when an explicit, // non-zero expiry time has been set for the layer. If none has been set, the expiry times of the layer's // individual levels are used, but only for images in the local file cache, not textures in memory. This is // to avoid incurring the overhead of checking expiration of in-memory textures, a very rarely used feature. if (this.getExpiryTime() > 0 && this.getExpiryTime() < System.currentTimeMillis()) { this.checkTextureExpiration(dc, this.currentTiles); } this.currentTiles.clear(); } this.sendRequests(); this.requestQ.clear(); } protected void checkTextureExpiration(DrawContext dc, List<CurtainTextureTile> tiles) { for (CurtainTextureTile tile : tiles) { if (tile.isTextureExpired()) { this.requestTexture(dc, tile); } } } protected void setBlendingFunction(DrawContext dc) { // Set up a premultiplied-alpha blending function. Any texture read by JOGL will have alpha-premultiplied color // components, as will any DDS file created by World Wind or the World Wind WMS. We'll also set up the base // color as a premultiplied color, so that any incoming premultiplied color will be properly combined with the // base color. GL2 gl = dc.getGL().getGL2(); double alpha = this.getOpacity(); gl.glColor4d(alpha, alpha, alpha, alpha); gl.glEnable(GL2.GL_BLEND); gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE_MINUS_SRC_ALPHA); } protected void sendRequests() { Runnable task = this.requestQ.poll(); while (task != null) { if (!WorldWind.getTaskService().isFull()) { WorldWind.getTaskService().addTask(task); } task = this.requestQ.poll(); } } @Override public boolean isLayerInView(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } if (dc.getView() == null) { String message = Logging.getMessage("layers.AbstractLayer.NoViewSpecifiedInDrawingContext"); Logging.logger().severe(message); throw new IllegalStateException(message); } Extent extent = path.getSegmentExtent(dc, Segment.FULL, curtainTop, curtainBottom, 1, followTerrain); return extent.intersects(dc.getView().getFrustumInModelCoordinates()); /*return dc.getVisibleSector() == null || dc.getVisibleSector().intersectsSegment(path.getPercentLatLon(0d), path.getPercentLatLon(1d));*/ } protected Vec4 computeReferencePoint(DrawContext dc) { if (dc.getViewportCenterPosition() != null) { return dc.getGlobe().computePointFromPosition(dc.getViewportCenterPosition()); } java.awt.geom.Rectangle2D viewport = dc.getView().getViewport(); int x = (int) viewport.getWidth() / 2; for (int y = (int) (0.5 * viewport.getHeight()); y >= 0; y--) { Position pos = dc.getView().computePositionFromScreenPoint(x, y); if (pos == null) { continue; } return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d); } return null; } protected Vec4 getReferencePoint(DrawContext dc) { return this.computeReferencePoint(dc); } protected static class LevelComparer implements Comparator<CurtainTextureTile> { @Override public int compare(CurtainTextureTile ta, CurtainTextureTile tb) { int la = ta.getFallbackTile() == null ? ta.getLevelNumber() : ta.getFallbackTile().getLevelNumber(); int lb = tb.getFallbackTile() == null ? tb.getLevelNumber() : tb.getFallbackTile().getLevelNumber(); return la < lb ? -1 : la == lb ? 0 : 1; } } protected void drawTileIDs(DrawContext dc, List<CurtainTextureTile> tiles) { java.awt.Rectangle viewport = dc.getView().getViewport(); TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), java.awt.Font.decode("Arial-Plain-13")); dc.getGL().glDisable(GL2.GL_DEPTH_TEST); dc.getGL().glDisable(GL2.GL_BLEND); dc.getGL().glDisable(GL2.GL_TEXTURE_2D); textRenderer.beginRendering(viewport.width, viewport.height); textRenderer.setColor(java.awt.Color.YELLOW); for (CurtainTextureTile tile : tiles) { String tileLabel = tile.getLabel(); if (tile.getFallbackTile() != null) { tileLabel += "/" + tile.getFallbackTile().getLabel(); } Vec4 pt = path.getSegmentCenterPoint(dc, tile.getSegment(), curtainTop, curtainBottom, followTerrain); pt = dc.getView().project(pt); textRenderer.draw(tileLabel, (int) pt.x, (int) pt.y); } textRenderer.setColor(java.awt.Color.WHITE); textRenderer.endRendering(); } protected void drawBoundingVolumes(DrawContext dc, List<CurtainTextureTile> tiles) { float[] previousColor = new float[4]; dc.getGL().glGetFloatv(GL2.GL_CURRENT_COLOR, previousColor, 0); dc.getGL().getGL2().glColor3d(0, 1, 0); for (CurtainTextureTile tile : tiles) { Extent extent = path.getSegmentExtent(dc, tile.getSegment(), curtainTop, curtainBottom, subsegments, followTerrain); if (extent instanceof Renderable) { ((Renderable) extent).render(dc); } /*dc.getGL().glBegin(GL.GL_POINTS); Vec4[] points = path.getPointsInSegment(dc, tile.getSegment(), top, bottom); for (Vec4 point : points) { dc.getGL().glVertex3d(point.x, point.y, point.z); } dc.getGL().glEnd();*/ } dc.getGL().getGL2().glColor4fv(previousColor, 0); } //**************************************************************// //******************** Configuration *************************// //**************************************************************// public static AVList getTiledCurtainLayerConfigParams(Element domElement, AVList params) { if (domElement == null) { String message = Logging.getMessage("nullValue.DocumentIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (params == null) { params = new AVListImpl(); } XPath xpath = WWXML.makeXPath(); // Common layer properties. AbstractLayer.getLayerConfigParams(domElement, params); // LevelSet properties. CurtainDataConfigurationUtils.getLevelSetConfigParams(domElement, params); // Service properties. WWXML.checkAndSetStringParam(domElement, params, AVKey.SERVICE_NAME, "Service/@serviceName", xpath); WWXML.checkAndSetBooleanParam(domElement, params, AVKey.RETRIEVE_PROPERTIES_FROM_SERVICE, "RetrievePropertiesFromService", xpath); // Image format properties. WWXML.checkAndSetStringParam(domElement, params, AVKey.IMAGE_FORMAT, "ImageFormat", xpath); WWXML.checkAndSetStringParam(domElement, params, AVKey.TEXTURE_FORMAT, "TextureFormat", xpath); WWXML.checkAndSetUniqueStringsParam(domElement, params, AVKey.AVAILABLE_IMAGE_FORMATS, "AvailableImageFormats/ImageFormat", xpath); // Optional behavior properties. WWXML.checkAndSetBooleanParam(domElement, params, AVKey.FORCE_LEVEL_ZERO_LOADS, "ForceLevelZeroLoads", xpath); WWXML.checkAndSetBooleanParam(domElement, params, AVKey.RETAIN_LEVEL_ZERO_TILES, "RetainLevelZeroTiles", xpath); WWXML.checkAndSetBooleanParam(domElement, params, AVKey.USE_MIP_MAPS, "UseMipMaps", xpath); WWXML.checkAndSetBooleanParam(domElement, params, AVKey.USE_TRANSPARENT_TEXTURES, "UseTransparentTextures", xpath); WWXML.checkAndSetDoubleParam(domElement, params, AVKey.DETAIL_HINT, "DetailHint", xpath); WWXML.checkAndSetColorArrayParam(domElement, params, AVKey.TRANSPARENCY_COLORS, "TransparencyColors/Color", xpath); // Retrieval properties. Convert the Long time values to Integers, because BasicTiledImageLayer is expecting // Integer values. WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.URL_CONNECT_TIMEOUT, "RetrievalTimeouts/ConnectTimeout/Time", xpath); WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.URL_READ_TIMEOUT, "RetrievalTimeouts/ReadTimeout/Time", xpath); WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, "RetrievalTimeouts/StaleRequestLimit/Time", xpath); // Curtain specific properties. WWXML.checkAndSetDoubleParam(domElement, params, AVKeyMore.CURTAIN_TOP, "CurtainTop", xpath); WWXML.checkAndSetDoubleParam(domElement, params, AVKeyMore.CURTAIN_BOTTOM, "CurtainBottom", xpath); WWXML.checkAndSetBooleanParam(domElement, params, AVKeyMore.FOLLOW_TERRAIN, "FollowTerrain", xpath); WWXML.checkAndSetIntegerParam(domElement, params, AVKeyMore.SUBSEGMENTS, "Subsegments", xpath); // Curtain path List<LatLon> positions = new ArrayList<LatLon>(); Element[] latlons = WWXML.getElements(domElement, "Path/LatLon", xpath); for (Element latlon : latlons) { LatLon ll = WWXML.getLatLon(latlon, null, xpath); positions.add(ll); } Path path = new Path(positions); params.setValue(AVKeyMore.PATH, path); // Parse the legacy configuration parameters. This enables TiledImageLayer to recognize elements from previous // versions of configuration documents. getLegacyTiledImageLayerConfigParams(domElement, params); return params; } /** * Parses TiledImageLayer configuration parameters from previous versions of * configuration documents. This writes output as key-value pairs to params. * If a parameter from the XML document already exists in params, that * parameter is ignored. Supported key and parameter names are: * <table> * <tr> * <th>Parameter</th> * <th>Element Path</th> * <th>Type</th> * </tr> * <tr> * <td>{@link AVKey#TEXTURE_FORMAT}</td> * <td>CompressTextures</td> * <td>"image/dds" if CompressTextures is "true"; null otherwise</td> * </tr> * </table> * * @param domElement * the XML document root to parse for legacy TiledImageLayer * configuration parameters. * @param params * the output key-value pairs which recieve the TiledImageLayer * configuration parameters. A null reference is permitted. * * @return a reference to params, or a new AVList if params is null. * * @throws IllegalArgumentException * if the document is null. */ protected static AVList getLegacyTiledImageLayerConfigParams(Element domElement, AVList params) { if (domElement == null) { String message = Logging.getMessage("nullValue.DocumentIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (params == null) { params = new AVListImpl(); } XPath xpath = WWXML.makeXPath(); Object o = params.getValue(AVKey.TEXTURE_FORMAT); if (o == null) { Boolean b = WWXML.getBoolean(domElement, "CompressTextures", xpath); if (b != null && b) { params.setValue(AVKey.TEXTURE_FORMAT, "image/dds"); } } return params; } // ============== Image Composition ======================= // // ============== Image Composition ======================= // // ============== Image Composition ======================= // public List<String> getAvailableImageFormats() { return new ArrayList<String>(this.supportedImageFormats); } public boolean isImageFormatAvailable(String imageFormat) { return imageFormat != null && this.supportedImageFormats.contains(imageFormat); } public String getDefaultImageFormat() { return this.supportedImageFormats.size() > 0 ? this.supportedImageFormats.get(0) : null; } protected void setAvailableImageFormats(String[] formats) { this.supportedImageFormats.clear(); if (formats != null) { this.supportedImageFormats.addAll(Arrays.asList(formats)); } } }