/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.globes;
import com.sun.opengl.util.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.pick.*;
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;
/**
* @author tag
* @version $Id: RectangularTessellator.java 5273 2008-05-02 00:42:05Z tgaskins $
*/
public class RectangularTessellator extends WWObjectImpl implements Tessellator
{
protected static class RenderInfo
{
private final int density;
private final int resolution;
private final Vec4 referenceCenter;
private final DoubleBuffer vertices;
private final DoubleBuffer texCoords;
private final IntBuffer indices;
private RenderInfo(int density, DoubleBuffer vertices, DoubleBuffer texCoords, Vec4 refCenter, int resolution)
{
this.density = density;
this.vertices = vertices;
this.texCoords = texCoords;
this.referenceCenter = refCenter;
this.indices = getIndices(this.density);
this.resolution = resolution;
}
private long getSizeInBytes()
{
// Texture coordinates are shared among all tiles of the same density, so do not count towards size.
// 8 references, doubles in buffer.
return 8 * 4 + (this.vertices.limit()) * Double.SIZE;
}
}
private static class RectTile implements SectorGeometry
{
private final RectangularTessellator tessellator;
private final int level;
private final Sector sector;
private final int density;
private final double log10CellSize;
private Extent extent; // extent of sector in object coordinates
private RenderInfo ri;
private int minColorCode = 0;
private int maxColorCode = 0;
public RectTile(RectangularTessellator tessellator, Extent extent, int level, int density, Sector sector,
double cellSize)
{
this.tessellator = tessellator;
this.level = level;
this.density = density;
this.sector = sector;
this.extent = extent;
this.log10CellSize = Math.log10(cellSize);
}
public Sector getSector()
{
return this.sector;
}
public Extent getExtent()
{
return this.extent;
}
public void renderMultiTexture(DrawContext dc, int numTextureUnits)
{
this.tessellator.renderMultiTexture(dc, this, numTextureUnits);
}
public void render(DrawContext dc)
{
this.tessellator.render(dc, this);
}
public void renderWireframe(DrawContext dc, boolean showTriangles, boolean showTileBoundary)
{
this.tessellator.renderWireframe(dc, this, showTriangles, showTileBoundary);
}
public void renderBoundingVolume(DrawContext dc)
{
this.tessellator.renderBoundingVolume(dc, this);
}
public PickedObject[] pick(DrawContext dc, List<Point> pickPoints)
{
return this.tessellator.pick(dc, this, pickPoints);
}
public void pick(DrawContext dc, Point pickPoint)
{
this.tessellator.pick(dc, this, pickPoint);
}
public Vec4 getSurfacePoint(Angle latitude, Angle longitude, double metersOffset)
{
return this.tessellator.getSurfacePoint(this, latitude, longitude, metersOffset);
}
}
private static class CacheKey
{
private final Sector sector;
private final int resolution;
private final int density;
private final Globe globe;
private final Object globeStateKey;
private final double verticalExaggeration;
public CacheKey(Globe globe, Sector sector, double verticalExaggeration, int resolution, int density)
{
this.sector = sector;
this.resolution = resolution;
this.density = density;
this.globe = globe;
this.globeStateKey = globe.getStateKey();
this.verticalExaggeration = verticalExaggeration;
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
if (density != cacheKey.density) return false;
if (resolution != cacheKey.resolution) return false;
if (Double.compare(cacheKey.verticalExaggeration, verticalExaggeration) != 0) return false;
if (globe != null ? !globe.equals(cacheKey.globe) : cacheKey.globe != null) return false;
if (globeStateKey != null ? !globeStateKey.equals(cacheKey.globeStateKey) : cacheKey.globeStateKey != null)
return false;
//noinspection RedundantIfStatement
if (sector != null ? !sector.equals(cacheKey.sector) : cacheKey.sector != null) return false;
return true;
}
public int hashCode()
{
int result;
long temp;
result = (sector != null ? sector.hashCode() : 0);
result = 31 * result + resolution;
result = 31 * result + density;
result = 31 * result + (globe != null ? globe.hashCode() : 0);
result = 31 * result + (globeStateKey != null ? globeStateKey.hashCode() : 0);
temp = verticalExaggeration != +0.0d ? Double.doubleToLongBits(verticalExaggeration) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
// TODO: Make all this configurable
private static final double DEFAULT_LOG10_RESOLUTION_TARGET = 1.3;
private static final int DEFAULT_MAX_LEVEL = 12;
private static final int DEFAULT_NUM_LAT_SUBDIVISIONS = 5;
private static final int DEFAULT_NUM_LON_SUBDIVISIONS = 10;
private static final int DEFAULT_DENSITY = 20;
private static final String CACHE_NAME = "Terrain";
private static final String CACHE_ID = RectangularTessellator.class.getName();
// Tri-strip indices and texture coordinates. These depend only on density and can therefore be statically cached.
private static final HashMap<Integer, DoubleBuffer> parameterizations = new HashMap<Integer, DoubleBuffer>();
private static final HashMap<Integer, IntBuffer> indexLists = new HashMap<Integer, IntBuffer>();
private ArrayList<RectTile> topLevels;
private PickSupport pickSupport = new PickSupport();
private SectorGeometryList currentTiles = new SectorGeometryList();
private Frustum currentFrustum;
private Sector currentCoverage; // union of all tiles selected during call to render()
private boolean makeTileSkirts = true;
private int currentLevel;
private int maxLevel = DEFAULT_MAX_LEVEL;
private Globe globe;
private int density = DEFAULT_DENSITY;
public SectorGeometryList tessellate(DrawContext dc)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (dc.getView() == null)
{
String msg = Logging.getMessage("nullValue.ViewIsNull");
Logging.logger().severe(msg);
throw new IllegalStateException(msg);
}
if (!WorldWind.getMemoryCacheSet().containsCache(CACHE_ID))
{
long size = Configuration.getLongValue(AVKey.SECTOR_GEOMETRY_CACHE_SIZE, 20000000L);
MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
cache.setName(CACHE_NAME);
WorldWind.getMemoryCacheSet().addCache(CACHE_ID, cache);
}
if (this.topLevels == null)
this.topLevels = this.createTopLevelTiles(dc);
this.currentTiles.clear();
this.currentLevel = 0;
this.currentCoverage = null;
this.currentFrustum = dc.getView().getFrustumInModelCoordinates();
for (RectTile tile : this.topLevels)
{
this.selectVisibleTiles(dc, tile);
}
this.currentTiles.setSector(this.currentCoverage);
for (SectorGeometry tile : this.currentTiles)
{
this.makeVerts(dc, (RectTile) tile);
}
return this.currentTiles;
}
private ArrayList<RectTile> createTopLevelTiles(DrawContext dc)
{
ArrayList<RectTile> tops =
new ArrayList<RectTile>(DEFAULT_NUM_LAT_SUBDIVISIONS * DEFAULT_NUM_LON_SUBDIVISIONS);
this.globe = dc.getGlobe();
double deltaLat = 180d / DEFAULT_NUM_LAT_SUBDIVISIONS;
double deltaLon = 360d / DEFAULT_NUM_LON_SUBDIVISIONS;
Angle lastLat = Angle.NEG90;
for (int row = 0; row < DEFAULT_NUM_LAT_SUBDIVISIONS; row++)
{
Angle lat = lastLat.addDegrees(deltaLat);
if (lat.getDegrees() + 1d > 90d)
lat = Angle.POS90;
Angle lastLon = Angle.NEG180;
for (int col = 0; col < DEFAULT_NUM_LON_SUBDIVISIONS; col++)
{
Angle lon = lastLon.addDegrees(deltaLon);
if (lon.getDegrees() + 1d > 180d)
lon = Angle.POS180;
Sector tileSector = new Sector(lastLat, lat, lastLon, lon);
tops.add(this.createTile(dc, tileSector, 0));
lastLon = lon;
}
lastLat = lat;
}
return tops;
}
private RectTile createTile(DrawContext dc, Sector tileSector, int level)
{
Cylinder cylinder = dc.getGlobe().computeBoundingCylinder(dc.getVerticalExaggeration(), tileSector);
double cellSize = tileSector.getDeltaLatRadians() * dc.getGlobe().getRadius() / this.density;
return new RectTile(this, cylinder, level, this.density, tileSector, cellSize);
}
public boolean isMakeTileSkirts()
{
return makeTileSkirts;
}
public void setMakeTileSkirts(boolean makeTileSkirts)
{
this.makeTileSkirts = makeTileSkirts;
}
public int getTargetResolution(DrawContext dc, RectTile tile)
{
return dc.getGlobe().getElevationModel().getTargetResolution(dc, tile.sector, tile.density);
}
private void selectVisibleTiles(DrawContext dc, RectTile tile)
{
Extent extent = tile.getExtent();
if (extent != null && !extent.intersects(this.currentFrustum))
return;
if (this.currentLevel < this.maxLevel - 1 && this.needToSplit(dc, tile))
{
++this.currentLevel;
RectTile[] subtiles = this.split(dc, tile);
for (RectTile child : subtiles)
{
this.selectVisibleTiles(dc, child);
}
--this.currentLevel;
return;
}
this.currentCoverage = tile.getSector().union(this.currentCoverage);
this.currentTiles.add(tile);
}
private boolean needToSplit(DrawContext dc, RectTile tile)
{
Vec4[] corners = tile.sector.computeCornerPoints(dc.getGlobe());
Vec4 centerPoint = tile.sector.computeCenterPoint(dc.getGlobe());
View view = dc.getView();
double d1 = view.getEyePoint().distanceTo3(corners[0]);
double d2 = view.getEyePoint().distanceTo3(corners[1]);
double d3 = view.getEyePoint().distanceTo3(corners[2]);
double d4 = view.getEyePoint().distanceTo3(corners[3]);
double d5 = view.getEyePoint().distanceTo3(centerPoint);
double minDistance = d1;
if (d2 < minDistance)
minDistance = d2;
if (d3 < minDistance)
minDistance = d3;
if (d4 < minDistance)
minDistance = d4;
if (d5 < minDistance)
minDistance = d5;
double logDist = Math.log10(minDistance);
boolean useTile = tile.log10CellSize <= (logDist - DEFAULT_LOG10_RESOLUTION_TARGET);
return !useTile;
}
private RectTile[] split(DrawContext dc, RectTile tile)
{
Sector[] sectors = tile.sector.subdivide();
RectTile[] subTiles = new RectTile[4];
subTiles[0] = this.createTile(dc, sectors[0], tile.level + 1);
subTiles[1] = this.createTile(dc, sectors[1], tile.level + 1);
subTiles[2] = this.createTile(dc, sectors[2], tile.level + 1);
subTiles[3] = this.createTile(dc, sectors[3], tile.level + 1);
return subTiles;
}
private RectangularTessellator.CacheKey createCacheKey(DrawContext dc, RectTile tile, int resolution)
{
return new CacheKey(dc.getGlobe(), tile.sector, dc.getVerticalExaggeration(), resolution, tile.density);
}
private void makeVerts(DrawContext dc, RectTile tile)
{
int resolution = this.getTargetResolution(dc, tile);
MemoryCache cache = WorldWind.getMemoryCache(CACHE_ID);
CacheKey cacheKey = this.createCacheKey(dc, tile, resolution);
tile.ri = (RenderInfo) cache.getObject(cacheKey);
if (tile.ri != null)
return;
tile.ri = this.buildVerts(dc, tile, resolution, this.makeTileSkirts);
if (tile.ri != null && tile.ri.resolution >= 0)
{
cacheKey = this.createCacheKey(dc, tile, tile.ri.resolution);
cache.add(cacheKey, tile.ri, tile.ri.getSizeInBytes());
}
}
public RenderInfo buildVerts(DrawContext dc, RectTile tile, int resolution, boolean makeSkirts)
{
int density = tile.density;
int numVertices = (density + 3) * (density + 3);
java.nio.DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 3);
Globe globe = dc.getGlobe();
ElevationModel.Elevations elevations = globe.getElevationModel().getElevations(tile.sector, resolution);
Angle latMax = tile.sector.getMaxLatitude();
Angle dLat = tile.sector.getDeltaLat().divide(density);
Angle lonMin = tile.sector.getMinLongitude();
Angle lonMax = tile.sector.getMaxLongitude();
Angle dLon = tile.sector.getDeltaLon().divide(density);
int iv = 0;
Angle lat = tile.sector.getMinLatitude();
double verticalExaggeration = dc.getVerticalExaggeration();
double exaggeratedMinElevation = makeSkirts ? globe.getMinElevation() * verticalExaggeration : 0;
LatLon centroid = tile.sector.getCentroid();
Vec4 refCenter = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(), 0d);
for (int j = 0; j <= density + 2; j++)
{
Angle lon = lonMin;
for (int i = 0; i <= density + 2; i++)
{
double elevation = verticalExaggeration * elevations.getElevation(lat.radians, lon.radians);
if (j == 0 || j >= tile.density + 2 || i == 0 || i >= tile.density + 2)
{ // use abs to account for negative elevation.
elevation -= exaggeratedMinElevation >= 0 ? exaggeratedMinElevation : -exaggeratedMinElevation;
}
Vec4 p = globe.computePointFromPosition(lat, lon, elevation);
verts.put(iv++, p.x - refCenter.x).put(iv++, p.y - refCenter.y).put(iv++, p.z - refCenter.z);
if (i > density)
lon = lonMax;
else if (i != 0)
lon = lon.add(dLon);
}
if (j > density)
lat = latMax;
else if (j != 0)
lat = lat.add(dLat);
}
return new RenderInfo(density, verts, getTextureCoordinates(density), refCenter, elevations.getResolution());
}
private void renderMultiTexture(DrawContext dc, RectTile tile, int numTextureUnits)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (numTextureUnits < 1)
{
String msg = Logging.getMessage("generic.NumTextureUnitsLessThanOne");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.render(dc, tile, numTextureUnits);
}
private void render(DrawContext dc, RectTile tile)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.render(dc, tile, 1);
}
private long render(DrawContext dc, RectTile tile, int numTextureUnits)
{
if (tile.ri == null)
{
String msg = Logging.getMessage("nullValue.RenderInfoIsNull");
Logging.logger().severe(msg);
throw new IllegalStateException(msg);
}
dc.getView().pushReferenceCenter(dc, tile.ri.referenceCenter);
GL gl = dc.getGL();
gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL.GL_DOUBLE, 0, tile.ri.vertices.rewind());
for (int i = 0; i < numTextureUnits; i++)
{
gl.glClientActiveTexture(GL.GL_TEXTURE0 + i);
gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL.GL_DOUBLE, 0, tile.ri.texCoords.rewind());
}
gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, tile.ri.indices.limit(),
javax.media.opengl.GL.GL_UNSIGNED_INT, tile.ri.indices.rewind());
gl.glPopClientAttrib();
dc.getView().popReferenceCenter(dc);
return tile.ri.indices.limit() - 2; // return number of triangles rendered
}
private 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);
}
if (tile.ri == null)
{
String msg = Logging.getMessage("nullValue.RenderInfoIsNull");
Logging.logger().severe(msg);
throw new IllegalStateException(msg);
}
java.nio.IntBuffer indices = getIndices(tile.ri.density);
indices.rewind();
dc.getView().pushReferenceCenter(dc, tile.ri.referenceCenter);
javax.media.opengl.GL gl = dc.getGL();
gl.glPushAttrib(
GL.GL_DEPTH_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT | GL.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(javax.media.opengl.GL.GL_CULL_FACE);
gl.glCullFace(javax.media.opengl.GL.GL_BACK);
gl.glDisable(javax.media.opengl.GL.GL_TEXTURE_2D);
gl.glColor4d(1d, 1d, 1d, 0.2);
gl.glPolygonMode(javax.media.opengl.GL.GL_FRONT, javax.media.opengl.GL.GL_LINE);
if (showTriangles)
{
gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL.GL_DOUBLE, 0, tile.ri.vertices);
gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, indices.limit(),
javax.media.opengl.GL.GL_UNSIGNED_INT, indices);
gl.glPopClientAttrib();
}
dc.getView().popReferenceCenter(dc);
if (showTileBoundary)
this.renderPatchBoundary(dc, tile, gl);
gl.glPopAttrib();
}
private void renderPatchBoundary(DrawContext dc, RectTile tile, GL gl)
{
// TODO: Currently only works if called from renderWireframe because no state is set here.
// TODO: Draw the boundary using the vertices along the boundary rather than just at the corners.
gl.glColor4d(1d, 0, 0, 1d);
Vec4[] corners = tile.sector.computeCornerPoints(dc.getGlobe());
gl.glBegin(javax.media.opengl.GL.GL_QUADS);
gl.glVertex3d(corners[0].x, corners[0].y, corners[0].z);
gl.glVertex3d(corners[1].x, corners[1].y, corners[1].z);
gl.glVertex3d(corners[2].x, corners[2].y, corners[2].z);
gl.glVertex3d(corners[3].x, corners[3].y, corners[3].z);
gl.glEnd();
}
private void renderBoundingVolume(DrawContext dc, RectTile tile)
{
Extent extent = tile.getExtent();
if (extent == null)
return;
if (extent instanceof Cylinder)
((Cylinder) extent).render(dc);
}
private PickedObject[] pick(DrawContext dc, RectTile tile, List<Point> pickPoints)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (pickPoints.size() == 0)
return null;
if (tile.ri == null)
return null;
PickedObject[] pos = new PickedObject[pickPoints.size()];
this.renderTrianglesWithUniqueColors(dc, tile);
for (int i = 0; i < pickPoints.size(); i++)
{
pos[i] = this.resolvePick(dc, tile, pickPoints.get(i));
}
return pos;
}
private void pick(DrawContext dc, RectTile tile, Point pickPoint)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (tile.ri == null)
return;
renderTrianglesWithUniqueColors(dc, tile);
PickedObject po = this.resolvePick(dc, tile, pickPoint);
if (po != null)
dc.addPickedObject(po);
}
private void renderTrianglesWithUniqueColors(DrawContext dc, RectTile tile)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (tile.ri.vertices == null)
return;
tile.ri.vertices.rewind();
tile.ri.indices.rewind();
javax.media.opengl.GL gl = dc.getGL();
if (null != tile.ri.referenceCenter)
dc.getView().pushReferenceCenter(dc, tile.ri.referenceCenter);
tile.minColorCode = dc.getUniquePickColor().getRGB();
int trianglesNum = tile.ri.indices.capacity() - 2;
gl.glBegin(GL.GL_TRIANGLES);
for (int i = 0; i < trianglesNum; i++)
{
java.awt.Color color = dc.getUniquePickColor();
gl.glColor3ub((byte) (color.getRed() & 0xFF),
(byte) (color.getGreen() & 0xFF),
(byte) (color.getBlue() & 0xFF));
int vIndex = 3 * tile.ri.indices.get(i);
gl.glVertex3d(tile.ri.vertices.get(vIndex), tile.ri.vertices.get(vIndex + 1), tile.ri.vertices.get(
vIndex + 2));
vIndex = 3 * tile.ri.indices.get(i + 1);
gl.glVertex3d(tile.ri.vertices.get(vIndex), tile.ri.vertices.get(vIndex + 1), tile.ri.vertices.get(
vIndex + 2));
vIndex = 3 * tile.ri.indices.get(i + 2);
gl.glVertex3d(tile.ri.vertices.get(vIndex), tile.ri.vertices.get(vIndex + 1), tile.ri.vertices.get(
vIndex + 2));
}
gl.glEnd();
tile.maxColorCode = dc.getUniquePickColor().getRGB();
if (null != tile.ri.referenceCenter)
dc.getView().popReferenceCenter(dc);
}
private PickedObject resolvePick(DrawContext dc, RectTile tile, Point pickPoint)
{
int colorCode = this.pickSupport.getTopColor(dc, pickPoint);
if (colorCode < tile.minColorCode || colorCode > tile.maxColorCode)
return null;
double EPSILON = (double) 0.00001f;
int triangleIndex = colorCode - tile.minColorCode - 1;
if (tile.ri.indices == null || triangleIndex >= (tile.ri.indices.capacity() - 2))
return null;
double centerX = tile.ri.referenceCenter.x;
double centerY = tile.ri.referenceCenter.y;
double centerZ = tile.ri.referenceCenter.z;
int vIndex = 3 * tile.ri.indices.get(triangleIndex);
Vec4 v0 = new Vec4((tile.ri.vertices.get(vIndex++) + centerX),
(tile.ri.vertices.get(vIndex++) + centerY),
(tile.ri.vertices.get(vIndex) + centerZ));
vIndex = 3 * tile.ri.indices.get(triangleIndex + 1);
Vec4 v1 = new Vec4((tile.ri.vertices.get(vIndex++) + centerX),
(tile.ri.vertices.get(vIndex++) + centerY),
(tile.ri.vertices.get(vIndex) + centerZ));
vIndex = 3 * tile.ri.indices.get(triangleIndex + 2);
Vec4 v2 = new Vec4((tile.ri.vertices.get(vIndex++) + centerX),
(tile.ri.vertices.get(vIndex++) + centerY),
(tile.ri.vertices.get(vIndex) + centerZ));
// get triangle edge vectors and plane normal
Vec4 e1 = v1.subtract3(v0);
Vec4 e2 = v2.subtract3(v0);
Vec4 N = e1.cross3(e2); // if N is 0, the triangle is degenerate, we are not dealing with it
Line ray = dc.getView().computeRayFromScreenPoint(pickPoint.getX(), pickPoint.getY());
Vec4 w0 = ray.getOrigin().subtract3(v0);
double a = -N.dot3(w0);
double b = N.dot3(ray.getDirection());
if (java.lang.Math.abs(b) < EPSILON) // ray is parallel to triangle plane
return null; // if a == 0 , ray lies in triangle plane
double r = a / b;
Vec4 intersect = ray.getOrigin().add3(ray.getDirection().multiply3(r));
Position pp = dc.getGlobe().computePositionFromPoint(intersect);
// Draw the elevation from the elevation model, not the geode.
double elev = dc.getGlobe().getElevation(pp.getLatitude(), pp.getLongitude());
Position p = new Position(pp.getLatitude(), pp.getLongitude(), elev);
return new PickedObject(pickPoint, colorCode, p, pp.getLatitude(), pp.getLongitude(), elev, true);
}
private Vec4 getSurfacePoint(RectTile tile, Angle latitude, Angle longitude, double metersOffset)
{
Vec4 result = this.getSurfacePoint(tile, latitude, longitude);
if (metersOffset != 0 && result != null)
result = applyOffset(this.globe, result, metersOffset);
return result;
}
/**
* Offsets <code>point</code> by <code>metersOffset</code> meters.
*
* @param globe the <code>Globe</code> from which to offset
* @param point the <code>Vec4</code> to offset
* @param metersOffset the magnitude of the offset
* @return <code>point</code> offset along its surface normal as if it were on <code>globe</code>
*/
private static Vec4 applyOffset(Globe globe, Vec4 point, double metersOffset)
{
Vec4 normal = globe.computeSurfaceNormalAtPoint(point);
point = Vec4.fromLine3(point, metersOffset, normal);
return point;
}
private Vec4 getSurfacePoint(RectTile tile, Angle latitude, Angle longitude)
{
if (latitude == null || longitude == null)
{
String msg = Logging.getMessage("nullValue.LatLonIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (!tile.sector.contains(latitude, longitude))
{
// not on this geometry
return null;
}
if (tile.ri == null)
return null;
double lat = latitude.getDegrees();
double lon = longitude.getDegrees();
double bottom = tile.sector.getMinLatitude().getDegrees();
double top = tile.sector.getMaxLatitude().getDegrees();
double left = tile.sector.getMinLongitude().getDegrees();
double right = tile.sector.getMaxLongitude().getDegrees();
double leftDecimal = (lon - left) / (right - left);
double bottomDecimal = (lat - bottom) / (top - bottom);
int row = (int) (bottomDecimal * (tile.density));
int column = (int) (leftDecimal * (tile.density));
double l = createPosition(column, leftDecimal, tile.ri.density);
double h = createPosition(row, bottomDecimal, tile.ri.density);
Vec4 result = interpolate(row, column, l, h, tile.ri);
result = result.add3(tile.ri.referenceCenter);
return result;
}
/**
* Computes from a column (or row) number, and a given offset ranged [0,1] corresponding to the distance along the
* edge of this sector, where between this column and the next column the corresponding position will fall, in the
* range [0,1].
*
* @param start the number of the column or row to the left, below or on this position
* @param decimal the distance from the left or bottom of the current sector that this position falls
* @param density the number of intervals along the sector's side
* @return a decimal ranged [0,1] representing the position between two columns or rows, rather than between two
* edges of the sector
*/
private static double createPosition(int start, double decimal, int density)
{
double l = ((double) start) / (double) density;
double r = ((double) (start + 1)) / (double) density;
return (decimal - l) / (r - l);
}
/**
* Calculates a <code>Point</code> that sits at <code>xDec</code> offset from <code>column</code> to <code>column +
* 1</code> and at <code>yDec</code> offset from <code>row</code> to <code>row + 1</code>. Accounts for the
* diagonals.
*
* @param row represents the row which corresponds to a <code>yDec</code> value of 0
* @param column represents the column which corresponds to an <code>xDec</code> value of 0
* @param xDec constrained to [0,1]
* @param yDec constrained to [0,1]
* @param ri the render info holding the vertices, etc.
* @return a <code>Point</code> geometrically within or on the boundary of the quadrilateral whose bottom left
* corner is indexed by (<code>row</code>, <code>column</code>)
*/
private static Vec4 interpolate(int row, int column, double xDec, double yDec, RenderInfo ri)
{
row++;
column++;
int numVerticesPerEdge = ri.density + 3;
int bottomLeft = row * numVerticesPerEdge + column;
bottomLeft *= 3;
int numVertsTimesThree = numVerticesPerEdge * 3;
Vec4 bL = new Vec4(ri.vertices.get(bottomLeft), ri.vertices.get(bottomLeft + 1), ri.vertices.get(
bottomLeft + 2));
Vec4 bR = new Vec4(ri.vertices.get(bottomLeft + 3), ri.vertices.get(bottomLeft + 4),
ri.vertices.get(bottomLeft + 5));
bottomLeft += numVertsTimesThree;
Vec4 tL = new Vec4(ri.vertices.get(bottomLeft), ri.vertices.get(bottomLeft + 1), ri.vertices.get(
bottomLeft + 2));
Vec4 tR = new Vec4(ri.vertices.get(bottomLeft + 3), ri.vertices.get(bottomLeft + 4),
ri.vertices.get(bottomLeft + 5));
return interpolate(bL, bR, tR, tL, xDec, yDec);
}
/**
* Calculates the point at (xDec, yDec) in the two triangles defined by {bL, bR, tL} and {bR, tR, tL}. If thought of
* as a quadrilateral, the diagonal runs from tL to bR. Of course, this isn't a quad, it's two triangles.
*
* @param bL the bottom left corner
* @param bR the bottom right corner
* @param tR the top right corner
* @param tL the top left corner
* @param xDec how far along, [0,1] 0 = left edge, 1 = right edge
* @param yDec how far along, [0,1] 0 = bottom edge, 1 = top edge
* @return the point xDec, yDec in the co-ordinate system defined by bL, bR, tR, tL
*/
private static Vec4 interpolate(Vec4 bL, Vec4 bR, Vec4 tR, Vec4 tL, double xDec, double yDec)
{
double pos = xDec + yDec;
if (pos == 1)
{
// on the diagonal - what's more, we don't need to do any "oneMinusT" calculation
return new Vec4(
tL.x * yDec + bR.x * xDec,
tL.y * yDec + bR.y * xDec,
tL.z * yDec + bR.z * xDec);
}
else if (pos > 1)
{
// in the "top right" half
// vectors pointing from top right towards the point we want (can be thought of as "negative" vectors)
Vec4 horizontalVector = (tL.subtract3(tR)).multiply3(1 - xDec);
Vec4 verticalVector = (bR.subtract3(tR)).multiply3(1 - yDec);
return tR.add3(horizontalVector).add3(verticalVector);
}
else
{
// pos < 1 - in the "bottom left" half
// vectors pointing from the bottom left towards the point we want
Vec4 horizontalVector = (bR.subtract3(bL)).multiply3(xDec);
Vec4 verticalVector = (tL.subtract3(bL)).multiply3(yDec);
return bL.add3(horizontalVector).add3(verticalVector);
}
}
private static DoubleBuffer getTextureCoordinates(int density)
{
if (density < 1)
density = 1;
// Approximate 1 to avoid shearing off of right and top skirts in SurfaceTileRenderer.
// TODO: dig into this more: why are the skirts being sheared off?
final double one = 0.999999;
DoubleBuffer p = parameterizations.get(density);
if (p != null)
return p;
int coordCount = (density + 3) * (density + 3);
p = BufferUtil.newDoubleBuffer(2 * coordCount);
double delta = 1d / density;
int k = 2 * (density + 3);
for (int j = 0; j < density; j++)
{
double v = j * delta;
// skirt column; duplicate first column
p.put(k++, 0d);
p.put(k++, v);
// interior columns
for (int i = 0; i < density; i++)
{
p.put(k++, i * delta); // u
p.put(k++, v);
}
// last interior column; force u to 1.
p.put(k++, one);//1d);
p.put(k++, v);
// skirt column; duplicate previous column
p.put(k++, one);//1d);
p.put(k++, v);
}
// Last interior row
//noinspection UnnecessaryLocalVariable
double v = one;//1d;
p.put(k++, 0d); // skirt column
p.put(k++, v);
for (int i = 0; i < density; i++)
{
p.put(k++, i * delta); // u
p.put(k++, v);
}
p.put(k++, one);//1d); // last interior column
p.put(k++, v);
p.put(k++, one);//1d); // skirt column
p.put(k++, v);
// last skirt row
int kk = k - 2 * (density + 3);
for (int i = 0; i < density + 3; i++)
{
p.put(k++, p.get(kk++));
p.put(k++, p.get(kk++));
}
// first skirt row
k = 0;
kk = 2 * (density + 3);
for (int i = 0; i < density + 3; i++)
{
p.put(k++, p.get(kk++));
p.put(k++, p.get(kk++));
}
parameterizations.put(density, p);
return p;
}
protected static IntBuffer getIndices(int density)
{
if (density < 1)
density = 1;
// return a pre-computed buffer if possible.
java.nio.IntBuffer buffer = indexLists.get(density);
if (buffer != null)
return buffer;
int sideSize = density + 2;
int indexCount = 2 * sideSize * sideSize + 4 * sideSize - 2;
buffer = BufferUtil.newIntBuffer(indexCount);
int k = 0;
for (int i = 0; i < sideSize; i++)
{
buffer.put(k);
if (i > 0)
{
buffer.put(++k);
buffer.put(k);
}
if (i % 2 == 0) // even
{
buffer.put(++k);
for (int j = 0; j < sideSize; j++)
{
k += sideSize;
buffer.put(k);
buffer.put(++k);
}
}
else // odd
{
buffer.put(--k);
for (int j = 0; j < sideSize; j++)
{
k -= sideSize;
buffer.put(k);
buffer.put(--k);
}
}
}
indexLists.put(density, buffer);
return buffer;
}
// TODO: The following method was brought over from BasicRectangularTessellator and is unchecked.
// // Compute normals for a strip
// protected static java.nio.DoubleBuffer getNormals(int density, DoubleBuffer vertices,
// java.nio.IntBuffer indices, Vec4 referenceCenter)
// {
// int numVertices = (density + 3) * (density + 3);
// int sideSize = density + 2;
// int numFaces = indices.limit() - 2;
// double centerX = referenceCenter.x;
// double centerY = referenceCenter.y;
// double centerZ = referenceCenter.z;
// // Create normal buffer
// java.nio.DoubleBuffer normals = BufferUtil.newDoubleBuffer(numVertices * 3);
// // Create per vertex normal lists
// ArrayList<ArrayList<Vec4>> normalLists = new ArrayList<ArrayList<Vec4>>(numVertices);
// for (int i = 0; i < numVertices; i++)
// normalLists.set(i, new ArrayList<Vec4>());
// // Go through all faces in the strip and store normals in lists
// for (int i = 0; i < numFaces; i++)
// {
// int vIndex = 3 * indices.get(i);
// Vec4 v0 = new Vec4((vertices.get(vIndex++) + centerX),
// (vertices.get(vIndex++) + centerY),
// (vertices.get(vIndex) + centerZ));
//
// vIndex = 3 * indices.get(i + 1);
// Vec4 v1 = new Vec4((vertices.get(vIndex++) + centerX),
// (vertices.get(vIndex++) + centerY),
// (vertices.get(vIndex) + centerZ));
//
// vIndex = 3 * indices.get(i + 2);
// Vec4 v2 = new Vec4((vertices.get(vIndex++) + centerX),
// (vertices.get(vIndex++) + centerY),
// (vertices.get(vIndex) + centerZ));
//
// // get triangle edge vectors and plane normal
// Vec4 e1 = v1.subtract3(v0);
// Vec4 e2 = v2.subtract3(v0);
// Vec4 N = e1.cross3(e2).normalize3(); // if N is 0, the triangle is degenerate
//
// // Store the face's normal for each of the vertices that make up the face.
// // TODO: Clear up warnings here
// normalLists.get(indices.get(i)).add(N);
// normalLists.get(indices.get(i + 1)).add(N);
// normalLists.get(indices.get(i + 2)).add(N);
// //System.out.println("Normal: " + N);
// }
//
// // Now loop through each vertex, and average out all the normals stored.
// int idx = 0;
// for (int i = 0; i < numVertices; i++)
// {
// Vec4 normal = Vec4.ZERO;
// // Sum
// for (int j = 0; j < normalLists.get(i).size(); ++j)
// normal = normal.add3(normalLists.get(i).get(j));
// // Average
// normal = normal.multiply3(1.0f / normalLists.get(i).size()).normalize3();
// // Fill normal buffer
// normals.put(idx++, normal.x);
// normals.put(idx++, normal.y);
// normals.put(idx++, normal.z);
// //System.out.println("Normal: " + normal + " - " + normalLists[i].size());
// //System.out.println("Normal buffer: " + normals.get(idx - 3) + ", " + normals.get(idx - 2) + ", " + normals.get(idx - 1));
// }
//
// return normals;
// }
}