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

src.gov.nasa.worldwind.symbology.milstd2525.graphics.areas.Ambush 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.areas;

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

import java.util.*;

/**
 * Implementation of the Ambush graphic (2.X.2.6.1.1).
 *
 * @author pabercrombie
 * @version $Id: Ambush.java 1585 2013-09-06 00:12:52Z pabercrombie $
 */
public class Ambush extends AbstractMilStd2525TacticalGraphic
{
    /** Default length of the arrowhead, as a fraction of the total line length. */
    public final static double DEFAULT_ARROWHEAD_LENGTH = 0.2;
    /**
     * Default angle of the arc. Control points 2 and 3 define a chord of a circle. This chord and the arc angle fully
     * define the circle, of which the arc is a segment.
     */
    public final static Angle DEFAULT_ARC_ANGLE = Angle.fromDegrees(60.0);
    /** Default angle of the arrowhead. */
    public final static Angle DEFAULT_ARROWHEAD_ANGLE = Angle.fromDegrees(70.0);
    /**
     * Default length of the legs of the graphic's base, as a fraction of the distance between the control points the
     * define the base.
     */
    public final static double DEFAULT_LEG_LENGTH = 0.5;

    /** Default number of intervals used to draw the arc. */
    public final static int DEFAULT_NUM_INTERVALS = 32;
    /** Default number of legs to draw on the graphic's arc. */
    public final static int DEFAULT_NUM_LEGS = 6;

    /** Number of intervals used to draw the arc. */
    protected int intervals = DEFAULT_NUM_INTERVALS;
    /** The arc is drawn as a segment of a circle intersected by this angle. */
    protected Angle arcAngle = DEFAULT_ARC_ANGLE;
    /** Length of the arrowhead from base to tip, as a fraction of the total line length. */
    protected Angle arrowAngle = DEFAULT_ARROWHEAD_ANGLE;
    /** Angle of the arrowhead. */
    protected double arrowLength = DEFAULT_ARROWHEAD_LENGTH;
    /** Number of "legs" drawn on this graphic's arc. */
    protected int numLegs = DEFAULT_NUM_LEGS;
    /**
     * Length of the legs on the graphic's base, as a fraction of the distance between the control points that define
     * the base.
     */
    protected double legLength = DEFAULT_LEG_LENGTH;

    /** First control point. */
    protected Position position1;
    /** Second control point. */
    protected Position position2;
    /** Third control point. */
    protected Position position3;

    /** Path used to render the graphic. */
    protected Path[] paths;

    /**
     * Data required for intermediate calculations while generating the Ambush graphic. This object is created and
     * populated by {@link Ambush#computeArc(gov.nasa.worldwind.render.DrawContext)}.
     */
    protected static class ArcData
    {
        /** Position at the midpoint of the arc. This point falls on the arc. */
        Position midpoint;
        /** Center of the circle, of which the arc is segment. */
        LatLon center;
        /** Radius of the circle. */
        double radius;
        /** Angle from North at which the arc begins. */
        Angle startAngle;
        /**
         * Angular length of the arc. Note that this is different than {@link Ambush#arcAngle}: this angle is signed,
         * where as Ambush.arcAngle is unsigned. This angle is calculated such that the end angle of the arc is equal to
         * {@code arcData.arcAngle + arcData.startAngle}.
         */
        Angle arcAngle;
        /** Direction of the arc. This vector points from the center of the circle to the midpoint of the arc. */
        Vec4 direction;
    }

    /**
     * Indicates the graphics supported by this class.
     *
     * @return List of masked SIDC strings that identify graphics that this class supports.
     */
    public static List getSupportedGraphics()
    {
        return Arrays.asList(TacGrpSidc.C2GM_SPL_LNE_AMB);
    }

    /**
     * Create a new graphic.
     *
     * @param sidc Symbol code the identifies the graphic.
     */
    public Ambush(String sidc)
    {
        super(sidc);
    }

    /**
     * Indicates the number of intervals used to draw the arc in this graphic.
     *
     * @return Intervals used to draw arc.
     */
    public int getIntervals()
    {
        return this.intervals;
    }

    /**
     * Specifies the number of intervals used to draw the arc in this graphic. More intervals will result in a smoother
     * looking arc.
     *
     * @param intervals Number of intervals for drawing the arc.
     */
    public void setIntervals(int intervals)
    {
        if (intervals < 1)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", intervals);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.intervals = intervals;
        this.onShapeChanged();
    }

    /**
     * Indicates the angle of this graphic's arc. The arc is drawn as a segment of a circle defined by this angle and
     * the chord formed by control points 2 and 3.
     *
     * @return Angle of the circular segment that forms this graphic's arc.
     */
    public Angle getArcAngle()
    {
        return this.arcAngle;
    }

    /**
     * Specifies the angle of this graphic's arc. The arc is drawn as a segment of a circle defined by this angle and
     * the chord formed by control points 2 and 3. A greater angle makes the arc more curved and a smaller angle makes
     * the arc less curved.
     *
     * @param arcAngle Angle of the circular segment that forms this graphic's arc.
     */
    public void setArcAngle(Angle arcAngle)
    {
        if (arcAngle == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.arcAngle = arcAngle;
    }

    /**
     * Indicates the angle of the arrowhead.
     *
     * @return Angle of the arrowhead in the graphic.
     */
    public Angle getArrowAngle()
    {
        return this.arrowAngle;
    }

    /**
     * Specifies the angle of the arrowhead in the graphic.
     *
     * @param arrowAngle The angle of the arrowhead. Must be greater than zero degrees and less than 90 degrees.
     */
    public void setArrowAngle(Angle arrowAngle)
    {
        if (arrowAngle == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (arrowAngle.degrees <= 0 || arrowAngle.degrees >= 90)
        {
            String msg = Logging.getMessage("generic.AngleOutOfRange");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.arrowAngle = arrowAngle;
        this.onShapeChanged();
    }

    /**
     * Indicates the length of the arrowhead.
     *
     * @return The length of the arrowhead as a fraction of the total line length.
     */
    public double getArrowLength()
    {
        return this.arrowLength;
    }

    /**
     * Specifies the length of the arrowhead.
     *
     * @param arrowLength Length of the arrowhead as a fraction of the total line length. If the arrowhead length is
     *                    0.25, then the arrowhead length will be one quarter of the total line length.
     */
    public void setArrowLength(double arrowLength)
    {
        if (arrowLength < 0)
        {
            String msg = Logging.getMessage("generic.ArgumentOutOfRange");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.arrowLength = arrowLength;
        this.onShapeChanged();
    }

    /**
     * Indicates how many "legs" stick out of the back side of this graphic's arc.
     *
     * @return Number of legs drawn on the arc of this graphic.
     */
    public int getLegs()
    {
        return this.numLegs;
    }

    /**
     * Specifies how many "legs" stick out of the back side of this graphic's arc.
     *
     * @param numLegs Number of legs to draw on the arc of this graphic.
     */
    public void setLegs(int numLegs)
    {
        if (numLegs < 0)
        {
            String msg = Logging.getMessage("generic.ArgumentOutOfRange");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.numLegs = numLegs;
        this.onShapeChanged();
    }

    /**
     * Indicates the length of legs on the graphic's base.
     *
     * @return The length of the legs on the base, as a fraction of the distance between the control points that define
     *         the base.
     */
    public double getLegLength()
    {
        return this.legLength;
    }

    /**
     * Specifies the length of the legs on the graphic's base.
     *
     * @param legLength Length of the legs on the graphic's base, as a fraction of the distance between the control
     *                  points that define the base.
     */
    public void setLegLength(double legLength)
    {
        if (legLength < 0)
        {
            String msg = Logging.getMessage("generic.ArgumentOutOfRange");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.legLength = legLength;
        this.onShapeChanged();
    }

    /**
     * {@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);
        }

        try
        {
            Iterator iterator = positions.iterator();
            this.position1 = iterator.next();
            this.position2 = iterator.next();
            this.position3 = iterator.next();
        }
        catch (NoSuchElementException e)
        {
            String message = Logging.getMessage("generic.InsufficientPositions");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.onShapeChanged();
    }

    /** {@inheritDoc} */
    public Iterable getPositions()
    {
        return Arrays.asList(this.position1, this.position2, this.position3);
    }

    /** {@inheritDoc} */
    public Position getReferencePosition()
    {
        return this.position1;
    }

    /** {@inheritDoc} */
    protected void doRenderGraphic(DrawContext dc)
    {
        if (this.paths == null)
        {
            this.createShapes(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);
        }
    }

    protected void onShapeChanged()
    {
        this.paths = null; // Need to recompute paths
    }

    /**
     * Create the paths required to draw the graphic.
     *
     * @param dc Current draw context.
     */
    protected void createShapes(DrawContext dc)
    {
        // This graphic requires three paths, plus one path for each of the legs.
        this.paths = new Path[3 + this.getLegs()];

        ArcData arcData = this.computeArc(dc);

        // Create a path for the arc.
        List positions = this.computeArcPositions(dc, arcData);
        this.paths[0] = this.createPath(positions);

        // Create a path for the line part of the arrow
        this.paths[1] = this.createPath(Arrays.asList(arcData.midpoint, this.position1));

        // Create a path for the arrow head
        positions = this.computeArrowheadPositions(dc, this.position1, arcData);
        this.paths[2] = this.createPath(positions);

        // Create the legs. The legs will be placed in the paths array starting at index 3.
        this.createLegs(dc, arcData, this.paths, 3, this.getLegs());
    }

    /**
     * Compute the arc. The arc is a segment of a circle defined by a chord and the arc angle. Control points 2 and 3
     * define the chord.
     *
     * @param dc Current draw context.
     *
     * @return Data that describes the arc.
     */
    protected ArcData computeArc(DrawContext dc)
    {
        Globe globe = dc.getGlobe();

        // The graphic looks like this:
        //
        //   A \
        //      \
        //   Mid |______\
        //       |      /
        //      /
        //   B /

        Vec4 pA = globe.computePointFromPosition(this.position2);
        Vec4 pB = globe.computePointFromPosition(this.position3);

        Position baseMidpoint = new Position(LatLon.interpolate(0.5, this.position2, this.position3), 0);
        Vec4 pMid = globe.computePointFromPosition(baseMidpoint);

        // Compute a vector perpendicular to the segment AB and the normal vector
        Vec4 vAB = pB.subtract3(pA);
        Vec4 normal = globe.computeSurfaceNormalAtPoint(pMid);
        Vec4 perpendicular = vAB.cross3(normal).normalize3();

        ArcData arcData = new ArcData();

        double chordLength = pA.distanceTo3(pB);
        Angle arcAngle = this.getArcAngle();

        // The arc is drawn as a segment of a circle defined by a chord and the arc angle. Control points 2 and 3 define
        // the chord. Compute the radius of the circle, and the distance from the center of the circle to the chord from
        // this information. See http://mathworld.wolfram.com/CircularSegment.html and
        // http://en.wikipedia.org/wiki/Circular_segment for background on these formulas.
        arcData.radius = chordLength / (2 * arcAngle.sinHalfAngle());
        double dist = arcData.radius * arcAngle.cosHalfAngle();

        // Determine which side of the line AB the arrow point falls on. We want the arc face away from the arrow, so
        // set the direction of the perpendicular component according to the sign of the scalar triple product.
        Vec4 vOrientation = globe.computePointFromPosition(this.position1).subtract3(pMid);
        double tripleProduct = perpendicular.dot3(vOrientation);
        double sign = (tripleProduct > 0) ? -1 : 1;

        arcData.direction = perpendicular.multiply3(sign);

        // Find the center point of the circle
        Vec4 pCenter = pMid.add3(arcData.direction.multiply3(dist));
        arcData.center = globe.computePositionFromPoint(pCenter);

        // Compute the start and sweep angles for the arc
        arcData.startAngle = LatLon.greatCircleAzimuth(arcData.center, this.position2);
        Angle endAngle = LatLon.greatCircleAzimuth(arcData.center, this.position3);

        // Compute the angle between the start and end points. Note that we cannot use Angle.angularDistance because
        // we need a signed distance here.
        double diffDegrees = endAngle.subtract(arcData.startAngle).degrees;
        if (diffDegrees < -180)
            diffDegrees += 360;
        else if (diffDegrees > 180)
            diffDegrees -= 360;

        arcData.arcAngle = Angle.fromDegrees(diffDegrees);

        // Find the midpoint of the arc
        double globeRadius = globe.getRadiusAt(arcData.center.getLatitude(), arcData.center.getLongitude());
        LatLon ll = LatLon.greatCircleEndPosition(arcData.center,
            arcData.arcAngle.divide(2.0).radians + arcData.startAngle.radians, arcData.radius / globeRadius);
        arcData.midpoint = new Position(ll, 0);

        return arcData;
    }

    /**
     * Compute positions required to draw the arc.
     *
     * @param dc      Current draw context.
     * @param arcData Data that describes the arc.
     *
     * @return Positions along the arc.
     */
    protected List computeArcPositions(DrawContext dc, ArcData arcData)
    {
        Globe globe = dc.getGlobe();

        Angle da = arcData.arcAngle.divide(this.intervals);
        double globeRadius = globe.getRadiusAt(arcData.center.getLatitude(), arcData.center.getLongitude());
        double radiusRadians = arcData.radius / globeRadius;

        // Compute the arc positions
        int intervals = this.getIntervals();
        List positions = new ArrayList(intervals);

        for (int i = 0; i < intervals; i++)
        {
            double angle = i * da.radians + arcData.startAngle.radians;

            LatLon ll = LatLon.greatCircleEndPosition(arcData.center, angle, radiusRadians);
            positions.add(new Position(ll, 0));
        }
        positions.add(this.position3); // Control point 3 is the end of the arc

        return positions;
    }

    /**
     * Create paths for the graphic's "legs". The legs stick out of the back side of the arc.
     *
     * @param dc         Current draw context.
     * @param arcData    Data that describes the graphic's arc.
     * @param paths      Array to receive the new paths.
     * @param startIndex Index into {@code paths} at which to place the first leg path.
     * @param pathCount  Number of leg paths to create. The {@code paths} array must have length of at least {@code
     *                   startIndex + pathCount}.
     */
    protected void createLegs(DrawContext dc, ArcData arcData, Path[] paths, int startIndex, int pathCount)
    {
        Globe globe = dc.getGlobe();

        Vec4 p1 = globe.computePointFromPosition(this.position1);
        Vec4 pMid = globe.computePointFromLocation(arcData.midpoint);

        //     __\
        //     ___\
        //     ____|Mid___\ Pt. 1
        //     ____|      /
        //     ___/
        //       /
        //  ^ Legs^

        // The end point of each leg will be computed by adding an offset in the direction of the arrow to a point on
        // the arc.
        Vec4 vOffset = pMid.subtract3(p1);
        vOffset = vOffset.normalize3().multiply3(vOffset.getLength3() * this.getLegLength());

        Angle da = arcData.arcAngle.divide(pathCount);
        double globeRadius = globe.getRadiusAt(arcData.center.getLatitude(), arcData.center.getLongitude());
        double radiusRadians = arcData.radius / globeRadius;

        for (int i = 0; i < pathCount; i++)
        {
            double angle = (i + 0.5) * da.radians + arcData.startAngle.radians;

            LatLon ll = LatLon.greatCircleEndPosition(arcData.center, angle, radiusRadians);

            Vec4 start = globe.computePointFromLocation(ll);
            Vec4 end = start.add3(vOffset);

            paths[startIndex + i] = this.createPath(TacticalGraphicUtil.asPositionList(globe, start, end));
        }
    }

    /**
     * Determine the positions that make up the arrowhead.
     *
     * @param dc      Current draw context.
     * @param tip     Position of the arrow head tip.
     * @param arcData Data that describes the arc of this graphic.
     *
     * @return Positions that define the arrowhead.
     */
    protected List computeArrowheadPositions(DrawContext dc, Position tip, ArcData arcData)
    {
        Globe globe = dc.getGlobe();
        //             _
        //        A\    | 1/2 width
        // ________B\  _|
        //          /
        //       C/
        //        | |
        //       Length

        Vec4 pB = globe.computePointFromPosition(tip);

        double baseLength = LatLon.greatCircleDistance(arcData.midpoint, tip).radians * globe.getRadius();
        double arrowLength = baseLength * this.getArrowLength();

        // Find the point at the base of the arrowhead
        Vec4 arrowBase = pB.add3(arcData.direction.normalize3().multiply3(arrowLength));

        Vec4 normal = globe.computeSurfaceNormalAtPoint(arrowBase);

        // Compute the length of the arrowhead
        double arrowHalfWidth = arrowLength * this.getArrowAngle().tanHalfAngle();

        // Compute a vector perpendicular to the segment and the normal vector
        Vec4 perpendicular = arcData.direction.cross3(normal);
        perpendicular = perpendicular.normalize3().multiply3(arrowHalfWidth);

        // Find points A and C
        Vec4 pA = arrowBase.add3(perpendicular);
        Vec4 pC = arrowBase.subtract3(perpendicular);

        return TacticalGraphicUtil.asPositionList(globe, pA, pB, pC);
    }

    /**
     * Create and configure the Path used to render this graphic.
     *
     * @param positions Positions that define the path.
     *
     * @return New path configured with defaults appropriate for this type of graphic.
     */
    protected Path createPath(List positions)
    {
        Path path = new Path(positions);
        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