src.gov.nasa.worldwind.render.SurfaceObjectTileBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* 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.render;
import com.jogamp.opengl.util.texture.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.Cacheable;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.TextureTile;
import gov.nasa.worldwind.util.*;
import javax.media.opengl.GL;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Builds a list of {@link gov.nasa.worldwind.render.SurfaceTile} instances who's content is defined by a specified set
* of {@link gov.nasa.worldwind.render.SurfaceObject} references. It's typically not necessary to use
* SurfaceObjectTileBuilder directly. World Wind's default {@link gov.nasa.worldwind.SceneController} automatically
* batches instances of {@link gov.nasa.worldwind.render.SurfaceObject} in a single SurfaceObjectTileBuilder.
* Applications that need to draw basic surface shapes should use or extend {@link
* gov.nasa.worldwind.render.SurfaceShape} instead of using SurfaceObjectTileBuilder directly.
*
* Surface tiles are built by calling {@link #buildTiles(DrawContext, Iterable)} (DrawContext, Iterable)} with an
* Iterable of SurfaceObjects. This assembles a set of surface tiles that meet the resolution requirements for the
* specified draw context, then draws the SurfaceObjects into those offscreen surface tiles by calling {@link
* gov.nasa.worldwind.render.SurfaceObject#render(DrawContext)}. This process may temporarily use the framebuffer to
* perform offscreen rendering, and therefore should be called during the preRender method of a World Wind {@link
* gov.nasa.worldwind.layers.Layer}. See {@link gov.nasa.worldwind.render.PreRenderable} for details. Once built, the
* surface tiles can be rendered by a {@link gov.nasa.worldwind.render.SurfaceTileRenderer}.
*
* By default, SurfaceObjectTileBuilder creates texture tiles with a width and height of 512 pixels, and with internal
* format GL_RGBA
. These parameters are configurable by calling {@link
* #setTileDimension(java.awt.Dimension)} or {@link #setTileTextureFormat(int)}.
*
* The most common usage pattern for SurfaceObjectTileBuilder is to build the surface tiles from a set of SurfaceObjects
* during the preRender phase, then draw those surface tiles during the render phase. For example, a {@link
* gov.nasa.worldwind.render.Renderable} can use SurfaceObjectTileBuilder to draw a set of SurfaceObjects as follows:
*
*
*
* class MyRenderable implements Renderable, PreRenderable
* {
* protected SurfaceObjectTileBuilder tileBuilder = new SurfaceObjectTileBuilder();
* protected ArrayList tiles = new ArrayList();
*
* public void preRender(DrawContext dc)
* {
* List surfaceObjects = Arrays.asList(
* new SurfaceCircle(LatLon.fromDegrees(0, 100), 10000),
* new SurfaceSquare(LatLon.fromDegrees(0, 101), 10000));
* this.tiles.addAll(this.tileBuilder.buildSurfaceTiles(dc, surfaceObjects));
* }
*
* public void render(DrawContext dc)
* {
* dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.tiles);
* }
* }
*
*
*
* @author dcollins
* @version $Id: SurfaceObjectTileBuilder.java 1824 2014-01-22 22:41:10Z dcollins $
*/
public class SurfaceObjectTileBuilder
{
/** The default surface tile texture dimension, in pixels. */
protected static final int DEFAULT_TEXTURE_DIMENSION = 512;
/** The default OpenGL internal format used to create surface tile textures. */
protected static final int DEFAULT_TEXTURE_INTERNAL_FORMAT = GL.GL_RGBA8;
/** The default OpenGL pixel format used to create surface tile textures. */
protected static final int DEFAULT_TEXTURE_PIXEL_FORMAT = GL.GL_RGBA;
/**
* The default split scale. The split scale 2.9 has been empirically determined to render sharp lines and edges with
* the SurfaceShapes such as SurfacePolyline and SurfacePolygon.
*/
protected static final double DEFAULT_SPLIT_SCALE = 2.9;
/** The default level zero tile delta used to construct a LevelSet. */
protected static final LatLon DEFAULT_LEVEL_ZERO_TILE_DELTA = LatLon.fromDegrees(36, 36);
/**
* The default number of levels used to construct a LevelSet. Approximately 0.1 meters per pixel at the Earth's
* equator.
*/
protected static final int DEFAULT_NUM_LEVELS = 17;
/** The next unique ID. This property is shared by all instances of SurfaceObjectTileBuilder. */
protected static long nextUniqueId = 1;
/**
* Map associating a tile texture dimension to its corresponding LevelSet. This map is a class property in order to
* share LevelSets across all instances of SurfaceObjectTileBuilder.
*/
protected static Map levelSetMap = new HashMap();
/**
* Map associating a tile texture dimension to a tile cache name. This map is an instance property so that the cache
* names for each instance and each tile dimension are unique.
*/
protected Map tileCacheNameMap = new HashMap();
/**
* Indicates the desired tile texture width and height, in pixels. Initially set to
* DEFAULT_TEXTURE_DIMENSION
.
*/
protected Dimension tileDimension = new Dimension(DEFAULT_TEXTURE_DIMENSION, DEFAULT_TEXTURE_DIMENSION);
/**
* Indicates the currently used tile texture width and height, in pixels. This is different than the caller
* specified tileDimension
if this value does not fit in the viewport, or is not a power of two.
* Initially null
.
*/
protected Dimension currentTileDimension;
/** The surface tile OpenGL texture format. 0 indicates the default format is used. */
protected int tileTextureFormat;
/** Controls if surface tiles are rendered using a linear filter or a nearest-neighbor filter. */
protected boolean useLinearFilter = true;
/** Controls if mip-maps are generated for surface tile textures. */
protected boolean useMipmaps;
/** Controls the tile resolution as distance changes between the globe's surface and the eye point. */
protected double splitScale = DEFAULT_SPLIT_SCALE;
/** List of currently assembled surface objects. Used during tile assembly and updating. */
protected List currentSurfaceObjects = new ArrayList();
/** List of currently assembled surface tiles. */
protected List currentTiles = new ArrayList();
/** Support class used to render to an offscreen surface tile. */
protected OGLRenderToTextureSupport rttSupport = new OGLRenderToTextureSupport();
/**
* Constructs a new SurfaceObjectTileBuilder with a tile width and height of 512
, with the default tile
* texture format, with linear filtering enabled, and with mip-mapping disabled.
*/
public SurfaceObjectTileBuilder()
{
}
/**
* Constructs a new SurfaceObjectTileBuilder width the specified tile dimension, tile texture format, and flags
* specifying if linear filtering and mip-mapping are enabled.
*
* @param tileTextureDimension the surface tile texture dimension, in pixels.
* @param tileTextureFormat the surface tile OpenGL texture format, or 0 to use the default format.
* @param useLinearFilter true to use linear filtering while rendering surface tiles; false to use
* nearest-neighbor filtering.
* @param useMipmaps true to generate mip-maps for surface tile textures; false otherwise.
*
* @throws IllegalArgumentException if the tile dimension is null.
*/
public SurfaceObjectTileBuilder(Dimension tileTextureDimension, int tileTextureFormat, boolean useLinearFilter,
boolean useMipmaps)
{
if (tileTextureDimension == null)
{
String message = Logging.getMessage("nullValue.DimensionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.setTileDimension(tileTextureDimension);
this.setTileTextureFormat(tileTextureFormat);
this.setUseLinearFilter(useLinearFilter);
this.setUseMipmaps(useMipmaps);
}
/**
* Returns the surface tile dimension.
*
* @return the surface tile dimension, in pixels.
*/
public Dimension getTileDimension()
{
return this.tileDimension;
}
/**
* Specifies the preferred surface tile texture dimension. If the dimension is larger than the viewport dimension,
* this uses a dimension with width and height set to the largest power of two that is less than or equal to the
* specified dimension and the viewport dimension.
*
* @param dimension the surface tile dimension, in pixels.
*
* @throws IllegalArgumentException if the dimension is null.
*/
public void setTileDimension(Dimension dimension)
{
if (dimension == null)
{
String message = Logging.getMessage("nullValue.DimensionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.tileDimension = dimension;
}
/**
* Returns the surface tile's OpenGL texture format, or 0 to indicate that the default format is used.
*
* @return the OpenGL texture format, or 0 if the default format is used.
*
* @see #setTileTextureFormat(int)
*/
public int getTileTextureFormat()
{
return tileTextureFormat;
}
/**
* Specifies the surface tile's OpenGL texture format. A value of 0 indicates that the default format should be
* used. Otherwise, the texture format may be one of the following: - GL_ALPHA
- GL_ALPHA4
* - GL_ALPHA8
- GL_ALPHA12
- GL_ALPHA16
- GL_COMPRESSED_ALPHA
* - GL_COMPRESSED_LUMINANCE
- GL_COMPRESSED_LUMINANCE_ALPHA
- GL_COMPRESSED_INTENSITY
* - GL_COMPRESSED_RGB
- GL_COMPRESSED_RGBA
- GL_DEPTH_COMPONENT
- GL_DEPTH_COMPONENT16
* - GL_DEPTH_COMPONENT24
- GL_DEPTH_COMPONENT32
- GL_LUMINANCE
- GL_LUMINANCE4
* - GL_LUMINANCE8
- GL_LUMINANCE12
- GL_LUMINANCE16
- GL_LUMINANCE_ALPHA
* - GL_LUMINANCE4_ALPHA4
- GL_LUMINANCE6_ALPHA2
- GL_LUMINANCE8_ALPHA8
* - GL_LUMINANCE12_ALPHA4
- GL_LUMINANCE12_ALPHA12
- GL_LUMINANCE16_ALPHA16
* - GL_INTENSITY
- GL_INTENSITY4
- GL_INTENSITY8
- GL_INTENSITY12
* - GL_INTENSITY16
- GL_R3_G3_B2
- GL_RGB
- GL_RGB4
- GL_RGB5
- GL_RGB8
* - GL_RGB10
- GL_RGB12
- GL_RGB16
- GL_RGBA
- GL_RGBA2
- GL_RGBA4
* - GL_RGB5_A1
- GL_RGBA8
- GL_RGB10_A2
- GL_RGBA12
- GL_RGBA16
* - GL_SLUMINANCE
- GL_SLUMINANCE8
- GL_SLUMINANCE_ALPHA
- GL_SLUMINANCE8_ALPHA8
* - GL_SRGB
- GL_SRGB8
- GL_SRGB_ALPHA
- GL_SRGB8_ALPHA8
*
* If the texture format is any of GL_RGB, GL_RGB8, GL_RGBA, or GL_RGBA8
, the tile builder attempts to
* use OpenGL framebuffer objects to render shapes to the texture tiles. Otherwise, this renders shapes to the
* framebuffer and copies the framebuffer contents to the texture tiles.
*
* @param textureFormat the OpenGL texture format, or 0 to use the default format.
*/
public void setTileTextureFormat(int textureFormat)
{
this.tileTextureFormat = textureFormat;
}
/**
* Returns if linear filtering is used when rendering surface tiles.
*
* @return true if linear filtering is used; false if nearest-neighbor filtering is used.
*/
public boolean isUseLinearFilter()
{
return useLinearFilter;
}
/**
* Specifies if linear filtering should be used when rendering surface tiles.
*
* @param useLinearFilter true to use linear filtering; false to use nearest-neighbor filtering.
*/
public void setUseLinearFilter(boolean useLinearFilter)
{
this.useLinearFilter = useLinearFilter;
}
/**
* Returns if mip-maps are generated for surface tile textures.
*
* @return true if mip-maps are generated; false otherwise.
*/
public boolean isUseMipmaps()
{
return this.useMipmaps;
}
/**
* Specifies if mip-maps should be generated for surface tile textures.
*
* @param useMipmaps true to generate mip-maps; false otherwise.
*/
public void setUseMipmaps(boolean useMipmaps)
{
this.useMipmaps = useMipmaps;
}
/**
* Sets the parameter controlling the tile resolution as distance changes between the globe's surface and the eye
* point. Higher resolution is displayed as the split scale increases from 1.0. Lower resolution is displayed as the
* split scale decreases from 1.0. The default value is 2.9.
*
* @param splitScale a value near 1.0 that controls the tile's surface texel resolution as the distance between the
* globe's surface and the eye point change. Increasing values select higher resolution,
* decreasing values select lower resolution. The default value is 2.9.
*/
public void setSplitScale(double splitScale)
{
this.splitScale = splitScale;
}
/**
* Returns the split scale value controlling the tile's surface texel resolution relative to the distance between
* the globe's surface at the image position and the eye point.
*
* @return the current split scale.
*
* @see #setSplitScale(double)
*/
public double getSplitScale()
{
return this.splitScale;
}
/**
* Assembles the surface tiles and draws any SurfaceObjects in the iterable into those offscreen tiles. The surface
* tiles are assembled to meet the necessary resolution of to the draw context's {@link gov.nasa.worldwind.View}.
* This may temporarily use the framebuffer to perform offscreen rendering, and therefore should be called during
* the preRender method of a World Wind {@link gov.nasa.worldwind.layers.Layer}.
*
* This returns an empty List if the specified iterable is null, is empty or contains no SurfaceObjects.
*
* @param dc the draw context to build tiles for.
* @param iterable the iterable to gather SurfaceObjects from.
*
* @return a List of SurfaceTiles containing a composite representation of the specified SurfaceObjects.
*
* @throws IllegalArgumentException if the draw context is null.
*/
public List buildTiles(DrawContext dc, Iterable iterable)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.currentSurfaceObjects.clear();
this.currentTiles.clear();
if (iterable == null)
return Collections.emptyList();
// Assemble the list of current surface objects from the specified iterable.
this.assembleSurfaceObjects(iterable);
// We've cleared any tile assembly state from the last rendering pass. Determine if we can assemble and update
// the tiles. If not, we're done.
if (this.currentSurfaceObjects.isEmpty() || !this.canAssembleTiles(dc))
return Collections.emptyList();
// Assemble the current visible tiles and update their associated textures if necessary.
this.assembleTiles(dc);
this.updateTiles(dc);
// Tiles in the current tile list contain references to SurfaceObjects. These references are used during tile
// update, and are longer needed. Clear these lists to ensure we don't retain any dangling references to the
// surface objects.
for (SurfaceObjectTile tile : this.currentTiles)
{
tile.clearObjectList();
}
ArrayList tiles = new ArrayList(this.currentTiles);
// Objects in the current surface object list and current tile list are no longer needed. Clear the lists to
// ensure we don't retain dangling references to any objects or tiles.
this.currentSurfaceObjects.clear();
this.currentTiles.clear();
return tiles;
}
/**
* Indicates the tile cache name to use for the specified tileDimension
. The cache name is unique to
* this SurfaceObjectTileBuilder
and the tileDimension
. Using a unique cache name for each
* instance and tile dimension ensures that the tiles for this instances and tileDimension
do not
* conflict with any other tiles in the cache.
*
* In practices, there are at most 10 dimensions we'll use (512, 256, 128, 64, 32, 16, 8, 4, 2, 1 ), and therefore
* at most 10 cache names for each SurfaceObjectTileBuilder
.
*
* Subsequent calls are guaranteed to return the same cache name for the same tileDimension
.
*
* @param tileDimension the texture tile dimension who's cache name is returned.
*
* @return the tile cache name for the specified tileDimension
*/
protected String getTileCacheName(Dimension tileDimension)
{
String s = this.tileCacheNameMap.get(tileDimension);
if (s == null)
{
s = this.nextCacheName();
this.tileCacheNameMap.put(tileDimension, s);
}
return s;
}
/**
* Returns a unique name appropriate for use as part of a cache name. The returned string is constructed as follows:
* {@code this.getClass().getName() + "/" + nextUniqueId()}.
*
* @return a unique cache name.
*/
protected String nextCacheName()
{
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName());
sb.append("/");
sb.append(nextUniqueId());
return sb.toString();
}
/**
* Returns the next unique integer associated with a SurfaceObjectTileBuilder. This method is synchronized to ensure
* that two threads calling simultaneously receive different IDs. Since this method is called from
* SurfaceObjectTileBuilder's constructor, this is critical to ensure that SurfaceObjectTileBuilders can be safely
* constructed on separate threads.
*
* @return the next unique integer.
*/
protected static synchronized long nextUniqueId()
{
return nextUniqueId++;
}
/**
* Returns the tile dimension used to create the tile textures for the specified DrawContext
. This
* attempts to use the caller configured {@link #tileDimension}
, but always returns a dimension that is
* is a power of two, is square, and fits in the DrawContext
's viewport.
*
* @param dc the DrawContext
to compute a texture tile dimension for.
*
* @return a texture tile dimension appropriate for the specified DrawContext
.
*/
protected Dimension computeTextureTileDimension(DrawContext dc)
{
// Force a square dimension by using the maximum of the tile builder's tileWidth and tileHeight.
int maxSize = Math.max(this.tileDimension.width, this.tileDimension.height);
// The viewport may be smaller than the desired dimension. For that reason, we constrain the desired tile
// dimension by the viewport width and height.
Rectangle viewport = dc.getView().getViewport();
if (maxSize > viewport.width)
maxSize = viewport.width;
if (maxSize > viewport.height)
maxSize = viewport.height;
// The final dimension used to render all surface tiles will be the power of two which is less than or equal to
// the preferred dimension, and which fits into the viewport.
int potSize = WWMath.powerOfTwoFloor(maxSize);
return new Dimension(potSize, potSize);
}
//**************************************************************//
//******************** Tile Updating *************************//
//**************************************************************//
/**
* Updates each {@link SurfaceObjectTileBuilder.SurfaceObjectTile} in the {@link #currentTiles} list. This is
* typically called after {@link #assembleTiles(DrawContext)} to update the assembled tiles.
*
* This method does nothing if currentTiles
is empty.
*
* @param dc the draw context the tiles relate to.
*/
protected void updateTiles(DrawContext dc)
{
if (this.currentTiles.isEmpty())
return;
// The tile drawing rectangle has the same dimension as the current tile viewport, but it's lower left corner
// is placed at the origin. This is because the orthographic projection setup by OGLRenderToTextureSupport
// maps (0, 0) to the lower left corner of the drawing region, therefore we can drop the (x, y) offset when
// drawing pixels to the texture, as (0, 0) is automatically mapped to (x, y). Since we've created the tiles
// from a LevelSet where each level has equivalent dimension, we assume that tiles in the current tile list
// have equivalent dimension.
// The OpenGL framebuffer object extension used by RenderToTextureSupport works only for texture formats
// GL_RGB and GL_RGBA. Disable framebuffer objects if the tile builder has been configured with a different
// format.
this.rttSupport.setEnableFramebufferObject(
this.tileTextureFormat == 0 || // Default format is GL_RGB8.
this.tileTextureFormat == GL.GL_RGB ||
this.tileTextureFormat == GL.GL_RGB8 ||
this.tileTextureFormat == GL.GL_RGBA ||
this.tileTextureFormat == GL.GL_RGBA8);
this.rttSupport.beginRendering(dc, 0, 0, this.currentTileDimension.width, this.currentTileDimension.height);
try
{
for (SurfaceObjectTile tile : this.currentTiles)
{
this.updateTile(dc, tile);
}
}
finally
{
this.rttSupport.endRendering(dc);
}
}
/**
* Draws the current list of SurfaceObjects into the specified surface tile. The surface tiles is updated only when
* necessary. The tile keeps track of the list of SurfaceObjects rendered into it, and the state keys those objects.
* The tile is updated if the list changes, if any of the state keys change, or if the tile has no texture.
* Otherwise the tile is left unchanged and the update is skipped.
*
* @param dc the draw context the tile relates to.
* @param tile the tile to update.
*/
protected void updateTile(DrawContext dc, SurfaceObjectTile tile)
{
// Get the tile's texture from the draw context's texture cache. If null we create a new texture and update the
// texture cache below.
Texture texture = tile.getTexture(dc.getTextureCache());
// Compare the previous tile state against the currently computed state to determine if the tile needs to be
// updated. The tile needs to be updated if any the following conditions are true:
// * The tile has no texture.
// * The tile has no state.
// * The list of intersecting objects has changed.
// * An intersecting object's state key is different than one stored in the tile's previous state key.
Object tileStateKey = tile.getStateKey(dc);
if (texture != null && tileStateKey.equals(tile.lastUpdateStateKey))
return;
// If the tile needs to be updated, then assign its lastUpdateStateKey before its texture is created. This
// ensures that the lastUpdateStateKey is current when the tile is added to the cache.
tile.lastUpdateStateKey = tileStateKey;
if (texture == null) // Create the tile's texture if it doesn't already have one.
{
texture = this.createTileTexture(dc, tile.getWidth(), tile.getHeight());
tile.setTexture(dc.getTextureCache(), texture);
}
if (texture == null) // This should never happen, but we check anyway.
{
Logging.logger().warning(Logging.getMessage("nullValue.TextureIsNull"));
return;
}
try
{
// SurfaceObjects expect the SurfaceTileDrawContext to be attached to the draw context's AVList. Create a
// SurfaceTileDrawContext with the tile's Sector and viewport. The Sector defines the context's geographic
// extent, and the viewport defines the context's corresponding viewport in pixels.
dc.setValue(AVKey.SURFACE_TILE_DRAW_CONTEXT, this.createSurfaceTileDrawContext(tile));
this.rttSupport.setColorTarget(dc, texture);
this.rttSupport.clear(dc, new Color(0, 0, 0, 0)); // Set all texture pixels to transparent black.
if (tile.hasObjects())
{
for (SurfaceObject so : tile.getObjectList())
{
so.render(dc);
}
}
}
finally
{
this.rttSupport.setColorTarget(dc, null);
dc.removeKey(AVKey.SURFACE_TILE_DRAW_CONTEXT);
}
}
/**
* Returns a new surface tile texture for use on the specified draw context with the specified width and height.
*
* The returned texture's internal format is specified by tilePixelFormat
. If
* tilePixelFormat
is zero, this returns a texture with internal format GL_RGBA8
.
*
* The returned texture's parameters are configured as follows: Parameter
* Name Value GL.GL_TEXTURE_MIN_FILTER
GL_LINEAR_MIPMAP_LINEAR
* if useLinearFilter
and useMipmaps
are both true, GL_LINEAR
if
* useLinearFilter
is true and useMipmaps
is false, and GL_NEAREST
if
* useLinearFilter
is false. GL.GL_TEXTURE_MAG_FILTER
GL_LINEAR
* if useLinearFilter
is true, GL_NEAREST
if useLinearFilter
is
* false. GL.GL_TEXTURE_WRAP_S
GL_CLAMP_TO_EDGE
* GL.GL_TEXTURE_WRAP_T
GL_CLAMP_TO_EDGE
*
* @param dc the draw context to create a texture for.
* @param width the texture's width, in pixels.
* @param height the texture's height, in pixels.
*
* @return a new texture with the specified width and height.
*/
protected Texture createTileTexture(DrawContext dc, int width, int height)
{
int internalFormat = this.tileTextureFormat;
if (internalFormat == 0)
internalFormat = DEFAULT_TEXTURE_INTERNAL_FORMAT;
int pixelFormat = OGLUtil.computeTexturePixelFormat(internalFormat);
if (pixelFormat == 0)
pixelFormat = DEFAULT_TEXTURE_PIXEL_FORMAT;
Texture t;
GL gl = dc.getGL();
TextureData td = new TextureData(
gl.getGLProfile(), // GL profile
internalFormat, // internal format
width, height, // dimension
0, // border
pixelFormat, // pixel format
GL.GL_UNSIGNED_BYTE, // pixel type
this.isUseMipmaps(), // mipmap
false, false, // dataIsCompressed, mustFlipVertically
null, null) // buffer, flusher
{
/**
* Overridden to return a non-zero size. TextureData does not compute an estimated memory size if the buffer
* is null. Therefore we override getEstimatedMemorySize() to return the appropriate size in bytes of a
* texture with the common pixel formats.
*/
@Override
public int getEstimatedMemorySize()
{
int sizeInBytes = OGLUtil.estimateTextureMemorySize(this.getInternalFormat(), this.getWidth(),
this.getHeight(), this.getMipmap());
if (sizeInBytes > 0)
return sizeInBytes;
return super.getEstimatedMemorySize();
}
};
t = TextureIO.newTexture(td);
t.bind(gl);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, this.isUseLinearFilter() ?
(this.isUseMipmaps() ? GL.GL_LINEAR_MIPMAP_LINEAR : GL.GL_LINEAR) : GL.GL_NEAREST);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, this.isUseLinearFilter() ?
GL.GL_LINEAR : GL.GL_NEAREST);
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);
return t;
}
/**
* Returns a new Object representing the drawing context for the specified tile. The returned object should
* represent the tile's sector and it's corresponding viewport in pixels.
*
* @param tile The tile to create a context for.
*
* @return a new drawing context for the specified tile.
*/
protected Object createSurfaceTileDrawContext(SurfaceObjectTile tile)
{
return new SurfaceTileDrawContext(tile.getSector(), tile.getWidth(), tile.getHeight());
}
//**************************************************************//
//******************** Surface Object Assembly ***************//
//**************************************************************//
/**
* Adds any SurfaceObjects in the specified Iterable to the tile builder's {@link #currentSurfaceObjects} list.
*
* @param iterable the Iterable to gather SurfaceObjects from.
*/
protected void assembleSurfaceObjects(Iterable iterable)
{
// Gather up all the SurfaceObjects, ignoring null references and non SurfaceObjects.
for (Object o : iterable)
{
if (o instanceof SurfaceObject)
this.currentSurfaceObjects.add((SurfaceObject) o);
}
}
//**************************************************************//
//******************** LevelSet Assembly *********************//
//**************************************************************//
/**
* Returns a shared LevelSet
for the specified tileDimension
. All instances of
* SurfaceObjectTileBuilder
share common LevelSets to determine which tiles are visible, but create
* unique tile instances and uses a unique tile cache name. Since all instances use the same tile structure to
* determine visible tiles, this saves memory while ensuring that each instance stores its own tiles in the cache.
*
* The returned LevelSet's cache name and dataset name are dummy values, and should not be used. Use this tile
* builder's cache name for the specified tileDimension
instead.
*
* In practice, there are at most 10 dimensions we use: 512, 256, 128, 64, 32, 16, 8, 4, 2, 1. Therefore keeping the
* LevelSet
s in a map requires little memory overhead, and ensures each LevelSet
is
* retained once constructed. Retaining references to the LevelSet
s means we're able to re-use the
* texture resources associated with each LevelSet
in the DrawContext
's texture cache.
*
* Subsequent calls are guaranteed to return the same LevelSet
for the same
* tileDimension
.
*
* @param tileDimension the DrawContext to return a LevelSet for.
*
* @return a LevelSet who's tile dimension fits in the DrawContext's viewport.
*/
protected LevelSet getLevelSet(Dimension tileDimension)
{
// If we already have a LevelSet for the dimension, just return it. Otherwise create it and put it in a map for
// use during subsequent calls.
LevelSet levelSet = levelSetMap.get(tileDimension);
if (levelSet == null)
{
levelSet = createLevelSet(tileDimension.width, tileDimension.height);
levelSetMap.put(tileDimension, levelSet);
}
return levelSet;
}
/**
* Returns a new LevelSet with the specified tile width and height. The LevelSet overs the full sphere, has a level
* zero tile delta of {@link #DEFAULT_LEVEL_ZERO_TILE_DELTA}, has number of levels equal to {@link
* #DEFAULT_NUM_LEVELS} (with no empty levels). The LevelSets' cache name and dataset name dummy values, and should
* not be used.
*
* @param tileWidth the LevelSet's tile width, in pixels.
* @param tileHeight the LevelSet's tile height, in pixels.
*
* @return a new LevelSet configured to with
*/
protected static LevelSet createLevelSet(int tileWidth, int tileHeight)
{
AVList params = new AVListImpl();
params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, DEFAULT_LEVEL_ZERO_TILE_DELTA);
params.setValue(AVKey.SECTOR, Sector.FULL_SPHERE);
params.setValue(AVKey.NUM_LEVELS, DEFAULT_NUM_LEVELS);
params.setValue(AVKey.NUM_EMPTY_LEVELS, 0);
params.setValue(AVKey.TILE_WIDTH, tileWidth);
params.setValue(AVKey.TILE_HEIGHT, tileHeight);
// This is a shared LevelSet, so just supply a dummy cache name and dataset name.
params.setValue(AVKey.DATA_CACHE_NAME, SurfaceObjectTileBuilder.class.getName());
params.setValue(AVKey.DATASET_NAME, SurfaceObjectTileBuilder.class.getName());
// We won't use any tile resource paths, so just supply a dummy format suffix.
params.setValue(AVKey.FORMAT_SUFFIX, SurfaceObjectTileBuilder.class.getName());
return new LevelSet(params);
}
//**************************************************************//
//******************** Tile Assembly *************************//
//**************************************************************//
/**
* Returns true if the draw context's viewport width and height are greater than zero.
*
* @param dc the DrawContext to test.
*
* @return true if the DrawContext's has a non-zero viewport; false otherwise.
*/
protected boolean canAssembleTiles(DrawContext dc)
{
Rectangle viewport = dc.getView().getViewport();
return viewport.getWidth() > 0 && viewport.getHeight() > 0;
}
/**
* Assembles a set of surface tiles that are visible in the specified DrawContext and meet the tile builder's
* resolution criteria. Tiles are culled against the current SurfaceObject list, against the DrawContext's view
* frustum during rendering mode, and against the DrawContext's pick frustums during picking mode. If a tile does
* not meet the tile builder's resolution criteria, it's split into four sub-tiles and the process recursively
* repeated on the sub-tiles. Visible leaf tiles are added to the {@link #currentTiles}
list.
*
* During assembly each SurfaceObject in {@link #currentSurfaceObjects} is sorted into the tiles they intersect. The
* top level tiles are used as an index to quickly determine which tiles each SurfaceObjects intersects.
* SurfaceObjects are sorted into sub-tiles by simple intersection tests. SurfaceObjects are added to each tile's
* surface object list at most once. See {@link SurfaceObjectTileBuilder.SurfaceObjectTile#addSurfaceObject(SurfaceObject,
* gov.nasa.worldwind.geom.Sector)}. Tiles that don't intersect any SurfaceObjects are discarded.
*
* @param dc the DrawContext to assemble tiles for.
*/
protected void assembleTiles(DrawContext dc)
{
// Compute the texture tile dimension to use for this DrawContext. This dimension is always square and a power
// of two.
this.currentTileDimension = this.computeTextureTileDimension(dc);
// Get the level set and tile cache name to use for the current tile dimension. The LevelSet is shared by all
// SurfaceObjectTileBuilders, while the cache name is unique to this instance. Since all
// SurfaceObjectTileBuilders use the same tile structure to determine visible tiles, this saves memory while
// preventing a conflict in the tile and texture caches.
String tileCacheName = this.getTileCacheName(this.currentTileDimension);
LevelSet levelSet = this.getLevelSet(this.currentTileDimension);
Level level = levelSet.getFirstLevel();
Angle dLat = level.getTileDelta().getLatitude();
Angle dLon = level.getTileDelta().getLongitude();
Angle latOrigin = levelSet.getTileOrigin().getLatitude();
Angle lonOrigin = levelSet.getTileOrigin().getLongitude();
// Store the top level tiles in a set to ensure that each top level tile is added only once. Store the tiles
// that intersect each surface object in a set to ensure that each object is added to a tile at most once.
Set topLevelTiles = new HashSet();
Set