All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.symbology.milstd2525.graphics.lines.AbstractAxisArrow Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * 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.symbology.milstd2525.graphics.lines;

import gov.nasa.worldwind.WorldWind;
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.symbology.milstd2525.AbstractMilStd2525TacticalGraphic;
import gov.nasa.worldwind.util.Logging;

import java.util.*;

/**
 * Base class for axis of advance arrow graphics. These arrows are specified by control points along the center line of
 * the arrow. The final control point determines either the width of the arrowhead (default) or the width of the route.
 * MIL-STD-2525C (pg. 517) specifies that the final control point determines the width of the arrowhead, but some
 * applications may prefer to use this control point to set the width of the route precisely. The {@link
 * #isFinalPointWidthOfRoute() finalPointWidthOfRoute} field controls this behavior.
 *
 * @author pabercrombie
 * @version $Id: AbstractAxisArrow.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public abstract class AbstractAxisArrow extends AbstractMilStd2525TacticalGraphic
{
    /** Path used to render the line. */
    protected Path[] paths;

    /** Control points that define the shape. */
    protected Iterable positions;

    /** Positions computed from the control points, used to draw the arrow path. */
    protected List arrowPositions;

    /**
     * Indicates whether the final control point marks the width of the route or the width of the arrowhead. Default is
     * the width of the arrowhead.
     */
    protected boolean finalPointWidthOfRoute;

    /**
     * Create a new arrow graphic.
     *
     * @param sidc Symbol code the identifies the graphic.
     */
    public AbstractAxisArrow(String sidc)
    {
        this(sidc, 1);
    }

    /**
     * Create a new arrow graphic made up of more than one path. This constructor allocates the {@link #paths}, and
     * creates the requested number of paths. The first element in the array is the main path that outlines the arrow.
     * Subclasses are responsible for configuring the other paths.
     *
     * @param sidc     Symbol code the identifies the graphic.
     * @param numPaths Number of paths to create.
     */
    public AbstractAxisArrow(String sidc, int numPaths)
    {
        super(sidc);

        if (numPaths < 1)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", numPaths);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.paths = new Path[numPaths];
        for (int i = 0; i < numPaths; i++)
        {
            this.paths[i] = this.createPath();
        }
    }

    /**
     * {@inheritDoc}
     *
     * @param positions Control points that orient the graphic. Must provide at least three points.
     */
    public void setPositions(Iterable positions)
    {
        if (positions == null)
        {
            String message = Logging.getMessage("nullValue.PositionsListIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Ensure that the position list provides at least 3 control points.
        try
        {
            Iterator iterator = positions.iterator();
            iterator.next();
            iterator.next();
            iterator.next();
        }
        catch (NoSuchElementException e)
        {
            String message = Logging.getMessage("generic.InsufficientPositions");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.positions = positions;
        this.arrowPositions = null; // Need to recompute path for the new control points
    }

    /** {@inheritDoc} */
    public Iterable getPositions()
    {
        return this.positions;
    }

    /** {@inheritDoc} */
    public Position getReferencePosition()
    {
        if (this.positions != null)
        {
            return this.positions.iterator().next(); // use the first position
        }
        return null;
    }

    /** {@inheritDoc} */
    protected void doRenderGraphic(DrawContext dc)
    {
        if (this.arrowPositions == null)
        {
            this.createShapePositions(dc);
        }

        for (Path path : this.paths)
        {
            path.render(dc);
        }
    }

    /** {@inheritDoc} */
    protected void applyDelegateOwner(Object owner)
    {
        if (this.paths == null)
            return;

        for (Path path : this.paths)
        {
            path.setDelegateOwner(owner);
        }
    }

    /**
     * Indicates whether or not the final control point marks the width of the arrowhead or the width of the route.
     * MIL-STD-2525C specifies that the final control point marks the edge of the arrow (pg. 517), and this is the
     * default. However, some applications may prefer to specify the width of the route rather than the width of the
     * arrowhead. In the diagram below, the default behavior is for the final control point to specify point A. When
     * {@code finalPointWidthOfRoute} is true the final control point specifies point B instead.
     * 

*

     *                 A
     *                 |\
     *                 | \
     * ----------------|  \
     *                 B   \
     *                     /
     * ----------------|  /
     *                 | /
     *                 |/
     * 
* * @return True if the final control point determines the width of the route instead of the width of the arrow head * (point B in the diagram above). */ public boolean isFinalPointWidthOfRoute() { return this.finalPointWidthOfRoute; } /** * Specifies whether or not the final control point marks the edge of the arrow head or the width of the route. See * {@link #isFinalPointWidthOfRoute()} for details. * * @param finalPointIsWidthOfRoute True if the final control point determines the width of the route instead of the * width of the arrow head. */ public void setFinalPointWidthOfRoute(boolean finalPointIsWidthOfRoute) { this.finalPointWidthOfRoute = finalPointIsWidthOfRoute; } /** * Create the list of positions that describe the arrow. * * @param dc Current draw context. */ protected void createShapePositions(DrawContext dc) { Globe globe = dc.getGlobe(); // Collect positions in two lists, one for points on the left side of the control line, and one for the right side. List leftPositions = new ArrayList(); List rightPositions = new ArrayList(); List arrowHeadPositions = new ArrayList(); double halfWidth = this.createArrowHeadPositions(leftPositions, rightPositions, arrowHeadPositions, globe); this.createLinePositions(leftPositions, rightPositions, halfWidth, globe); Collections.reverse(leftPositions); List allPositions = new ArrayList(leftPositions); allPositions.addAll(arrowHeadPositions); allPositions.addAll(rightPositions); this.arrowPositions = allPositions; this.paths[0].setPositions(allPositions); // Apply positions to the main path } /** * Create positions that make up the arrow head. *

* The arrow head is defined by the first two control points, and the last point. Pt. 1' is the point on the center * line at the base of the arrow head, and Pt. N' is the reflection of Pt. N about the center line. *

     *                 Pt N
     *                 |\
     * Left line       | \
     * ----------------|  \
     * Pt 2        Pt 1'   \ Pt 1
     *                     /
     * ----------------|  /
     * Right line      | /
     *                 |/Pt N'
     * 
* * @param leftPositions List to collect positions on the left arrow line. This list receives the position where * the left line meets the arrow head. * @param rightPositions List to collect positions on the right arrow line. This list receives the position * where the right line meets the arrow head. * @param arrowHeadPositions List to collect positions that make up the arrow head. This list receives positions for * Pt. N, Pt. 1, and Pt. N', in that order. * @param globe Current globe. * * @return The distance from the center line to the left and right lines. */ protected double createArrowHeadPositions(List leftPositions, List rightPositions, List arrowHeadPositions, Globe globe) { Iterator iterator = this.positions.iterator(); Position pos1 = iterator.next(); Position pos2 = iterator.next(); Position posN = null; while (iterator.hasNext()) { posN = iterator.next(); } if (posN == null) { String message = Logging.getMessage("generic.InsufficientPositions"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } Vec4 pt1 = globe.computePointFromLocation(pos1); Vec4 pt2 = globe.computePointFromLocation(pos2); Vec4 ptN = globe.computePointFromLocation(posN); // Compute a vector that points from Pt. 1 toward Pt. 2 Vec4 v12 = pt1.subtract3(pt2).normalize3(); Vec4 pt1_prime = pt1.add3(v12.multiply3(ptN.subtract3(pt1).dot3(v12))); // If the final control point determines the width of the route (not the width of the arrowhead) then compute // Point N from the final control point. if (this.isFinalPointWidthOfRoute()) { ptN = ptN.add3(ptN.subtract3(pt1_prime)); posN = globe.computePositionFromPoint(ptN); } Vec4 ptN_prime = pt1_prime.subtract3(ptN.subtract3(pt1_prime)); Vec4 normal = globe.computeSurfaceNormalAtPoint(pt1_prime); // Compute the distance from the center line to the left and right lines. double halfWidth = ptN.subtract3(pt1_prime).getLength3() / 2; Vec4 offset = normal.cross3(v12).normalize3().multiply3(halfWidth); Vec4 pLeft = pt1_prime.add3(offset); Vec4 pRight = pt1_prime.subtract3(offset); Position posLeft = globe.computePositionFromPoint(pLeft); Position posRight = globe.computePositionFromPoint(pRight); leftPositions.add(posLeft); rightPositions.add(posRight); Position posN_prime = globe.computePositionFromPoint(ptN_prime); // Compute the scalar triple product of the vector 12, the normal vector, and a vector from the center line // toward Pt. N to determine if the offset points to the left or the right of the control line. double tripleProduct = offset.dot3(ptN.subtract3(pt1_prime)); if (tripleProduct < 0) { Position tmp = posN; // Swap N and N' posN = posN_prime; posN_prime = tmp; } arrowHeadPositions.add(posN); arrowHeadPositions.add(pos1); arrowHeadPositions.add(posN_prime); return halfWidth; } /** * Create positions that make up the left and right arrow lines. * * @param leftPositions List to collect positions on the left line. * @param rightPositions List to collect positions on the right line. * @param halfWidth Distance from the center line to the left or right lines. Half the width of the arrow's * double lines. * @param globe Current globe. */ protected void createLinePositions(List leftPositions, List rightPositions, double halfWidth, Globe globe) { Iterator iterator = positions.iterator(); Position posB = iterator.next(); Position posA = iterator.next(); // Starting at the arrow head end of the line, take points three at a time. B is the current control point, A is // the next point in the line, and C is the previous point. We need to a find a vector that bisects angle ABC. // B // ---------> C // / // / // / // A / Vec4 pA = globe.computePointFromLocation(posA); Vec4 pB = globe.computePointFromLocation(posB); Vec4 pC; while (iterator.hasNext()) { posA = iterator.next(); pC = pB; pB = pA; pA = globe.computePointFromLocation(posA); Vec4 offset; Vec4 normal = globe.computeSurfaceNormalAtPoint(pB); Vec4 vBC = pC.subtract3(pB); // Compute a vector perpendicular to segment BC, and the globe normal vector. Vec4 perpendicular = vBC.cross3(normal); if (iterator.hasNext() && !Vec4.areColinear(pA, pB, pC)) { Vec4 vBA = pA.subtract3(pB); // Calculate the vector that bisects angle ABC. offset = vBA.normalize3().add3(vBC.normalize3()); offset = offset.normalize3(); // Compute the scalar triple product of the vector BC, the normal vector, and the offset vector to // determine if the offset points to the left or the right of the control line. double tripleProduct = perpendicular.dot3(offset); if (tripleProduct < 0) { offset = offset.multiply3(-1); } } else { // If this is the last control point then don't consider the surrounding points, just compute an offset // perpendicular to the control line. offset = perpendicular.normalize3(); } // Determine the length of the offset vector that will keep the left and right lines parallel to the control // line. Angle theta = vBC.angleBetween3(offset); double length = halfWidth / theta.sin(); offset = offset.multiply3(length); // Determine the left and right points by applying the offset. Vec4 pRight = pB.add3(offset); Vec4 pLeft = pB.subtract3(offset); // Convert cartesian points to geographic. Position posLeft = globe.computePositionFromPoint(pLeft); Position posRight = globe.computePositionFromPoint(pRight); leftPositions.add(posLeft); rightPositions.add(posRight); } } /** * Create and configure the Path used to render this graphic. * * @return New path configured with defaults appropriate for this type of graphic. */ protected Path createPath() { Path path = new Path(); path.setFollowTerrain(true); path.setPathType(AVKey.GREAT_CIRCLE); path.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); path.setDelegateOwner(this.getActiveDelegateOwner()); path.setAttributes(this.getActiveShapeAttributes()); return path; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy