/******************************************************************************* * 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.terrain; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.Extent; import gov.nasa.worldwind.geom.LatLon; import gov.nasa.worldwind.geom.Sector; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.terrain.RectangularTessellator; import gov.nasa.worldwind.terrain.SectorGeometry; import gov.nasa.worldwind.terrain.SectorGeometryList; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.OGLStackHandler; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.media.opengl.GL2; /** * Subclass of the {@link RectangularTessellator} that adds several features: * <ul> * <li>Ability to enable/disable depth testing for the elevation wireframe.</li> * <li>Ability to disable backface culling for tiled image layers.</li> * <li>Smart skirts: instead of skirts that go down to the minimum elevation, * smart skirts are skirts generated from the vertices of neighbouring tiles, * ensuring that no gaps exist, but also ensuring that skirts don't get in the * way of sub-surface navigation.</li> * </ul> * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class WireframeRectangularTessellator extends RectangularTessellator { private boolean wireframeDepthTesting = true; private boolean backfaceCulling = false; private boolean smartSkirts = true; /** * @return Is depth testing enabled for the elevation model wireframe? */ public boolean isWireframeDepthTesting() { return wireframeDepthTesting; } /** * Enable/disable depth testing for the elevation model wireframe. * * @param wireframeDepthTesting */ public void setWireframeDepthTesting(boolean wireframeDepthTesting) { this.wireframeDepthTesting = wireframeDepthTesting; } /** * @return Is backface culling enabled for surface tiles (tiled image * layers)? */ public boolean isBackfaceCulling() { return backfaceCulling; } /** * Enable/disable backface culling for surface tiles. * * @param backfaceCulling */ public void setBackfaceCulling(boolean backfaceCulling) { this.backfaceCulling = backfaceCulling; } /** * @return Are smart skirts enabled? Smart skirts are skirts that use * neighbouring tile vertices as skirts, instead of skirts that go * down to the minimum elevation. This helps sub-surface rendering. */ public boolean isSmartSkirts() { return smartSkirts; } /** * Enable/disable smart skirts. * * @param smartSkirts */ public void setSmartSkirts(boolean smartSkirts) { this.smartSkirts = smartSkirts; } @Override protected void renderWireframe(DrawContext dc, RectTile tile, boolean showTriangles, boolean showTileBoundary) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } RenderInfo ri = tile.getRi(); if (ri == null) { String msg = Logging.getMessage("nullValue.RenderInfoIsNull"); Logging.logger().severe(msg); throw new IllegalStateException(msg); } dc.getView().pushReferenceCenter(dc, ri.getReferenceCenter()); GL2 gl = dc.getGL().getGL2(); gl.glPushAttrib(GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_POLYGON_BIT | GL2.GL_TEXTURE_BIT | GL2.GL_ENABLE_BIT | GL2.GL_CURRENT_BIT); //gl.glEnable(GL.GL_BLEND); //gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); //gl.glDisable(javax.media.opengl.GL.GL_DEPTH_TEST); gl.glEnable(GL2.GL_CULL_FACE); gl.glCullFace(GL2.GL_BACK); gl.glDisable(GL2.GL_TEXTURE_2D); gl.glColor4d(0.6, 0.8, 0.8, 1.0); gl.glPolygonMode(GL2.GL_FRONT, GL2.GL_LINE); if (isWireframeDepthTesting()) { gl.glEnable(GL2.GL_POLYGON_OFFSET_LINE); gl.glPolygonOffset(-1, 1); } else { gl.glDisable(GL2.GL_DEPTH_TEST); } if (showTriangles) { OGLStackHandler ogsh = new OGLStackHandler(); try { ogsh.pushClientAttrib(gl, GL2.GL_CLIENT_VERTEX_ARRAY_BIT); gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); FloatBuffer vertices = ri.getVertices(); IntBuffer indices = ri.getIndices(); gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices.rewind()); gl.glDrawElements(GL2.GL_TRIANGLE_STRIP, indices.limit(), GL2.GL_UNSIGNED_INT, indices.rewind()); } finally { ogsh.pop(gl); } } dc.getView().popReferenceCenter(dc); gl.glPopAttrib(); if (showTileBoundary) { this.renderPatchBoundary(dc, tile); } } @Override protected long render(DrawContext dc, RectTile tile, int numTextureUnits) { if (!backfaceCulling) { dc.getGL().glDisable(GL2.GL_CULL_FACE); } return super.render(dc, tile, numTextureUnits); } @Override public synchronized SectorGeometryList tessellate(DrawContext dc) { SectorGeometryList currentTiles = super.tessellate(dc); if (isMakeTileSkirts() && smartSkirts) { Map<RectTileKey, RowColRectTile> tileMap = new HashMap<RectTileKey, RowColRectTile>(); for (SectorGeometry t : currentTiles) { RowColRectTile tile = (RowColRectTile) t; RectTileKey tileKey = new RectTileKey(tile.getLevel(), tile.getRow(), tile.getColumn()); tileMap.put(tileKey, tile); } for (SectorGeometry tile : currentTiles) { fixSkirts(dc, (RowColRectTile) tile, tileMap); } } return currentTiles; } @Override protected void makeVerts(DrawContext dc, RectTile tile) { //vertices are rebuilt if required in the super method ((RowColRectTile) tile).rebuiltVertices = false; super.makeVerts(dc, tile); } @Override public boolean buildVerts(DrawContext dc, RectTile tile, boolean makeSkirts) { //mark the tile's vertices as rebuilt ((RowColRectTile) tile).rebuiltVertices = true; return super.buildVerts(dc, tile, false); } @Override protected ArrayList<LatLon> computeLocations(RectTile tile) { //Changed to remove the latMax/lonMax calculations, as the small difference in the double //lat/lon locations between the skirts and the tile edges were causing large differences //in the returned elevation. Perhaps an ElevationModel bug? int density = tile.getDensity(); int numVertices = (density + 3) * (density + 3); Sector sector = tile.getSector(); Angle dLat = sector.getDeltaLat().divide(density); Angle lat = sector.getMinLatitude(); Angle lonMin = sector.getMinLongitude(); Angle dLon = sector.getDeltaLon().divide(density); ArrayList<LatLon> latlons = new ArrayList<LatLon>(numVertices); for (int j = 0; j <= density + 2; j++) { Angle lon = lonMin; for (int i = 0; i <= density + 2; i++) { latlons.add(new LatLon(lat, lon)); if (i != 0 && i <= density) { lon = lon.add(dLon); } if (lon.degrees < -180) { lon = Angle.NEG180; } else if (lon.degrees > 180) { lon = Angle.POS180; } } if (j != 0 && j <= density) { lat = lat.add(dLat); } } return latlons; } protected void fixSkirts(DrawContext dc, RowColRectTile tile, Map<RectTileKey, RowColRectTile> tileMap) { int row = tile.getRow(); int column = tile.getColumn(); int level = tile.getLevel(); int sRow = row / 2; int sColumn = column / 2; int sLevel = level - 1; boolean topHalf = row % 2 == 0; boolean leftHalf = column % 2 == 0; RowColRectTile sLeft = leftHalf ? tileMap.get(new RectTileKey(sLevel, sRow, sColumn - 1)) : null; RowColRectTile sRight = !leftHalf ? tileMap.get(new RectTileKey(sLevel, sRow, sColumn + 1)) : null; RowColRectTile sTop = topHalf ? tileMap.get(new RectTileKey(sLevel, sRow - 1, sColumn)) : null; RowColRectTile sBottom = !topHalf ? tileMap.get(new RectTileKey(sLevel, sRow + 1, sColumn)) : null; RowColRectTile left = sLeft == null ? tileMap.get(new RectTileKey(level, row, column - 1)) : null; RowColRectTile top = sTop == null ? tileMap.get(new RectTileKey(level, row - 1, column)) : null; boolean anyRebuilt = tile.rebuiltVertices || (sLeft != null && sLeft.rebuiltVertices) || (sRight != null && sRight.rebuiltVertices) || (sTop != null && sTop.rebuiltVertices) || (sBottom != null && sBottom.rebuiltVertices) || (left != null && left.rebuiltVertices) || (top != null && top.rebuiltVertices); if (!anyRebuilt) { return; } FloatBuffer vertices = tile.getRi().getVertices(); Vec4 refCenter = tile.getRi().getReferenceCenter(); int density = tile.getDensity(); int size = density + 3; if (sLeft != null) { FloatBuffer leftVertices = sLeft.getRi().getVertices(); Vec4 leftRefCenter = sLeft.getRi().getReferenceCenter(); int srcStart = topHalf ? 1 : density / 2 + 1; subdivideVerticesFromNeighboringSuperTile(leftVertices, vertices, leftRefCenter, refCenter, size, srcStart, size - 1, 0, true); } else if (left != null) { FloatBuffer leftVertices = left.getRi().getVertices(); Vec4 leftRefCenter = left.getRi().getReferenceCenter(); copyVerticesFromNeighboringTile(leftVertices, vertices, leftRefCenter, refCenter, size, size - 1, 0, true); } if (sRight != null) { FloatBuffer rightVertices = sRight.getRi().getVertices(); Vec4 rightRefCenter = sRight.getRi().getReferenceCenter(); int srcStart = topHalf ? 1 : density / 2 + 1; subdivideVerticesFromNeighboringSuperTile(rightVertices, vertices, rightRefCenter, refCenter, size, srcStart, 0, size - 1, true); } if (sTop != null) { FloatBuffer topVertices = sTop.getRi().getVertices(); Vec4 topRefCenter = sTop.getRi().getReferenceCenter(); int srcStart = leftHalf ? 1 : density / 2 + 1; subdivideVerticesFromNeighboringSuperTile(topVertices, vertices, topRefCenter, refCenter, size, srcStart, size - 1, 0, false); } else if (top != null) { FloatBuffer topVertices = top.getRi().getVertices(); Vec4 topRefCenter = top.getRi().getReferenceCenter(); copyVerticesFromNeighboringTile(topVertices, vertices, topRefCenter, refCenter, size, size - 1, 0, false); } if (sBottom != null) { FloatBuffer bottomVertices = sBottom.getRi().getVertices(); Vec4 bottomRefCenter = sBottom.getRi().getReferenceCenter(); int srcStart = leftHalf ? 1 : density / 2 + 1; subdivideVerticesFromNeighboringSuperTile(bottomVertices, vertices, bottomRefCenter, refCenter, size, srcStart, 0, size - 1, false); } if (dc.getGLRuntimeCapabilities().isUseVertexBufferObject()) { GL2 gl = dc.getGL().getGL2(); OGLStackHandler ogsh = new OGLStackHandler(); try { ogsh.pushClientAttrib(gl, GL2.GL_CLIENT_VERTEX_ARRAY_BIT); int[] vboIds = (int[]) dc.getGpuResourceCache().get(tile.getRi().getVboCacheKey()); if (vboIds != null) { gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vboIds[0]); gl.glBufferData(GL2.GL_ARRAY_BUFFER, vertices.limit() * 4, vertices.rewind(), GL2.GL_DYNAMIC_DRAW); gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); } } finally { ogsh.pop(gl); } } } private void subdivideVerticesFromNeighboringSuperTile(FloatBuffer src, FloatBuffer dst, Vec4 srcRefCenter, Vec4 dstRefCenter, int size, int srcStart, int srcRC, int dstRC, boolean column) { int offsetFactor = (column ? 1 : size) * 3; int srcOffset = srcRC * offsetFactor; int dstOffset = dstRC * offsetFactor; int stride = (column ? size : 1) * 3; Vec4 last = null; for (int di = 1, si = srcStart; di < size - 1; di += 2, si++) { int srcIndex = srcOffset + si * stride; Vec4 current = new Vec4(src.get(srcIndex), src.get(srcIndex + 1), src.get(srcIndex + 2)); current = current.add3(srcRefCenter).subtract3(dstRefCenter); Vec4 previous = last == null ? current : last.add3(current).divide3(2); last = current; int dstIndex = dstOffset + (di - 1) * stride; dst.put(dstIndex, (float) previous.x).put(dstIndex + 1, (float) previous.y) .put(dstIndex + 2, (float) previous.z); dstIndex += stride; dst.put(dstIndex, (float) current.x).put(dstIndex + 1, (float) current.y) .put(dstIndex + 2, (float) current.z); if (di >= size - 2) { dstIndex += stride; dst.put(dstIndex, (float) current.x).put(dstIndex + 1, (float) current.y) .put(dstIndex + 2, (float) current.z); } } } private void copyVerticesFromNeighboringTile(FloatBuffer src, FloatBuffer dst, Vec4 srcRefCenter, Vec4 dstRefCenter, int size, int srcRC, int dstRC, boolean column) { int offsetFactor = (column ? 1 : size) * 3; int srcOffset = srcRC * offsetFactor; int dstOffset = dstRC * offsetFactor; int stride = (column ? size : 1) * 3; for (int i = 0; i < size; i++) { //don't use skirts to copy from int srcIndex = srcOffset + i * stride; int dstIndex = dstOffset + i * stride; dst.put(dstIndex, src.get(srcIndex) + (float) (srcRefCenter.x - dstRefCenter.x)); dst.put(dstIndex + 1, src.get(srcIndex + 1) + (float) (srcRefCenter.y - dstRefCenter.y)); dst.put(dstIndex + 2, src.get(srcIndex + 2) + (float) (srcRefCenter.z - dstRefCenter.z)); } } @Override protected RectTile[] split(DrawContext dc, RectTile tile) { //override the split() function to speed up the row/column calculation //we don't need to override the createTopLevelTiles() function, as it's only called once Sector[] sectors = tile.getSector().subdivide(); int row = ((RowColRectTile) tile).getRow() * 2; int column = ((RowColRectTile) tile).getColumn() * 2; RectTile[] subTiles = new RectTile[4]; subTiles[0] = this.createTile(dc, sectors[0], tile.getLevel() + 1, row, column); subTiles[1] = this.createTile(dc, sectors[1], tile.getLevel() + 1, row, column + 1); subTiles[2] = this.createTile(dc, sectors[2], tile.getLevel() + 1, row + 1, column); subTiles[3] = this.createTile(dc, sectors[3], tile.getLevel() + 1, row + 1, column + 1); return subTiles; } @Override protected RectTile createTile(DrawContext dc, Sector tileSector, int level) { double deltaLat = 180d / DEFAULT_NUM_LAT_SUBDIVISIONS; double deltaLon = 360d / DEFAULT_NUM_LON_SUBDIVISIONS; LatLon centroid = tileSector.getCentroid(); int row = getTileY(centroid.latitude, Angle.NEG90, level, deltaLat); int column = getTileX(centroid.longitude, Angle.NEG180, level, deltaLon); return createTile(dc, tileSector, level, row, column); } protected RectTile createTile(DrawContext dc, Sector tileSector, int level, int row, int column) { Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), tileSector); double cellSize = tileSector.getDeltaLatRadians() * dc.getGlobe().getRadius() / this.density; return new RowColRectTile(this, extent, level, this.density, tileSector, cellSize, row, column); } protected static int getTileX(Angle longitude, Angle longitudeOrigin, int level, double lztsd) { double layerpow = Math.pow(0.5, level); double X = (longitude.degrees - longitudeOrigin.degrees) / (lztsd * layerpow); return (int) X; } protected static int getTileY(Angle latitude, Angle latitudeOrigin, int level, double lztsd) { double layerpow = Math.pow(0.5, level); double Y = (latitude.degrees - latitudeOrigin.degrees) / (lztsd * layerpow); return (int) Y; } /** * Subclass of {@link RectTile} that store the tile's row/column, so that * neighbouring tiles can easily be calculated for the smart skirts. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ protected static class RowColRectTile extends RectTile { protected boolean rebuiltVertices = false; protected final int row; protected final int column; public RowColRectTile(RectangularTessellator tessellator, Extent extent, int level, int density, Sector sector, double cellSize, int row, int column) { super(tessellator, extent, level, density, sector, cellSize); this.row = row; this.column = column; } @Override public int getLevel() { return level; } public int getRow() { return row; } public int getColumn() { return column; } @Override public String toString() { return "(" + level + "," + row + "," + column + ")"; } } protected static class RectTileKey { protected final int level; protected final int row; protected final int column; public RectTileKey(int level, int row, int column) { this.level = level; this.row = row; this.column = column; } @Override public int hashCode() { int result; result = level; result = 29 * result + row; result = 29 * result + column; return result; } @Override public boolean equals(Object obj) { if (obj instanceof RectTileKey) { RectTileKey key = (RectTileKey) obj; return key.level == this.level && key.column == this.column && key.row == this.row; } return false; } } }