/*
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.render;
import com.sun.opengl.util.*;
import com.sun.opengl.util.texture.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.globes.*;
import gov.nasa.worldwind.util.Logging;
import javax.media.opengl.*;
import java.nio.*;
import java.util.*;
import java.util.logging.Level;
/**
* @author tag
* @version $Id: SurfaceTileRenderer.java 5121 2008-04-22 17:54:54Z tgaskins $
*/
public abstract class SurfaceTileRenderer implements Disposable
{
private static final int DEFAULT_ALPHA_TEXTURE_SIZE = 2;
private Texture alphaTexture;
private Texture outlineTexture;
private boolean showImageTileOutlines = false;
/**
* Free internal resources held by this surface tile renderer.
* A GL context must be current when this method is called.
*
* @throws javax.media.opengl.GLException - If an OpenGL context is not current when this method is called.
*/
public void dispose()
{
if (this.alphaTexture != null)
this.alphaTexture.dispose();
this.alphaTexture = null;
if (this.outlineTexture != null)
this.outlineTexture.dispose();
this.outlineTexture = null;
}
public boolean isShowImageTileOutlines()
{
return showImageTileOutlines;
}
public void setShowImageTileOutlines(boolean showImageTileOutlines)
{
this.showImageTileOutlines = showImageTileOutlines;
}
public void renderTile(DrawContext dc, SurfaceTile tile)
{
if (tile == null)
{
String message = Logging.getMessage("nullValue.TileIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
ArrayList<SurfaceTile> al = new ArrayList<SurfaceTile>(1);
al.add(tile);
this.renderTiles(dc, al);
al.clear();
}
protected static class Transform
{
double HScale;
double VScale;
double HShift;
double VShift;
}
abstract protected void preComputeTransform(DrawContext dc, SectorGeometry sg);
abstract protected void computeTransform(DrawContext dc, SurfaceTile tile, Transform t);
abstract protected Iterable<SurfaceTile> getIntersectingTiles(DrawContext dc, SectorGeometry sg,
Iterable<? extends SurfaceTile> tiles);
public void renderTiles(DrawContext dc, Iterable<? extends SurfaceTile> tiles)
{
if (tiles == null)
{
String message = Logging.getMessage("nullValue.TileIterableIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
GL gl = dc.getGL();
gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT // for alpha func
| GL.GL_ENABLE_BIT
| GL.GL_CURRENT_BIT
| GL.GL_DEPTH_BUFFER_BIT // for depth func
| GL.GL_TEXTURE_BIT // for texture env
| GL.GL_TRANSFORM_BIT);
try
{
this.alphaTexture = dc.getTextureCache().get(this);
if (this.alphaTexture == null)
{
this.initAlphaTexture(DEFAULT_ALPHA_TEXTURE_SIZE); // TODO: choose size to match incoming tile sizes?
dc.getTextureCache().put(this, this.alphaTexture);
}
boolean showOutlines = this.showImageTileOutlines && dc.getNumTextureUnits() > 2;
if (showOutlines && this.outlineTexture == null)
this.initOutlineTexture(128);
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthFunc(GL.GL_LEQUAL);
gl.glEnable(GL.GL_ALPHA_TEST);
gl.glAlphaFunc(GL.GL_GREATER, 0.01f);
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
if (!dc.isPickingMode())
{
gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
}
else
{
gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);
gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS);
gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_REPLACE);
}
int numTexUnitsUsed = 2;
int alphaTextureUnit = GL.GL_TEXTURE1;
if (showOutlines)
{
numTexUnitsUsed = 3;
alphaTextureUnit = GL.GL_TEXTURE2;
gl.glActiveTexture(GL.GL_TEXTURE1);
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_ADD);
}
gl.glActiveTexture(alphaTextureUnit);
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
// For each current geometry tile, find the intersecting image tiles and render the geometry
// tile once for each intersecting image tile.
Transform transform = new Transform();
for (SectorGeometry sg : dc.getSurfaceGeometry())
{
Iterable<SurfaceTile> tilesToRender = this.getIntersectingTiles(dc, sg, tiles);
if (tilesToRender == null)
continue;
// Pre-load info to compute the texture transform
this.preComputeTransform(dc, sg);
// For each interesecting tile, establish the texture transform necessary to map the image tile
// into the geometry tile's texture space. Use an alpha texture as a mask to prevent changing the
// frame buffer where the image tile does not overlap the geometry tile. Render both the image and
// alpha textures via multi-texture rendering.
// TODO: Figure out how to apply multi-texture to more than one tile at a time, most likely via a
// fragment shader.
for (SurfaceTile tile : tilesToRender)
{
gl.glActiveTexture(GL.GL_TEXTURE0);
if (tile.bind(dc))
{
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glLoadIdentity();
tile.applyInternalTransform(dc);
// Determine and apply texture transform to map image tile into geometry tile's texture space
this.computeTransform(dc, tile, transform);
gl.glScaled(transform.HScale, transform.VScale, 1d);
gl.glTranslated(transform.HShift, transform.VShift, 0d);
if (showOutlines)
{
gl.glActiveTexture(GL.GL_TEXTURE1);
this.outlineTexture.bind();
// Apply the same texture transform to the outline texture. The outline textures uses a
// different texture unit than the tile, so the transform made above does not carry over.
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glLoadIdentity();
gl.glScaled(transform.HScale, transform.VScale, 1d);
gl.glTranslated(transform.HShift, transform.VShift, 0d);
}
// Prepare the alpha texture to be used as a mask where texture coords are outside [0,1]
gl.glActiveTexture(alphaTextureUnit);
this.alphaTexture.bind();
// Apply the same texture transform to the alpha texture. The alpha texture uses a
// different texture unit than the tile, so the transform made above does not carry over.
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glLoadIdentity();
gl.glScaled(transform.HScale, transform.VScale, 1d);
gl.glTranslated(transform.HShift, transform.VShift, 0d);
// Render the geometry tile
sg.renderMultiTexture(dc, numTexUnitsUsed);
}
}
}
gl.glActiveTexture(alphaTextureUnit);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPopMatrix();
gl.glDisable(GL.GL_TEXTURE_2D);
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPopMatrix();
gl.glDisable(GL.GL_TEXTURE_2D);
if (showOutlines)
{
gl.glActiveTexture(GL.GL_TEXTURE1);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPopMatrix();
gl.glDisable(GL.GL_TEXTURE_2D);
}
}
catch (Exception e)
{
Logging.logger().log(Level.SEVERE,
Logging.getMessage("generic.ExceptionWhileRenderingLayer", this.getClass().getName()), e);
}
finally
{
// TODO: pop matrix stack too
gl.glPopAttrib();
}
}
private static void fillByteBuffer(ByteBuffer buffer, byte value)
{
for (int i = 0; i < buffer.capacity(); i++)
{
buffer.put(value);
}
}
private void initAlphaTexture(int size)
{
ByteBuffer textureBytes = BufferUtil.newByteBuffer(size * size);
fillByteBuffer(textureBytes, (byte) 0xff);
TextureData textureData = new TextureData(GL.GL_ALPHA, size, size, 0, GL.GL_ALPHA,
GL.GL_UNSIGNED_BYTE, false, false, false, textureBytes.rewind(), null);
this.alphaTexture = TextureIO.newTexture(textureData);
this.alphaTexture.bind();
this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_BORDER);
this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_BORDER);
// Assume the default border color of (0, 0, 0, 0).
}
private void initOutlineTexture(int size)
{
ByteBuffer textureBytes = BufferUtil.newByteBuffer(size * size);
for (int row = 0; row < size; row++)
{
for (int col = 0; col < size; col++)
{
byte p;
if (row == 0 || col == 0 || row == size - 1 || col == size - 1)
p = (byte) 0xff;
else
p = (byte) 0;
textureBytes.put(row * size + col, p);
}
}
TextureData textureData = new TextureData(GL.GL_LUMINANCE, size, size, 0, GL.GL_LUMINANCE,
GL.GL_UNSIGNED_BYTE, false, false, false, textureBytes.rewind(), null);
this.outlineTexture = TextureIO.newTexture(textureData);
this.outlineTexture.bind();
this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
}
}