/*******************************************************************************
* 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.render.fastshape;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.Cacheable;
import gov.nasa.worldwind.geom.Extent;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Sphere;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.BasicWWTexture;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.OrderedRenderable;
import gov.nasa.worldwind.render.WWTexture;
import gov.nasa.worldwind.util.BufferWrapper;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.OGLStackHandler;
import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.imageio.ImageIO;
import javax.media.opengl.GL2;
import javax.media.opengl.GL2GL3;
import au.gov.ga.earthsci.worldwind.common.layers.Bounded;
import au.gov.ga.earthsci.worldwind.common.layers.Bounds;
import au.gov.ga.earthsci.worldwind.common.layers.Wireframeable;
import com.jogamp.opengl.util.texture.Texture;
/**
* The FastShape class is a representation of a piece of geometry. It is useful
* for meshes or points or lines with a large number of vertices, as the vertex
* positions aren't updated every frame (instead they are updated in a vertex
* updater thread).
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class FastShape implements OrderedRenderable, Cacheable, Bounded, Wireframeable
{
protected final static SingleTaskRunner VertexUpdater = new SingleTaskRunner(FastShape.class.getName()
+ " VertexUpdater"); //$NON-NLS-1$
protected final static SingleTaskRunner IndexUpdater = new SingleTaskRunner(FastShape.class.getName()
+ " IndexUpdater"); //$NON-NLS-1$
protected final ReadWriteLock positionLock = new ReentrantReadWriteLock();
protected final PickSupport pickSupport = new PickSupport();
protected Layer pickLayer = null;
protected List<Position> positions;
protected ReadWriteLock positionsLock = new ReentrantReadWriteLock();
protected String name = "Shape";
protected final int mode;
//calculated:
protected final FloatVBO vertexVBO = new FloatVBO(3);
protected final FloatVBO normalVBO = new FloatVBO(3);
protected final IntIndexVBO sortedIndexVBO = new IntIndexVBO();
//set:
protected final IntIndexVBO indexVBO = new IntIndexVBO();
protected final FloatVBO colorVBO = new FloatVBO(3);
protected final FloatVBO pickingColorVBO = new FloatVBO(3);
protected final FloatVBO textureCoordinateVBO = new FloatVBO(2);
protected boolean useOrderedRendering = false;
protected double distanceFromEye = 0;
protected double alphaForOrderedRenderingMode;
protected Sphere boundingSphere;
protected Sphere modBoundingSphere;
protected Bounds bounds;
protected boolean colorBufferEnabled = true;
protected Color color = Color.white;
protected double opacity = 1;
protected boolean followTerrain = false;
protected long followTerrainUpdateFrequency = 2000; //ms
protected Globe lastGlobe = null;
protected boolean verticesDirty = true;
protected Vec4 lastEyePoint = null;
protected double lastVerticalExaggeration = -Double.MAX_VALUE;
protected double elevation = 0d;
protected boolean elevationChanged = false;
protected boolean calculateNormals = false;
protected boolean reverseNormals = false;
protected boolean fogEnabled = false;
protected boolean wireframe = false;
protected boolean lighted = false;
protected boolean sortTransparentPrimitives = true;
protected boolean forceSortedPrimitives = false;
protected boolean backfaceCulling = false;
protected boolean enabled = true;
protected boolean twoSidedLighting = false;
protected Double lineWidth;
protected Double pointSize;
protected Double pointMinSize;
protected Double pointMaxSize;
protected Double pointConstantAttenuation;
protected Double pointLinearAttenuation;
protected Double pointQuadraticAttenuation;
protected boolean pointSprite = false;
protected URL pointTextureUrl;
protected WWTexture pointTexture;
protected WWTexture blankTexture;
protected boolean textured = true; //only actually textured if texture is not null
protected Texture texture;
protected double[] textureMatrix;
protected Layer lastLayer;
protected long lastFollowTerrainUpdateTime;
protected final List<FastShapeRenderListener> renderListeners = new ArrayList<FastShapeRenderListener>();
public FastShape(List<Position> positions, int mode)
{
this(positions, null, mode);
}
public FastShape(List<Position> positions, int[] indices, int mode)
{
this.mode = mode;
setPositions(positions);
setIndices(indices);
}
@Override
public double getDistanceFromEye()
{
return distanceFromEye;
}
@Override
public void pick(DrawContext dc, Point pickPoint)
{
if (useOrderedRendering && !dc.isOrderedRenderingMode())
{
render(dc);
return;
}
Color color = getColor();
boolean lighted = isLighted();
boolean textured = isTextured();
boolean deepPicking = dc.isDeepPickingEnabled();
try
{
Color uniqueColor = dc.getUniquePickColor();
pickSupport.clearPickList();
pickSupport.addPickableObject(uniqueColor.getRGB(), this);
setColor(uniqueColor);
setLighted(false);
setTextured(false);
dc.setDeepPickingEnabled(true);
try
{
pickSupport.beginPicking(dc);
render(dc);
}
finally
{
pickSupport.endPicking(dc);
pickSupport.resolvePick(dc, pickPoint, pickLayer);
}
}
finally
{
setColor(color);
setLighted(lighted);
setTextured(textured);
dc.setDeepPickingEnabled(deepPicking);
}
}
@Override
public void render(DrawContext dc)
{
if (!isEnabled())
{
return;
}
if (!dc.getGLRuntimeCapabilities().isUseVertexBufferObject())
{
String message = "Vertex Buffer Objects are disabled or unsupported by your graphics card."; //$NON-NLS-1$
Logging.logger().severe(message);
setEnabled(false);
return;
}
if (dc.getCurrentLayer() != null)
{
lastLayer = dc.getCurrentLayer();
}
//Store all the parameters locally, so they don't change in the middle of rendering.
//This means we don't have to lock the writeLock when changing render parameters.
boolean backfaceCulling = isBackfaceCulling();
Sphere boundingSphere = this.boundingSphere;
Color color = getColor();
boolean colorBufferEnabled = isColorBufferEnabled();
boolean fogEnabled = isFogEnabled();
boolean forceSortedPrimitives = isForceSortedPrimitives();
boolean lighted = isLighted();
Double lineWidth = getLineWidth();
int mode = getMode();
Double pointConstantAttenuation = getPointConstantAttenuation();
Double pointLinearAttenuation = getPointLinearAttenuation();
Double pointQuadraticAttenuation = getPointQuadraticAttenuation();
Double pointMinSize = getPointMinSize();
Double pointMaxSize = getPointMaxSize();
Double pointSize = getPointSize();
boolean pointSprite = isPointSprite();
WWTexture pointTexture = this.pointTexture;
URL pointTextureUrl = getPointTextureUrl();
boolean sortTransparentPrimitives = isSortTransparentPrimitives();
Texture texture = getTexture();
boolean textured = isTextured();
double[] textureMatrix = getTextureMatrix();
boolean twoSidedLighting = isTwoSidedLighting();
boolean wireframe = isWireframe();
boolean willCalculateNormals = willCalculateNormals();
boolean useOrderedRenderingMode = isUseOrderedRendering();
double alpha = getOpacity();
if (dc.getCurrentLayer() != null)
{
alpha *= dc.getCurrentLayer().getOpacity();
}
recalculateIfRequired(dc, alpha);
if (positions.isEmpty() || vertexVBO.getBuffer() == null)
{
return;
}
if (boundingSphere == null || !dc.getView().getFrustumInModelCoordinates().intersects(boundingSphere))
{
return;
}
if (useOrderedRenderingMode)
{
if (!dc.isOrderedRenderingMode())
{
alphaForOrderedRenderingMode = alpha;
pickLayer = dc.getCurrentLayer();
dc.addOrderedRenderable(this);
return;
}
alpha = alphaForOrderedRenderingMode;
}
if (dc.isPickingMode())
{
alpha = 1;
}
GL2 gl = dc.getGL().getGL2();
OGLStackHandler stack = new OGLStackHandler();
try
{
notifyRenderListenersOfPreRender(dc);
boolean colorBufferContainsAlpha = colorVBO.getBuffer() != null && colorVBO.getElementStride() > 3;
boolean willUseSortedIndices =
(forceSortedPrimitives || (sortTransparentPrimitives && alpha < 1.0))
&& sortedIndexVBO.getBuffer() != null;
boolean willUsePointSprite = mode == GL2.GL_POINTS && pointSprite && pointTextureUrl != null;
boolean willUseTextureBlending = (alpha < 1.0 || color != null) && colorBufferContainsAlpha;
if (willUsePointSprite && pointTexture == null)
{
try
{
BufferedImage image = ImageIO.read(pointTextureUrl.openStream());
pointTexture = new BasicWWTexture(image, true);
}
catch (IOException e)
{
e.printStackTrace();
willUsePointSprite = false;
}
}
if (willUseTextureBlending && blankTexture == null)
{
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
blankTexture = new BasicWWTexture(image, true);
}
int attributesToPush = GL2.GL_CURRENT_BIT | GL2.GL_POINT_BIT;
if (!fogEnabled)
{
attributesToPush |= GL2.GL_FOG_BIT;
}
if (wireframe || backfaceCulling)
{
attributesToPush |= GL2.GL_POLYGON_BIT;
}
if (lighted)
{
attributesToPush |= GL2.GL_LIGHTING_BIT;
}
if (willUseSortedIndices)
{
attributesToPush |= GL2.GL_DEPTH_BUFFER_BIT;
}
if (lineWidth != null)
{
attributesToPush |= GL2.GL_LINE_BIT;
}
if (willUsePointSprite || willUseTextureBlending || (textured && texture != null))
{
attributesToPush |= GL2.GL_TEXTURE_BIT;
}
stack.pushAttrib(gl, attributesToPush);
stack.pushClientAttrib(gl, GL2.GL_CLIENT_VERTEX_ARRAY_BIT);
Vec4 referenceCenter = boundingSphere.getCenter();
dc.getView().pushReferenceCenter(dc, referenceCenter);
if (lineWidth != null)
{
gl.glLineWidth(lineWidth.floatValue());
}
if (pointSize != null)
{
gl.glPointSize(pointSize.floatValue());
}
if (pointMinSize != null)
{
gl.glPointParameterf(GL2.GL_POINT_SIZE_MIN, pointMinSize.floatValue());
}
if (pointMaxSize != null)
{
gl.glPointParameterf(GL2.GL_POINT_SIZE_MAX, pointMaxSize.floatValue());
}
if (pointConstantAttenuation != null || pointLinearAttenuation != null || pointQuadraticAttenuation != null)
{
float ca = pointConstantAttenuation != null ? pointConstantAttenuation.floatValue() : 1f;
float la = pointLinearAttenuation != null ? pointLinearAttenuation.floatValue() : 0f;
float qa = pointQuadraticAttenuation != null ? pointQuadraticAttenuation.floatValue() : 0f;
gl.glPointParameterfv(GL2.GL_POINT_DISTANCE_ATTENUATION, new float[] { ca, la, qa }, 0);
}
if (!fogEnabled)
{
gl.glDisable(GL2.GL_FOG);
}
if (wireframe)
{
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
}
if (backfaceCulling)
{
gl.glEnable(GL2.GL_CULL_FACE);
gl.glCullFace(GL2.GL_BACK);
}
if (lighted)
{
Vec4 cameraPosition = dc.getView().getEyePoint();
Vec4 lightPos = cameraPosition.subtract3(referenceCenter);
float[] lightPosition = { (float) lightPos.x, (float) lightPos.y, (float) lightPos.z, 1.0f };
float[] lightAmbient = { 0.0f, 0.0f, 0.0f, 1.0f };
float[] lightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
float[] lightSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
float[] modelAmbient = { 0.3f, 0.3f, 0.3f, 1.0f };
gl.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, modelAmbient, 0);
gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, lightPosition, 0);
gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, lightDiffuse, 0);
gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_AMBIENT, lightAmbient, 0);
gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_SPECULAR, lightSpecular, 0);
gl.glDisable(GL2.GL_LIGHT0);
gl.glEnable(GL2.GL_LIGHT1);
gl.glEnable(GL2.GL_LIGHTING);
gl.glEnable(GL2.GL_COLOR_MATERIAL);
gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, twoSidedLighting ? GL2.GL_TRUE : GL2.GL_FALSE);
}
if (willUsePointSprite)
{
gl.glEnable(GL2.GL_POINT_SMOOTH);
gl.glEnable(GL2.GL_POINT_SPRITE);
//stage 0: previous (color) * texture
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
gl.glTexEnvi(GL2.GL_POINT_SPRITE, GL2.GL_COORD_REPLACE, GL2.GL_TRUE);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_REPLACE);
//TODO consider (instead of 2 calls above):
//gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS);
//gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_SRC1_RGB, GL.GL_TEXTURE);
//gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_MODULATE);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2GL3.GL_SRC1_ALPHA, GL2.GL_TEXTURE);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_MODULATE);
pointTexture.bind(dc);
}
if (willUseTextureBlending)
{
float r = 1, g = 1, b = 1;
if (color != null)
{
r = color.getRed() / 255f;
g = color.getGreen() / 255f;
b = color.getBlue() / 255f;
}
//stage 1: previous (color) * texture envionment color
gl.glActiveTexture(willUsePointSprite ? GL2.GL_TEXTURE1 : GL2.GL_TEXTURE0);
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_CONSTANT);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_MODULATE);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2GL3.GL_SRC1_ALPHA, GL2.GL_CONSTANT);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_MODULATE);
gl.glTexEnvfv(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_COLOR, new float[] { r, g, b, (float) alpha }, 0);
blankTexture.bind(dc);
}
if (textured && texture != null)
{
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glEnable(GL2.GL_TEXTURE_2D);
texture.bind(gl);
}
if (textureMatrix != null && textureMatrix.length >= 16)
{
stack.pushTexture(gl);
gl.glLoadMatrixd(textureMatrix, 0);
}
if (colorBufferEnabled)
{
FloatVBO vbo = dc.isPickingMode() && pickingColorVBO.getBuffer() != null ? pickingColorVBO : colorVBO;
if (vbo.getBuffer() != null)
{
gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
vbo.bind(gl);
gl.glColorPointer(vbo.getElementStride(), GL2.GL_FLOAT, 0, 0);
}
}
if (color != null && !willUseTextureBlending)
{
float r = color.getRed() / 255f, g = color.getGreen() / 255f, b = color.getBlue() / 255f;
gl.glColor4f(r, g, b, (float) alpha);
}
if (textureCoordinateVBO.getBuffer() != null)
{
gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
textureCoordinateVBO.bind(gl);
gl.glTexCoordPointer(textureCoordinateVBO.getElementStride(), GL2.GL_FLOAT, 0, 0);
}
if (alpha < 1.0 || colorBufferContainsAlpha || willUseSortedIndices)
{
gl.glEnable(GL2.GL_BLEND);
gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
}
gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
vertexVBO.bind(gl);
gl.glVertexPointer(vertexVBO.getElementStride(), GL2.GL_FLOAT, 0, 0);
if (willCalculateNormals)
{
gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
normalVBO.bind(gl);
gl.glNormalPointer(GL2.GL_FLOAT, 0, 0);
}
if (willUseSortedIndices)
{
gl.glDepthMask(false);
sortedIndexVBO.bind(gl);
gl.glDrawElements(mode, sortedIndexVBO.getBuffer().length, GL2.GL_UNSIGNED_INT, 0);
}
else if (indexVBO.getBuffer() != null)
{
indexVBO.bind(gl);
gl.glDrawElements(mode, indexVBO.getBuffer().length, GL2.GL_UNSIGNED_INT, 0);
}
else
{
gl.glDrawArrays(mode, 0, vertexVBO.getBuffer().length / vertexVBO.getElementStride());
}
//unbind the buffers
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);
}
finally
{
stack.pop(gl);
dc.getView().popReferenceCenter(dc);
notifyRenderListenersOfPostRender(dc);
}
}
protected void recalculateIfRequired(DrawContext dc, double alpha)
{
boolean followTerrainRecalculationRequired = false;
if (followTerrain)
{
long currentTime = System.currentTimeMillis();
if (currentTime - lastFollowTerrainUpdateTime > getFollowTerrainUpdateFrequency())
{
lastFollowTerrainUpdateTime = currentTime;
followTerrainRecalculationRequired = true;
}
}
boolean recalculateVertices =
followTerrainRecalculationRequired || elevationChanged || verticesDirty || lastGlobe != dc.getGlobe()
|| lastVerticalExaggeration != dc.getVerticalExaggeration();
if (recalculateVertices)
{
boolean willRecalculate = recalculateVertices(dc, false);
if (willRecalculate)
{
lastGlobe = dc.getGlobe();
verticesDirty = false;
elevationChanged = false;
lastVerticalExaggeration = dc.getVerticalExaggeration();
}
}
Vec4 eyePoint = dc.getView().getEyePoint();
boolean recalculateIndices =
(forceSortedPrimitives || (sortTransparentPrimitives && alpha < 1.0))
&& (mode == GL2.GL_TRIANGLES || mode == GL2.GL_POINTS) && !eyePoint.equals(lastEyePoint);
if (recalculateIndices)
{
lastEyePoint = eyePoint;
resortIndices(dc, lastEyePoint);
}
}
protected boolean recalculateVertices(final DrawContext dc, boolean runNow)
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
positionsLock.readLock().lock();
try
{
int size = positions.size() * 3;
float[] vertices;
vertexVBO.lock();
try
{
vertices = vertexVBO.getBuffer();
if (vertices == null || vertices.length != size)
{
vertices = new float[size];
}
calculateVertices(dc, vertices);
vertexVBO.setBuffer(vertices);
}
finally
{
vertexVBO.unlock();
}
if (willCalculateNormals())
{
normalVBO.lock();
try
{
float[] normals = normalVBO.getBuffer();
if (normals == null || normals.length != size)
{
normals = new float[size];
}
calculateNormals(vertices, normals);
normalVBO.setBuffer(normals);
}
finally
{
normalVBO.unlock();
}
}
Sphere temp = boundingSphere;
boundingSphere = modBoundingSphere;
modBoundingSphere = temp;
}
finally
{
positionsLock.readLock().unlock();
}
//when the vertices have been recalculated, trigger a render of the layer
if (lastLayer != null)
{
lastLayer.firePropertyChange(AVKey.LAYER, null, lastLayer);
}
}
};
if (runNow)
{
runnable.run();
return true;
}
else
{
return VertexUpdater.run(this, runnable);
}
}
protected synchronized void calculateVertices(DrawContext dc, float[] vertices)
{
int index = 0;
for (LatLon position : positions)
{
Vec4 v = calculateVertex(dc, position);
vertices[index++] = (float) v.x;
vertices[index++] = (float) v.y;
vertices[index++] = (float) v.z;
}
BufferWrapper wrapper = new BufferWrapper.FloatBufferWrapper(FloatBuffer.wrap(vertices));
modBoundingSphere = createBoundingSphere(wrapper);
//prevent NullPointerExceptions when there's no vertices:
if (modBoundingSphere == null)
{
modBoundingSphere = new Sphere(Vec4.ZERO, 1);
}
for (int i = 0; i < vertices.length; i += 3)
{
vertices[i + 0] -= (float) modBoundingSphere.getCenter().x;
vertices[i + 1] -= (float) modBoundingSphere.getCenter().y;
vertices[i + 2] -= (float) modBoundingSphere.getCenter().z;
}
}
protected Vec4 calculateVertex(DrawContext dc, LatLon position)
{
double elevation = this.elevation;
if (followTerrain)
{
elevation += dc.getGlobe().getElevation(position.getLatitude(), position.getLongitude());
}
elevation += calculateElevationOffset(position);
elevation *= dc.getVerticalExaggeration();
elevation = Math.max(elevation, -dc.getGlobe().getMaximumRadius());
return dc.getGlobe().computePointFromPosition(position.add(calculateLatLonOffset()), elevation);
}
protected double calculateElevationOffset(LatLon position)
{
if (position instanceof Position)
{
return ((Position) position).elevation;
}
return 0;
}
protected LatLon calculateLatLonOffset()
{
return LatLon.ZERO;
}
protected static Sphere createBoundingSphere(BufferWrapper wrapper)
{
//the Sphere.createBoundingSphere() function doesn't ensure that the radius is at least 1, causing errors
Vec4[] extrema = Vec4.computeExtrema(wrapper);
if (extrema == null)
{
return null;
}
Vec4 center =
new Vec4((extrema[0].x + extrema[1].x) / 2.0, (extrema[0].y + extrema[1].y) / 2.0,
(extrema[0].z + extrema[1].z) / 2.0);
double radius = Math.max(1, extrema[0].distanceTo3(extrema[1]) / 2.0);
return new Sphere(center, radius);
}
protected void calculateNormals(float[] vertices, float[] normals)
{
int size = normals.length / 3;
int[] count = new int[size];
Vec4[] verts = new Vec4[size];
Vec4[] norms = new Vec4[size];
for (int i = 0, j = 0; i < vertices.length; i += 3, j++)
{
verts[j] = new Vec4(vertices[i + 0], vertices[i + 1], vertices[i + 2]);
norms[j] = new Vec4(0);
}
int[] indices = indexVBO.getBuffer();
boolean hasIndices = indices != null;
int loopLimit = hasIndices ? indices.length : size;
int loopIncrement = 3;
if (mode == GL2.GL_TRIANGLE_STRIP)
{
loopLimit -= 2;
loopIncrement = 1;
}
for (int i = 0; i < loopLimit; i += loopIncrement)
{
//don't touch indices's position/mark, because it may currently be in use by OpenGL thread
int index0 = hasIndices ? indices[i + 0] : i + 0;
int index1 = hasIndices ? indices[i + 1] : i + 1;
int index2 = hasIndices ? indices[i + 2] : i + 2;
Vec4 v0 = verts[index0];
Vec4 v1 = verts[index1];
Vec4 v2 = verts[index2];
Vec4 e1 = v1.subtract3(v0);
Vec4 e2 = mode == GL2.GL_TRIANGLE_STRIP && i % 2 == 0 ? v0.subtract3(v2) : v2.subtract3(v0);
Vec4 N = reverseNormals ? e2.cross3(e1).normalize3() : e1.cross3(e2).normalize3();
// if N is 0, the triangle is degenerate
if (N.getLength3() > 0)
{
norms[index0] = norms[index0].add3(N);
norms[index1] = norms[index1].add3(N);
norms[index2] = norms[index2].add3(N);
count[index0]++;
count[index1]++;
count[index2]++;
}
}
for (int i = 0, j = 0; i < normals.length; i += 3, j++)
{
int c = count[j] > 0 ? count[j] : 1; //prevent divide by zero
normals[i + 0] = (float) norms[j].x / c;
normals[i + 1] = (float) norms[j].y / c;
normals[i + 2] = (float) norms[j].z / c;
}
}
protected synchronized void resortIndices(final DrawContext dc, final Vec4 eyePoint)
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
float[] vertices = vertexVBO.getBuffer();
if (vertices == null)
{
return;
}
int[] indices = indexVBO.getBuffer();
int size = indices != null ? indices.length : vertices.length / 3;
sortedIndexVBO.lock();
try
{
int[] sortedIndices = sortedIndexVBO.getBuffer();
if (sortedIndices == null || sortedIndices.length != size)
{
sortedIndices = new int[size];
}
sortIndices(dc, eyePoint, vertices, indices, sortedIndices);
sortedIndexVBO.setBuffer(sortedIndices);
}
finally
{
sortedIndexVBO.unlock();
}
}
};
IndexUpdater.run(this, runnable);
}
protected void sortIndices(DrawContext dc, Vec4 eyePoint, float[] vertices, int[] indices, int[] sortedIndices)
{
int size = vertices.length / 3;
Vec4[] verts = new Vec4[size];
for (int i = 0, j = 0; i < vertices.length; i += 3, j++)
{
verts[j] = new Vec4(vertices[i + 0], vertices[i + 1], vertices[i + 2]);
}
if (boundingSphere != null)
{
eyePoint = eyePoint.subtract3(boundingSphere.getCenter());
}
if (mode == GL2.GL_TRIANGLES)
{
boolean hasIndices = indices != null;
int triangleCountBy3 = hasIndices ? indices.length : size;
IndexAndDistance[] distances = new IndexAndDistance[triangleCountBy3 / 3];
for (int i = 0, j = 0; i < triangleCountBy3; i += 3, j++)
{
int index0 = hasIndices ? indices[i + 0] : i + 0;
int index1 = hasIndices ? indices[i + 1] : i + 1;
int index2 = hasIndices ? indices[i + 2] : i + 2;
Vec4 v0 = verts[index0];
Vec4 v1 = verts[index1];
Vec4 v2 = verts[index2];
double distance =
v0.distanceToSquared3(eyePoint) + v1.distanceToSquared3(eyePoint)
+ v2.distanceToSquared3(eyePoint);
distances[j] = new IndexAndDistance(distance, i);
}
Arrays.sort(distances);
IndexAndDistance closest = distances[distances.length - 1];
Vec4 closestPoint = verts[hasIndices ? indices[closest.index] : closest.index];
distanceFromEye = closestPoint.distanceTo3(eyePoint);
for (int i = 0, j = 0; i < triangleCountBy3; i += 3, j++)
{
IndexAndDistance distance = distances[j];
sortedIndices[i + 0] = hasIndices ? indices[distance.index + 0] : distance.index + 0;
sortedIndices[i + 1] = hasIndices ? indices[distance.index + 1] : distance.index + 1;
sortedIndices[i + 2] = hasIndices ? indices[distance.index + 2] : distance.index + 2;
}
}
else if (mode == GL2.GL_POINTS)
{
IndexAndDistance[] distances = new IndexAndDistance[size];
for (int i = 0; i < size; i++)
{
double distance = verts[i].distanceToSquared3(eyePoint);
distances[i] = new IndexAndDistance(distance, i);
}
Arrays.sort(distances);
IndexAndDistance closest = distances[distances.length - 1];
Vec4 closestPoint = verts[closest.index];
distanceFromEye = closestPoint.distanceTo3(eyePoint);
for (int i = 0; i < size; i++)
{
sortedIndices[i] = distances[i].index;
}
}
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public Color getColor()
{
return color;
}
public void setColor(Color color)
{
this.color = color;
}
public float[] getColorBuffer()
{
return colorVBO.getBuffer();
}
public void setColorBuffer(float[] colorBuffer)
{
colorVBO.setBuffer(colorBuffer);
}
public int getColorBufferElementSize()
{
return colorVBO.getElementStride();
}
public void setColorBufferElementSize(int colorBufferElementSize)
{
colorVBO.setElementStride(colorBufferElementSize);
}
public float[] getPickingColorBuffer()
{
return pickingColorVBO.getBuffer();
}
public void setPickingColorBuffer(float[] pickingColorBuffer)
{
pickingColorVBO.setBuffer(pickingColorBuffer);
}
public int getPickingColorBufferElementSize()
{
return pickingColorVBO.getElementStride();
}
public void setPickingColorBufferElementSize(int pickingColorBufferElementSize)
{
pickingColorVBO.setElementStride(pickingColorBufferElementSize);
}
public boolean isColorBufferEnabled()
{
return colorBufferEnabled;
}
public void setColorBufferEnabled(boolean useColorBuffer)
{
this.colorBufferEnabled = useColorBuffer;
}
public float[] getTextureCoordinateBuffer()
{
return textureCoordinateVBO.getBuffer();
}
public void setTextureCoordinateBuffer(float[] textureCoordinateBuffer)
{
textureCoordinateVBO.setBuffer(textureCoordinateBuffer);
}
public double getOpacity()
{
return opacity;
}
public void setOpacity(double opacity)
{
this.opacity = opacity;
}
public List<Position> getPositions()
{
return positions;
}
public void setPositions(List<Position> positions)
{
positionsLock.writeLock().lock();
try
{
this.positions = positions;
verticesDirty = true;
bounds = null;
for (Position position : positions)
{
bounds = Bounds.union(bounds, position);
}
}
finally
{
positionsLock.writeLock().unlock();
}
}
public int[] getIndices()
{
return indexVBO.getBuffer();
}
public void setIndices(int[] indices)
{
indexVBO.setBuffer(indices);
}
@Override
public boolean isFollowTerrain()
{
return followTerrain;
}
public void setFollowTerrain(boolean followTerrain)
{
this.followTerrain = followTerrain;
verticesDirty = true;
}
public long getFollowTerrainUpdateFrequency()
{
return followTerrainUpdateFrequency;
}
public void setFollowTerrainUpdateFrequency(long followTerrainUpdateFrequency)
{
this.followTerrainUpdateFrequency = followTerrainUpdateFrequency;
}
public int getMode()
{
return mode;
}
public double getElevation()
{
return elevation;
}
public void setElevation(double elevation)
{
elevationChanged = this.elevation != elevation;
this.elevation = elevation;
}
public boolean isCalculateNormals()
{
return calculateNormals;
}
public void setCalculateNormals(boolean calculateNormals)
{
this.calculateNormals = calculateNormals;
}
public boolean isReverseNormals()
{
return reverseNormals;
}
public void setReverseNormals(boolean reverseNormals)
{
this.reverseNormals = reverseNormals;
verticesDirty = true;
}
protected boolean willCalculateNormals()
{
return isCalculateNormals() && (getMode() == GL2.GL_TRIANGLES || getMode() == GL2.GL_TRIANGLE_STRIP);
}
public boolean isFogEnabled()
{
return fogEnabled;
}
public void setFogEnabled(boolean fogEnabled)
{
this.fogEnabled = fogEnabled;
}
@Override
public boolean isWireframe()
{
return wireframe;
}
@Override
public void setWireframe(boolean wireframe)
{
this.wireframe = wireframe;
}
public boolean isBackfaceCulling()
{
return backfaceCulling;
}
public void setBackfaceCulling(boolean backfaceCulling)
{
this.backfaceCulling = backfaceCulling;
}
public boolean isLighted()
{
return lighted;
}
public void setLighted(boolean lighted)
{
this.lighted = lighted;
}
public boolean isTwoSidedLighting()
{
return twoSidedLighting;
}
public void setTwoSidedLighting(boolean twoSidedLighting)
{
this.twoSidedLighting = twoSidedLighting;
}
public boolean isSortTransparentPrimitives()
{
return sortTransparentPrimitives;
}
public void setSortTransparentPrimitives(boolean sortTransparentPrimitives)
{
this.sortTransparentPrimitives = sortTransparentPrimitives;
}
public boolean isForceSortedPrimitives()
{
return forceSortedPrimitives;
}
public void setForceSortedPrimitives(boolean forceSortedPrimitives)
{
this.forceSortedPrimitives = forceSortedPrimitives;
}
public Double getLineWidth()
{
return lineWidth;
}
public void setLineWidth(Double lineWidth)
{
this.lineWidth = lineWidth;
}
public Double getPointSize()
{
return pointSize;
}
public void setPointSize(Double pointSize)
{
this.pointSize = pointSize;
}
public Double getPointMinSize()
{
return pointMinSize;
}
public void setPointMinSize(Double pointMinSize)
{
this.pointMinSize = pointMinSize;
}
public Double getPointMaxSize()
{
return pointMaxSize;
}
public void setPointMaxSize(Double pointMaxSize)
{
this.pointMaxSize = pointMaxSize;
}
public Double getPointConstantAttenuation()
{
return pointConstantAttenuation;
}
public void setPointConstantAttenuation(Double pointConstantAttenuation)
{
this.pointConstantAttenuation = pointConstantAttenuation;
}
public Double getPointLinearAttenuation()
{
return pointLinearAttenuation;
}
public void setPointLinearAttenuation(Double pointLinearAttenuation)
{
this.pointLinearAttenuation = pointLinearAttenuation;
}
public Double getPointQuadraticAttenuation()
{
return pointQuadraticAttenuation;
}
public void setPointQuadraticAttenuation(Double pointQuadraticAttenuation)
{
this.pointQuadraticAttenuation = pointQuadraticAttenuation;
}
public boolean isPointSprite()
{
return pointSprite;
}
public void setPointSprite(boolean pointSprite)
{
this.pointSprite = pointSprite;
}
public URL getPointTextureUrl()
{
return pointTextureUrl;
}
public void setPointTextureUrl(URL pointTextureUrl)
{
this.pointTextureUrl = pointTextureUrl;
pointTexture = null;
}
public Texture getTexture()
{
return texture;
}
public void setTexture(Texture texture)
{
this.texture = texture;
}
public boolean isTextured()
{
return textured;
}
public void setTextured(boolean textured)
{
this.textured = textured;
}
public double[] getTextureMatrix()
{
return textureMatrix;
}
public void setTextureMatrix(double[] textureMatrix)
{
this.textureMatrix = textureMatrix;
}
public boolean isUseOrderedRendering()
{
return useOrderedRendering;
}
public void setUseOrderedRendering(boolean useOrderedRendering)
{
this.useOrderedRendering = useOrderedRendering;
}
@Override
public long getSizeInBytes()
{
//very approximate, measured by checking JVM memory usage over many object creations
return 500 + 80 * getPositions().size();
}
/**
* @return The extent of this shape. This is calculated by
* {@link FastShape#render(DrawContext)}, so don't use this for
* frustum culling.
*/
public Extent getExtent()
{
return boundingSphere;
}
@Override
public Bounds getBounds()
{
positionsLock.readLock().lock();
try
{
return bounds;
}
finally
{
positionsLock.readLock().unlock();
}
}
public void addRenderListener(FastShapeRenderListener renderListener)
{
renderListeners.add(renderListener);
}
public void removeRenderListener(FastShapeRenderListener renderListener)
{
renderListeners.remove(renderListener);
}
protected void notifyRenderListenersOfPreRender(DrawContext dc)
{
for (int i = renderListeners.size() - 1; i >= 0; i--)
{
renderListeners.get(i).shapePreRender(dc, this);
}
}
protected void notifyRenderListenersOfPostRender(DrawContext dc)
{
for (int i = renderListeners.size() - 1; i >= 0; i--)
{
renderListeners.get(i).shapePostRender(dc, this);
}
}
public static float[] color4ToFloats(List<Color> colors)
{
return color4ToFloats(colors, new float[colors.size() * 4]);
}
public static float[] color4ToFloats(List<Color> colors, float[] floats)
{
int i = 0;
for (Color color : colors)
{
floats[i++] = color.getRed() / 255f;
floats[i++] = color.getGreen() / 255f;
floats[i++] = color.getBlue() / 255f;
floats[i++] = color.getAlpha() / 255f;
}
return floats;
}
public static float[] color3ToFloats(List<Color> colors)
{
return color3ToFloats(colors, new float[colors.size() * 3]);
}
public static float[] color3ToFloats(List<Color> colors, float[] floats)
{
int i = 0;
for (Color color : colors)
{
floats[i++] = color.getRed() / 255f;
floats[i++] = color.getGreen() / 255f;
floats[i++] = color.getBlue() / 255f;
}
return floats;
}
}