gov.nasa.worldwind.layers.TextureTile Maven / Gradle / Ivy
The 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.worldwind.layers;
import com.jogamp.opengl.util.texture.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;
import com.jogamp.opengl.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* This class manages the conversion and timing of image data to a JOGL Texture, and provides an interface for binding
* the texture and applying any texture transforms to align the texture and texture coordinates.
*
*
* @author tag
* @version $Id: TextureTile.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class TextureTile extends Tile implements SurfaceTile
{
private volatile TextureData textureData; // if non-null, then must be converted to a Texture
private TextureTile fallbackTile = null; // holds texture to use if own texture not available
protected boolean hasMipmapData = false;
protected AtomicLong updateTime = new AtomicLong(0);
/**
* Returns the memory cache used to cache tiles for this class and its subclasses, initializing the cache if it
* doesn't yet exist.
*
* @return the memory cache associated with the tile.
*/
public static synchronized MemoryCache getMemoryCache()
{
if (!WorldWind.getMemoryCacheSet().containsCache(TextureTile.class.getName()))
{
long size = Configuration.getLongValue(AVKey.TEXTURE_IMAGE_CACHE_SIZE, 3000000L);
MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
cache.setName("Texture Tiles");
WorldWind.getMemoryCacheSet().addCache(TextureTile.class.getName(), cache);
}
return WorldWind.getMemoryCacheSet().getCache(TextureTile.class.getName());
}
public TextureTile(Sector sector)
{
super(sector);
}
public TextureTile(Sector sector, Level level, int row, int col)
{
super(sector, level, row, col);
}
public TextureTile(Sector sector, Level level, int row, int column, String cacheName)
{
super(sector, level, row, column, cacheName);
}
@Override
public long getSizeInBytes()
{
long size = super.getSizeInBytes();
if (this.textureData != null)
size += this.textureData.getEstimatedMemorySize();
return size;
}
public List extends LatLon> getCorners()
{
ArrayList list = new ArrayList(4);
for (LatLon ll : this.getSector())
{
list.add(ll);
}
return list;
}
public TextureTile getFallbackTile()
{
return this.fallbackTile;
}
public void setFallbackTile(TextureTile fallbackTile)
{
this.fallbackTile = fallbackTile;
}
/**
* Returns the texture data most recently specified for the tile. New texture data is typically specified when a new
* image is read, either initially or in response to image expiration.
*
* If texture data is non-null, a new texture is created from the texture data when the tile is next bound or
* otherwise initialized. The texture data field is then set to null. Subsequently setting texture data to be
* non-null causes a new texture to be created when the tile is next bound or initialized.
*
* @return the texture data, which may be null.
*/
public TextureData getTextureData()
{
return this.textureData;
}
/**
* Specifies new texture data for the tile. New texture data is typically specified when a new image is read, either
* initially or in response to image expiration.
*
* If texture data is non-null, a new texture is created from the texture data when the tile is next bound or
* otherwise initialized. The texture data field is then set to null. Subsequently setting texture data to be
* non-null causes a new texture to be created when the tile is next bound or initialized.
*
* When a texture is created from the texture data, the texture data field is set to null to indicate that the data
* has been converted to a texture and its resources may be released.
*
* @param textureData the texture data, which may be null.
*/
public void setTextureData(TextureData textureData)
{
this.textureData = textureData;
if (textureData.getMipmapData() != null)
this.hasMipmapData = true;
}
public Texture getTexture(GpuResourceCache tc)
{
if (tc == null)
{
String message = Logging.getMessage("nullValue.TextureCacheIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
return tc.getTexture(this.getTileKey());
}
public boolean isTextureInMemory(GpuResourceCache tc)
{
if (tc == null)
{
String message = Logging.getMessage("nullValue.TextureCacheIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
return this.getTexture(tc) != null || this.getTextureData() != null;
}
public long getUpdateTime()
{
return this.updateTime.get();
}
public boolean isTextureExpired()
{
return this.isTextureExpired(this.getLevel().getExpiryTime());
}
public boolean isTextureExpired(long expiryTime)
{
return this.updateTime.get() > 0 && this.updateTime.get() < expiryTime;
}
public void setTexture(GpuResourceCache tc, Texture texture)
{
if (tc == null)
{
String message = Logging.getMessage("nullValue.TextureCacheIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
tc.put(this.getTileKey(), texture);
this.updateTime.set(System.currentTimeMillis());
// No more need for texture data; allow garbage collector and memory cache to reclaim it.
// This also signals that new texture data has been converted.
this.textureData = null;
this.updateMemoryCache();
}
public Vec4 getCentroidPoint(Globe globe)
{
if (globe == null)
{
String msg = Logging.getMessage("nullValue.GlobeIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return globe.computePointFromLocation(this.getSector().getCentroid());
}
public Extent getExtent(DrawContext dc)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), this.getSector());
}
/**
* Splits this texture tile into four tiles; one for each sub quadrant of this texture tile. This attempts to
* retrieve each sub tile from the texture tile cache. This calls {@link #createSubTile(gov.nasa.worldwind.geom.Sector,
* gov.nasa.worldwind.util.Level, int, int)} to create sub tiles not found in the cache.
*
* @param nextLevel the level for the sub tiles.
*
* @return a four-element array containing this texture tile's sub tiles.
*
* @throws IllegalArgumentException if the level is null.
*/
public TextureTile[] createSubTiles(Level nextLevel)
{
if (nextLevel == null)
{
String msg = Logging.getMessage("nullValue.LevelIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Angle p0 = this.getSector().getMinLatitude();
Angle p2 = this.getSector().getMaxLatitude();
Angle p1 = Angle.midAngle(p0, p2);
Angle t0 = this.getSector().getMinLongitude();
Angle t2 = this.getSector().getMaxLongitude();
Angle t1 = Angle.midAngle(t0, t2);
int row = this.getRow();
int col = this.getColumn();
TextureTile[] subTiles = new TextureTile[4];
TileKey key = this.createSubTileKey(nextLevel, 2 * row, 2 * col);
TextureTile subTile = this.getTileFromMemoryCache(key);
if (subTile != null)
subTiles[0] = subTile;
else
subTiles[0] = this.createSubTile(new Sector(p0, p1, t0, t1), nextLevel, 2 * row, 2 * col);
key = this.createSubTileKey(nextLevel, 2 * row, 2 * col + 1);
subTile = this.getTileFromMemoryCache(key);
if (subTile != null)
subTiles[1] = subTile;
else
subTiles[1] = this.createSubTile(new Sector(p0, p1, t1, t2), nextLevel, 2 * row, 2 * col + 1);
key = this.createSubTileKey(nextLevel, 2 * row + 1, 2 * col);
subTile = this.getTileFromMemoryCache(key);
if (subTile != null)
subTiles[2] = subTile;
else
subTiles[2] = this.createSubTile(new Sector(p1, p2, t0, t1), nextLevel, 2 * row + 1, 2 * col);
key = this.createSubTileKey(nextLevel, 2 * row + 1, 2 * col + 1);
subTile = this.getTileFromMemoryCache(key);
if (subTile != null)
subTiles[3] = subTile;
else
subTiles[3] = this.createSubTile(new Sector(p1, p2, t1, t2), nextLevel, 2 * row + 1, 2 * col + 1);
return subTiles;
}
/**
* Creates a sub tile of this texture tile with the specified {@link gov.nasa.worldwind.geom.Sector}, {@link
* gov.nasa.worldwind.util.Level}, row, and column. This is called by {@link #createSubTiles(gov.nasa.worldwind.util.Level)},
* to construct a sub tile for each quadrant of this tile. Subclasses must override this method to return an
* instance of the derived version.
*
* @param sector the sub tile's sector.
* @param level the sub tile's level.
* @param row the sub tile's row.
* @param col the sub tile's column.
*
* @return a sub tile of this texture tile.
*/
protected TextureTile createSubTile(Sector sector, Level level, int row, int col)
{
return new TextureTile(sector, level, row, col);
}
/**
* Returns a key for a sub tile of this texture tile with the specified {@link gov.nasa.worldwind.util.Level}, row,
* and column. This is called by {@link #createSubTiles(gov.nasa.worldwind.util.Level)}, to create a sub tile key
* for each quadrant of this tile.
*
* @param level the sub tile's level.
* @param row the sub tile's row.
* @param col the sub tile's column.
*
* @return a sub tile of this texture tile.
*/
protected TileKey createSubTileKey(Level level, int row, int col)
{
return new TileKey(level.getLevelNumber(), row, col, level.getCacheName());
}
protected TextureTile getTileFromMemoryCache(TileKey tileKey)
{
return (TextureTile) getMemoryCache().getObject(tileKey);
}
protected void updateMemoryCache()
{
if (this.getTileFromMemoryCache(this.getTileKey()) != null)
getMemoryCache().add(this.getTileKey(), this);
}
protected Texture initializeTexture(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
Texture t = this.getTexture(dc.getTextureCache());
// Return texture if found and there is no new texture data
if (t != null && this.getTextureData() == null)
return t;
if (this.getTextureData() == null) // texture not in cache yet texture data is null, can't initialize
{
String msg = Logging.getMessage("nullValue.TextureDataIsNull");
Logging.logger().severe(msg);
throw new IllegalStateException(msg);
}
try
{
t = TextureIO.newTexture(this.getTextureData());
}
catch (Exception e)
{
String msg = Logging.getMessage("layers.TextureLayer.ExceptionAttemptingToReadTextureFile", "");
Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
return null;
}
this.setTexture(dc.getTextureCache(), t);
t.bind(dc.getGL());
this.setTextureParameters(dc, t);
return t;
}
protected void setTextureParameters(DrawContext dc, Texture t)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
GL gl = dc.getGL();
// Use a mipmap minification filter when either of the following is true:
// a. The texture has mipmap data. This is typically true for formats with embedded mipmaps, such as DDS.
// b. The texture is setup to have GL automatically generate mipmaps. This is typically true when a texture is
// loaded from a standard image type, such as PNG or JPEG, and the caller instructed JOGL to generate
// mipmaps.
// Additionally, the texture must be in the latitude range (-80, 80). We do this to prevent seams that appear
// between textures near the poles.
//
// TODO: remove the latitude range restriction if a better tessellator fixes the problem.
boolean useMipmapFilter = (this.hasMipmapData || t.isUsingAutoMipmapGeneration())
&& this.getSector().getMaxLatitude().degrees < 80d && this.getSector().getMinLatitude().degrees > -80;
// Set the texture minification filter. If the texture qualifies for mipmaps, apply a minification filter that
// will access the mipmap data using the highest quality algorithm. If the anisotropic texture filter is
// available, we will enable it. This will sharpen the appearance of the mipmap filter when the textured
// surface is at a high slope to the eye.
if (useMipmapFilter)
{
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
// If the maximum degree of anisotropy is 2.0 or greater, then we know this graphics context supports
// the anisotropic texture filter.
double maxAnisotropy = dc.getGLRuntimeCapabilities().getMaxTextureAnisotropy();
if (dc.getGLRuntimeCapabilities().isUseAnisotropicTextureFilter() && maxAnisotropy >= 2.0)
{
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, (float) maxAnisotropy);
}
}
// If the texture does not qualify for mipmaps, then apply a linear minification filter.
else
{
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
}
// Set the texture magnification filter to a linear filter. This will blur the texture as the eye gets very
// near, but this is still a better choice than nearest neighbor filtering.
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
// Set the S and T wrapping modes to clamp to the texture edge. This way no border pixels will be sampled by
// either the minification or magnification filters.
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
}
public boolean bind(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
// Reinitialize texture if new texture data
if (this.getTextureData() != null)
{
Texture t = this.initializeTexture(dc);
if (t != null)
return true; // texture was bound during initialization.
}
Texture t = this.getTexture(dc.getTextureCache());
if (t == null && this.getFallbackTile() != null)
{
TextureTile resourceTile = this.getFallbackTile();
t = resourceTile.getTexture(dc.getTextureCache());
if (t == null)
{
t = resourceTile.initializeTexture(dc);
if (t != null)
return true; // texture was bound during initialization.
}
}
if (t != null)
t.bind(dc.getGL());
return t != null;
}
public void applyInternalTransform(DrawContext dc, boolean textureIdentityActive)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
Texture t;
if (this.getTextureData() != null) // Reinitialize if new texture data
t = this.initializeTexture(dc);
else
t = this.getTexture(dc.getTextureCache()); // Use the tile's texture if available
if (t != null)
{
if (t.getMustFlipVertically())
{
if (!textureIdentityActive)
{
gl.glMatrixMode(GL2.GL_TEXTURE);
gl.glLoadIdentity();
}
gl.glScaled(1, -1, 1);
gl.glTranslated(0, -1, 0);
}
return;
}
// Use the tile's fallback texture if its primary texture is not available.
TextureTile resourceTile = this.getFallbackTile();
if (resourceTile == null) // no fallback specified
return;
t = resourceTile.getTexture(dc.getTextureCache());
if (t == null && resourceTile.getTextureData() != null)
t = resourceTile.initializeTexture(dc);
if (t == null) // was not able to initialize the fallback texture
return;
// Apply necessary transforms to the fallback texture.
if (!textureIdentityActive)
{
gl.glMatrixMode(GL2.GL_TEXTURE);
gl.glLoadIdentity();
}
if (t.getMustFlipVertically())
{
gl.glScaled(1, -1, 1);
gl.glTranslated(0, -1, 0);
}
this.applyResourceTextureTransform(dc);
}
protected void applyResourceTextureTransform(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (this.getLevel() == null)
return;
int levelDelta = this.getLevelNumber() - this.getFallbackTile().getLevelNumber();
if (levelDelta <= 0)
return;
double twoToTheN = Math.pow(2, levelDelta);
double oneOverTwoToTheN = 1 / twoToTheN;
double sShift = oneOverTwoToTheN * (this.getColumn() % twoToTheN);
double tShift = oneOverTwoToTheN * (this.getRow() % twoToTheN);
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glTranslated(sShift, tShift, 0);
gl.glScaled(oneOverTwoToTheN, oneOverTwoToTheN, 1);
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final TextureTile tile = (TextureTile) o;
return !(this.getTileKey() != null ? !this.getTileKey().equals(tile.getTileKey()) : tile.getTileKey() != null);
}
@Override
public int hashCode()
{
return (this.getTileKey() != null ? this.getTileKey().hashCode() : 0);
}
@Override
public String toString()
{
return this.getSector().toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy