/*
Copyright (C) 2001, 2006, 2007 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.render;
import gov.nasa.worldwind.Locatable;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.SectorGeometryList;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.util.Logging;
import javax.media.opengl.GL;
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
/**
* Basic implementation of AnnotationRenderer. Process Annotation rendering as OrderedRenderable objects batch.
*
* @author Patrick Murris
* @version $Id: BasicAnnotationRenderer.java 5178 2008-04-25 21:51:20Z patrickmurris $
* @see AbstractAnnotation
* @see AnnotationAttributes
* @see AnnotationLayer
*/
public class BasicAnnotationRenderer implements AnnotationRenderer
{
private PickSupport pickSupport = new PickSupport();
private static boolean isAnnotationValid(Annotation annotation, boolean checkPosition)
{
if (annotation == null || annotation.getText() == null)
return false;
//noinspection RedundantIfStatement
if (checkPosition && annotation instanceof Locatable)
return ((Locatable)annotation).getPosition() != null;
return true;
}
public void pick(DrawContext dc, Iterable<Annotation> annotations, Point pickPoint, Layer layer)
{
this.drawMany(dc, annotations);
}
public void pick(DrawContext dc, Annotation annotation, Vec4 annotationPoint, java.awt.Point pickPoint, Layer layer)
{
if (!isAnnotationValid(annotation, false))
return;
this.drawOne(dc, annotation, annotationPoint);
}
public void render(DrawContext dc, Iterable<Annotation> annotations)
{
this.drawMany(dc, annotations);
}
public void render(DrawContext dc, Annotation annotation, Vec4 annotationPoint)
{
if (!isAnnotationValid(annotation, false))
return;
this.drawOne(dc, annotation, annotationPoint);
}
private void drawMany(DrawContext dc, Iterable<Annotation> annotations)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (dc.getVisibleSector() == null)
return;
SectorGeometryList geos = dc.getSurfaceGeometry();
//noinspection RedundantIfStatement
if (geos == null)
return;
if (annotations == null)
{
String msg = Logging.getMessage("nullValue.AnnotationIterator");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Iterator<Annotation> iterator = annotations.iterator();
if (!iterator.hasNext())
return;
while (iterator.hasNext())
{
Annotation annotation = iterator.next();
if (!isAnnotationValid(annotation, true))
continue;
if (!annotation.getAttributes().isVisible())
continue;
// TODO: cull annotations that are beyound the horizon or outside the view frustrum
double eyeDistance = 1;
if (annotation instanceof Locatable)
{
// Determine Cartesian position from the surface geometry if the annotation is near the surface,
// otherwise draw it from the globe.
Vec4 annotationPoint = getAnnotationDrawPoint(dc, annotation);
if (annotationPoint == null)
continue;
eyeDistance = annotation.isAlwaysOnTop() ? 0 : dc.getView().getEyePoint().distanceTo3(annotationPoint);
}
// The annotations aren't drawn here, but added to the ordered queue to be drawn back-to-front.
dc.addOrderedRenderable(new OrderedAnnotation(annotation, eyeDistance));
}
}
private void drawOne(DrawContext dc, Annotation annotation, Vec4 annotationPoint)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (dc.getVisibleSector() == null)
return;
SectorGeometryList geos = dc.getSurfaceGeometry();
//noinspection RedundantIfStatement
if (geos == null)
return;
if (!annotation.getAttributes().isVisible())
return;
double eyeDistance = 1;
if (annotation instanceof Locatable)
{
if (annotationPoint == null)
{
Position pos = ((Locatable)annotation).getPosition();
if (!dc.getVisibleSector().contains(pos.getLatitude(), pos.getLongitude()))
return;
// Determine Cartesian position from the surface geometry if the annotation is near the surface,
// otherwise draw it from the globe.
annotationPoint = getAnnotationDrawPoint(dc, annotation);
if (annotationPoint == null)
return;
}
if (!dc.getView().getFrustumInModelCoordinates().contains(annotationPoint))
return;
double horizon = dc.getView().computeHorizonDistance();
eyeDistance = annotation.isAlwaysOnTop() ? 0 : dc.getView().getEyePoint().distanceTo3(annotationPoint);
if (eyeDistance > horizon)
return;
}
// The annotation isn't drawn here, but added to the ordered queue to be drawn back-to-front.
dc.addOrderedRenderable(new OrderedAnnotation(annotation, eyeDistance));
}
/**
* Get the final Vec4 point at which an annotation will be drawn. If the annotation Position elevation
* is lower then the highest elevation on the globe, it will be drawn above the ground using its elevation as an
* offset. Otherwise, the original elevation will be used.
* @param dc the current DrawContext.
* @param annotation the annotation
* @return the annotation draw cartesian point
*/
public Vec4 getAnnotationDrawPoint(DrawContext dc, Annotation annotation)
{
Vec4 drawPoint = null;
if(annotation instanceof Locatable)
{
Position pos = ((Locatable)annotation).getPosition();
if (pos.getElevation() < dc.getGlobe().getMaxElevation())
drawPoint = dc.getSurfaceGeometry().getSurfacePoint(pos.getLatitude(), pos.getLongitude(), pos.getElevation());
if (drawPoint == null)
drawPoint = dc.getGlobe().computePointFromPosition(pos);
}
return drawPoint;
}
private class OrderedAnnotation implements OrderedRenderable
{
Annotation annotation;
double eyeDistance;
Layer layer;
OrderedAnnotation(Annotation annotation, double eyeDistance)
{
this.annotation = annotation;
this.eyeDistance = eyeDistance;
}
OrderedAnnotation(Annotation annotation, Layer layer, double eyeDistance)
{
this.annotation = annotation;
this.eyeDistance = eyeDistance;
this.layer = layer;
}
public double getDistanceFromEye()
{
return this.eyeDistance;
}
public void render(DrawContext dc)
{
BasicAnnotationRenderer.this.beginDrawAnnotations(dc);
try
{
this.annotation.draw(dc);
// Draw as many as we can in a batch to save ogl state switching.
while (dc.getOrderedRenderables().peek() instanceof OrderedAnnotation)
{
OrderedAnnotation oa = (OrderedAnnotation) dc.getOrderedRenderables().poll();
oa.annotation.draw(dc);
}
}
catch (WWRuntimeException e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingAnnotation", e);
}
catch (Exception e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingAnnotation", e);
}
finally
{
BasicAnnotationRenderer.this.endDrawAnnotations(dc);
}
}
public void pick(DrawContext dc, java.awt.Point pickPoint)
{
BasicAnnotationRenderer.this.pickSupport.clearPickList();
BasicAnnotationRenderer.this.beginDrawAnnotations(dc);
try
{
this.annotation.setPickSupport(BasicAnnotationRenderer.this.pickSupport);
this.annotation.draw(dc);
// Draw as many as we can in a batch to save ogl state switching.
while (dc.getOrderedRenderables().peek() instanceof OrderedAnnotation)
{
OrderedAnnotation oa = (OrderedAnnotation) dc.getOrderedRenderables().poll();
oa.annotation.setPickSupport(BasicAnnotationRenderer.this.pickSupport);
oa.annotation.draw(dc);
}
}
catch (WWRuntimeException e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingAnnotation", e);
}
catch (Exception e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhilePickingAnnotation", e);
}
finally
{
BasicAnnotationRenderer.this.endDrawAnnotations(dc);
BasicAnnotationRenderer.this.pickSupport.resolvePick(dc, pickPoint, layer);
BasicAnnotationRenderer.this.pickSupport.clearPickList(); // to ensure entries can be garbage collected
}
}
}
private void beginDrawAnnotations(DrawContext dc)
{
GL gl = dc.getGL();
int attributeMask =
GL.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func
| GL.GL_TRANSFORM_BIT // for modelview and perspective
| GL.GL_VIEWPORT_BIT // for depth range
| GL.GL_CURRENT_BIT // for current color
| GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
| GL.GL_TEXTURE_BIT // for texture env
| GL.GL_DEPTH_BUFFER_BIT // for depth func
| GL.GL_ENABLE_BIT // for enable/disable changes
| GL.GL_LINE_BIT
| GL.GL_HINT_BIT;
gl.glPushAttrib(attributeMask);
// Apply the depth buffer but don't change it.
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
// Suppress any fully transparent image pixels
gl.glEnable(GL.GL_ALPHA_TEST);
gl.glAlphaFunc(GL.GL_GREATER, 0.001f);
gl.glDisable(GL.GL_LIGHTING);
gl.glDisable(GL.GL_CULL_FACE);
// Load a parallel projection with dimensions (viewportWidth, viewportHeight)
int[] viewport = new int[4];
gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glOrtho(0d, viewport[2], 0d, viewport[3], -1d, 1d);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glPushMatrix();
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
if (dc.isPickingMode())
{
this.pickSupport.beginPicking(dc);
/* // Set up to replace the non-transparent texture colors with the single pick color.
gl.glEnable(GL.GL_TEXTURE_2D);
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); */
// No textures, no blend.
gl.glDisable(GL.GL_TEXTURE_2D);
gl.glDisable(GL.GL_BLEND);
}
else
{
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
}
}
private void endDrawAnnotations(DrawContext dc)
{
if (dc.isPickingMode())
this.pickSupport.endPicking(dc);
GL gl = dc.getGL();
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glPopMatrix();
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glPopMatrix();
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPopMatrix();
gl.glPopAttrib();
}
//-- Collision avoidance ---------------------------------------------------
ArrayList<Rectangle> usedRectangles = new ArrayList<Rectangle>();
Point defaultDrawOffset = new Point(-10, 20);
// Try to find a free rectangular space around a point
// TODO: Fix me
private Point computeOffset(Point point, Dimension dimension)
{
Point offset = this.defaultDrawOffset;
Rectangle r = new Rectangle(point.x + offset.x - dimension.width / 2,
point.y + offset.y + dimension.height,
dimension.width, dimension.height);
double radius = 20;
Angle angle = Angle.ZERO;
int step = 0;
int angleStep = 1;
while(rectangleIntersectsUsed(r))
{
// Give up after some number of tries
if(step++ > 100)
{
usedRectangles.clear();
return this.defaultDrawOffset;
}
// Increment angle and radius
int a = 90 + (10 * (angleStep / 2) * (angleStep % 2 == 0 ? 1 : -1));
if(Math.abs(a) <= 10)
{
angleStep = 1;
radius += 50;
}
else
angleStep++;
// Compute new rectangle
angle = Angle.fromDegrees(a);
offset.x = (int)(radius * angle.cos());
offset.y = (int)(radius * angle.sin());
r.setBounds(point.x + offset.x - dimension.width / 2,
point.y + offset.y + dimension.height,
dimension.width, dimension.height);
}
// Keep track of used rectangle
this.usedRectangles.add(r);
return offset;
}
// Test if a rectangle intersects one of the previously used rectangles
private boolean rectangleIntersectsUsed(Rectangle r)
{
for(Rectangle ur : this.usedRectangles)
if(r.intersects(ur))
return true;
return false;
}
}