Package org.geoforge.worldwind.render

Source Code of org.geoforge.worldwind.render.GfrDirectedPath

/*
* Copyright (C) 2001, 2011 United States Government
* as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/

package org.geoforge.worldwind.render;

import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.terrain.Terrain;
import gov.nasa.worldwind.util.Logging;

import gov.nasa.worldwind.util.measure.LengthMeasurer;
import javax.media.opengl.GL;
import java.nio.*;
import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL2;

/**
* A {@link Path} that draws arrowheads between the path positions to indicate direction. All arrowheads are drawn at a
* constant geographic size (the arrows get smaller as the view moves away from the path, and larger as the view get
* closer to the path). One arrowhead is drawn on each path segment, unless the path segment is smaller than the
* arrowhead, in which the arrowhead is not drawn.
*
* @author pabercrombie
* @version $Id: DirectedPath.java 1 2011-07-16 23:22:47Z dcollins $
*/
public class GfrDirectedPath extends Path implements MeasurableLength
{

   /** Default arrow length, in meters. */
   public static final double DEFAULT_ARROW_LENGTH = 300;

   /** Default arrow angle. */
   public static final Angle DEFAULT_ARROW_ANGLE = Angle.fromDegrees(45.0);

   /** Default maximum screen size of the arrowheads, in pixels. */
   public static final double DEFAULT_MAX_SCREEN_SIZE = 20.0;

   /** The length, in meters, of the arrowhead, from tip to base. */
   protected double arrowLength = DEFAULT_ARROW_LENGTH;

   /** The angle of the arrowhead tip. */
   protected Angle arrowAngle = DEFAULT_ARROW_ANGLE;

   /** The maximum screen size, in pixels, of the direction arrowheads. */
   protected double maxScreenSize = DEFAULT_MAX_SCREEN_SIZE;

   protected LengthMeasurer _measurer = null;
  
   /** Creates a path with no positions. */
   public GfrDirectedPath()
   {
      super();
     
      this._measurer = new LengthMeasurer();
     
      this._measurer.setFollowTerrain(this.followTerrain);
      this._measurer.setPathType(Polyline.GREAT_CIRCLE);
   }
   /**
    * Creates a path with specified positions.
    * <p/>
    * Note: If fewer than two positions is specified, no path is drawn.
    *
    * @param positions the path positions. This reference is retained by this shape; the positions are not copied. If
    *                  any positions in the set change, {@link #setPositions(Iterable)} must be called to inform this
    *                  shape of the change.
    *
    * @throws IllegalArgumentException if positions is null.
    */
   public GfrDirectedPath(Iterable<? extends Position> positions)
   {
      super();
     
      this._measurer = new LengthMeasurer();
      this._measurer.setFollowTerrain(this.followTerrain);
      this._measurer.setPathType(Polyline.GREAT_CIRCLE);
     
      this.setPositions(positions);
   }

   /**
    * Creates a path with positions specified via a generic list.
    * <p/>
    * Note: If fewer than two positions is specified, the path is not drawn.
    *
    * @param positions the path positions. This reference is retained by this shape; the positions are not copied. If
    *                  any positions in the set change, {@link #setPositions(Iterable)} must be called to inform this
    *                  shape of the change.
    *
    * @throws IllegalArgumentException if positions is null.
    */
   public GfrDirectedPath(Position.PositionList positions)
   {
      super();
     
      this._measurer = new LengthMeasurer();
      this._measurer.setFollowTerrain(this.followTerrain);
      this._measurer.setPathType(Polyline.GREAT_CIRCLE);
     
      if (positions == null)
        {
            String message = Logging.getMessage("nullValue.PositionsListIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.setPositions(positions.list);
   }

   /**
    * Creates a path between two positions.
    *
    * @param posA the first position.
    * @param posB the second position.
    *
    * @throws IllegalArgumentException if either position is null.
    */
   public GfrDirectedPath(Position posA, Position posB)
   {
      super();
     
      this._measurer = new LengthMeasurer();
      this._measurer.setFollowTerrain(this.followTerrain);
      this._measurer.setPathType(Polyline.GREAT_CIRCLE);
     
     
      if (posA == null || posB == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        List<Position> endPoints = new ArrayList<Position>(2);
        endPoints.add(posA);
        endPoints.add(posB);
        this.setPositions(endPoints);
   }

   /**
    * Indicates the length, in meters, of the direction arrowheads, from base to tip.
    *
    * @return The geographic length of the direction arrowheads.
    */
   public double getArrowLength()
   {
      return this.arrowLength;
   }

   @Override
   public void setPositions(Iterable<? extends Position> positions)
   {
      super.setPositions(positions);
     
      this._measurer.setPositions((ArrayList<? extends Position>)this.positions);
   }

   @Override
   public void setFollowTerrain(boolean followTerrain)
   {
      super.setFollowTerrain(followTerrain);
     
      this._measurer.setFollowTerrain(followTerrain);
   }
  
  

  
  
  
  

   /**
    * Specifies the length, in meters, of the direction arrowheads, from base to tip.
    *
    * @param arrowLength length, in meters, of the direction arrowheads. The length must be greater than zero.
    */
   public void setArrowLength(double arrowLength)
   {
      if (arrowLength <= 0)
      {
         String message = Logging.getMessage("generic.ArgumentOutOfRange", arrowLength);
         Logging.logger().severe(message);
         throw new IllegalArgumentException(message);
      }

      this.arrowLength = arrowLength;
   }

   /**
    * Indicates the maximum screen size, in pixels, of the direction arrowheads. The arrowheads are drawn a fixed
    * geographic, but they are not allowed to get bigger than {@code maxScreenSize} pixels.
    *
    * @return The maximum screen size, in pixels, of the direction arrowheads, measured tip to base.
    */
   public double getMaxScreenSize()
   {
      return this.maxScreenSize;
   }

   /**
    * Specifies the maximum screen size, in pixels, of the direction arrowheads. The arrowheads are drawn at a fixed
    * geographic size, but they will not allowed to get bigger than {@code maxScreenSize} pixels.
    *
    * @param maxScreenSize the maximum screen size, in pixels, of the direction arrowheads, measured tip to base.
    */
   public void setMaxScreenSize(double maxScreenSize)
   {
      if (maxScreenSize <= 0)
      {
         String message = Logging.getMessage("generic.ArgumentOutOfRange", maxScreenSize);
         Logging.logger().severe(message);
         throw new IllegalArgumentException(message);
      }

      this.maxScreenSize = maxScreenSize;
   }

   /**
    * Indicates the angle of the direction arrowheads. A larger angle draws a fat arrowhead, and a smaller angle draws
    * a narrow arrow head.
    *
    * @return The angle of the direction arrowhead tip.
    */
   public Angle getArrowAngle()
   {
      return this.arrowAngle;
   }

   /**
    * Specifies the angle of the direction arrowheads. A larger angle draws a fat arrowhead, and a smaller angle draws
    * a narrow arrow.
    *
    * @param arrowAngle angle of the direction arrowhead tip. Valid values are between 0 degrees and 90 degrees.
    */
   public void setArrowAngle(Angle arrowAngle)
   {
      if (arrowAngle == null)
      {
         String message = Logging.getMessage("nullValue.AngleIsNull", arrowAngle);
         Logging.logger().severe(message);
         throw new IllegalArgumentException(message);
      }

      if ((arrowAngle.compareTo(Angle.ZERO) <= 0) || (arrowAngle.compareTo(Angle.POS90) >= 0))
      {
         String message = Logging.getMessage("generic.ArgumentOutOfRange", arrowAngle);
         Logging.logger().severe(message);
         throw new IllegalArgumentException(message);
      }

      this.arrowAngle = arrowAngle;
   }

   protected static final String ARROWS_KEY = "DirectedPath.DirectionArrows";

   /**
    * {@inheritDoc}
    * <p/>
    * Overridden to also compute the geometry of the direction arrows.
    */
   @Override
   protected void computePath(DrawContext dc, List<Position> positions, PathData pathData)
   {
      super.computePath(dc, positions, pathData);
      this.computeDirectionArrows(dc, pathData);
   }

   @Override
   public double getLength(Globe globe)
   {
      // The length measurer will throw an exception and log the error if globe is null
      return this._measurer.getLength(globe);
   }

   /**
    * Compute the geometry of the direction arrows.
    *
    * @param dc       current draw context.
    * @param pathData the current globe-specific path data.
    */
   protected void computeDirectionArrows(DrawContext dc, PathData pathData)
   {
      IntBuffer polePositions = pathData.getPolePositions();
      int numPositions = polePositions.limit() / 2; // One arrow head for each path segment
      List<Position> tessellatedPositions = pathData.getTessellatedPositions();

      final int FLOATS_PER_ARROWHEAD = 9; // 3 points * 3 coordinates per point
      FloatBuffer buffer = (FloatBuffer) pathData.getValue(ARROWS_KEY);
      if (buffer == null || buffer.capacity() < numPositions * FLOATS_PER_ARROWHEAD)
         buffer = Buffers.newDirectFloatBuffer(FLOATS_PER_ARROWHEAD * numPositions);
      pathData.setValue(ARROWS_KEY, buffer);

      buffer.clear();

      Terrain terrain = dc.getTerrain();

      double arrowBase = this.getArrowLength() * this.getArrowAngle().tanHalfAngle();

      // Step through polePositions to find the original path locations.
      int thisPole = polePositions.get(0) / 2;
      Position poleA = tessellatedPositions.get(thisPole);
      Vec4 polePtA = this.computePoint(terrain, poleA);

      // Draw one arrowhead for each segment in the original position list. The path may be tessellated,
      // so we need to find the tessellated segment halfway between each pair of original positions.
      // polePositions holds indices into the rendered path array of the original vertices. Step through
      // polePositions by 2 because we only care about where the top of the pole is, not the bottom.
      for (int i = 2; i < polePositions.limit(); i += 2)
      {
         // Find the position of this pole and the next pole. Divide by 2 to convert an index in the
         // renderedPath buffer to a index in the tessellatedPositions list.
         int nextPole = polePositions.get(i) / 2;

         Position poleB = tessellatedPositions.get(nextPole);

         Vec4 polePtB = this.computePoint(terrain, poleB);

         // Find the segment that is midway between the two poles.
         int midPoint = (thisPole + nextPole) / 2;

         Position posA = tessellatedPositions.get(midPoint);
         Position posB = tessellatedPositions.get(midPoint + 1);

         Vec4 ptA = this.computePoint(terrain, posA);
         Vec4 ptB = this.computePoint(terrain, posB);

         this.computeArrowheadGeometry(dc, polePtA, polePtB, ptA, ptB, this.getArrowLength(), arrowBase, buffer,
                 pathData);

         thisPole = nextPole;
         polePtA = polePtB;
      }
   }

   /**
    * Compute the geometry of a direction arrow between two points.
    *
    * @param dc          current draw context
    * @param polePtA     the first pole position. This is one of the application defined path positions.
    * @param polePtB     second pole position
    * @param ptA         first position of the tessellated path midway between the poles
    * @param ptB         second position of the tessellated path midway between the poles
    * @param arrowLength length of the arrowhead, in meters. The arrow head may not be rendered at full size, because
    *                    is it may not exceed {@link #maxScreenSize} pixels in length.
    * @param arrowBase   length of the arrowhead base
    * @param buffer      buffer in which to place computed points
    * @param pathData    the current globe-specific path data.
    */
   protected void computeArrowheadGeometry(DrawContext dc, Vec4 polePtA, Vec4 polePtB, Vec4 ptA, Vec4 ptB,
           double arrowLength, double arrowBase, FloatBuffer buffer, PathData pathData)
   {
      // Build a triangle to represent the arrowhead. The triangle is built from two vectors, one parallel to the
      // segment, and one perpendicular to it. The plane of the arrowhead will be parallel to the surface.

      double poleDistance = polePtA.distanceTo3(polePtB);

      // Compute parallel component
      Vec4 parallel = ptA.subtract3(ptB);

      Vec4 surfaceNormal = dc.getGlobe().computeSurfaceNormalAtPoint(ptB);

      // Compute perpendicular component
      Vec4 perpendicular = surfaceNormal.cross3(parallel);

      // Compute midpoint of segment
      Vec4 midPoint = ptA.add3(ptB).divide3(2.0);

      if (!this.isArrowheadSmall(dc, midPoint, 1))
      {
         // Compute the size of the arrowhead in pixels to ensure that the arrow does not exceed the maximum
         // screen size.
         View view = dc.getView();
         double midpointDistance = view.getEyePoint().distanceTo3(midPoint);
         double pixelSize = view.computePixelSizeAtDistance(midpointDistance);
         if (arrowLength / pixelSize > this.maxScreenSize)
         {
            arrowLength = this.maxScreenSize * pixelSize;
            arrowBase = arrowLength * this.getArrowAngle().tanHalfAngle();
         }

         // Don't draw an arrowhead if the path segment is smaller than the arrow
         if (poleDistance <= arrowLength)
            return;

         perpendicular = perpendicular.normalize3().multiply3(arrowBase);
         parallel = parallel.normalize3().multiply3(arrowLength);

         // If the distance between the poles is greater than the arrow length, center the arrow on the midpoint.
         // Otherwise position the tip of the arrow at the midpoint. On short segments it looks weird if the
         // tip of the arrow does not fall on the path, but on longer segments it looks better to center the
         // arrow on the segment.
         if (poleDistance > arrowLength)
            midPoint = midPoint.subtract3(parallel.divide3(2.0));

         // Compute geometry of direction arrow
         Vec4 vertex1 = midPoint.add3(parallel).add3(perpendicular);
         Vec4 vertex2 = midPoint.add3(parallel).add3(perpendicular.multiply3(-1.0));

         // Add geometry to the buffer
         Vec4 referencePoint = pathData.getReferencePoint();
         buffer.put((float) (vertex1.x - referencePoint.x));
         buffer.put((float) (vertex1.y - referencePoint.y));
         buffer.put((float) (vertex1.z - referencePoint.z));

         buffer.put((float) (vertex2.x - referencePoint.x));
         buffer.put((float) (vertex2.y - referencePoint.y));
         buffer.put((float) (vertex2.z - referencePoint.z));

         buffer.put((float) (midPoint.x - referencePoint.x));
         buffer.put((float) (midPoint.y - referencePoint.y));
         buffer.put((float) (midPoint.z - referencePoint.z));
      }
   }
//
//    /** {@inheritDoc} */
//    @Override
//    protected boolean mustRegenerateGeometry(DrawContext dc)
//    {
//        // Path never regenerates geometry for absolute altitude mode paths, but the direction arrows in DirectedPath
//        // need to be recomputed because the view may have changed and the size of the arrows needs to be recalculated.
//        if (this.getCurrentPathData().isExpired(dc))
//            return true;
//
//        return super.mustRegenerateGeometry(dc);
//    }

   /**
    * Determines if an direction arrow drawn a point will be less than a specified number of pixels.
    *
    * @param dc        current draw context
    * @param arrowPt   point at which to draw direction arrow
    * @param numPixels the number of pixels which is considered to be "small"
    *
    * @return {@code true} if an arrow drawn at {@code arrowPt} would occupy less than or equal to {@code numPixels}.
    */
   protected boolean isArrowheadSmall(DrawContext dc, Vec4 arrowPt, int numPixels)
   {
      return this.getArrowLength() <= numPixels * dc.getView().computePixelSizeAtDistance(
              dc.getView().getEyePoint().distanceTo3(arrowPt));
   }

   /**
    * {@inheritDoc}
    * <p/>
    * Overridden to also draw direction arrows.
    *
    * @param dc Current draw context.
    */
   @Override
   protected void doDrawOutline(DrawContext dc)
   {
      this.computeDirectionArrows(dc, this.getCurrentPathData());
      this.drawDirectionArrows(dc, this.getCurrentPathData());
      super.doDrawOutline(dc);
   }

   /**
    * Draws this DirectedPath's direction arrows. Called from {@link #doDrawOutline(gov.nasa.worldwind.render.DrawContext)}
    * before drawing the Path's actual outline.
    * <p/>
    * If this Path is entirely located on the terrain, this applies an offset to the arrow's depth values to to ensure
    * they shows over the terrain. This does not apply a depth offset in any other case to avoid incorrectly drawing
    * the arrows over objects they should be behind, including the terrain. In addition to applying a depth offset,
    * this disables writing to the depth buffer to avoid causing subsequently drawn ordered renderables to incorrectly
    * fail the depth test. Since the arrows are located on the terrain, the terrain already provides the necessary
    * depth values and we can be certain that other ordered renderables should appear on top of them.
    *
    * @param dc       Current draw context.
    * @param pathData the current globe-specific path data.
    */
   protected void drawDirectionArrows(DrawContext dc, PathData pathData)
   {
      GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
      boolean projectionOffsetPushed = false; // keep track for error recovery

      try
      {
         if (this.isSurfacePath())
         {
            // Pull the arrow triangles forward just a bit to ensure they show over the terrain.
            dc.pushProjectionOffest(SURFACE_PATH_DEPTH_OFFSET);
            gl.glDepthMask(false);
            projectionOffsetPushed = true;
         }

         FloatBuffer directionArrows = (FloatBuffer) pathData.getValue(ARROWS_KEY);
         gl.glVertexPointer(3, GL.GL_FLOAT, 0, directionArrows.rewind());
         gl.glDrawArrays(GL.GL_TRIANGLES, 0, directionArrows.limit() / 3);
      }
      finally
      {
         if (projectionOffsetPushed)
         {
            dc.popProjectionOffest();
            gl.glDepthMask(true);
         }
      }
   }

}
TOP

Related Classes of org.geoforge.worldwind.render.GfrDirectedPath

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.