Package com.bbn.openmap.omGraphics.util

Source Code of com.bbn.openmap.omGraphics.util.ArcCalc

// **********************************************************************
//
// <copyright>
//
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
//
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/util/ArcCalc.java,v $
// $RCSfile: ArcCalc.java,v $
// $Revision: 1.3.2.2 $
// $Date: 2005/08/10 22:45:14 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.omGraphics.util;

import java.awt.Graphics;
import java.awt.Point;
import java.io.Serializable;

import com.bbn.openmap.MoreMath;
import com.bbn.openmap.omGraphics.OMColor;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMLine;
import com.bbn.openmap.omGraphics.OMRect;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;

/**
* A class that calculates an arc between two points, given the point
* coordinates, and an arc measurement that represents, in radians,
* the length of the part of the circle that should be represented by
* the arc.
*/
public class ArcCalc implements Serializable {

    /** Debugging list showing algorithm points. */
    protected transient OMGraphicList arcGraphics = null;

    protected transient int[] xpoints;
    protected transient int[] ypoints;

    /**
     * This setting is the amount of an angle, limited to a
     * semi-circle (PI) that the curve will represent. In other words,
     * the arc between the two end points is going to look like a 0
     * degrees of a circle (straight line, which is the default), or
     * 180 degrees of a circle (full semi-circle). Given in radians,
     * though, not degrees. OK?
     */
    protected double arcAngle = 0;

    /**
     * For x-y and offset lines that have an arc drawn between them,
     * tell which way the arc should be drawn, toward the Equator, or
     * away from it, generally. Default is true, to make it look like
     * great circle line for northern hemishere lines.
     */
    protected boolean arcUp = true;

    /**
     * Set to true if the points for the arc line up from x2, y2 to
     * x1, y1
     */
    protected boolean reversed = false;

    /**
     * Set the arc that is drawn between the points of a x-y or offset
     * line. If the arc amount is negative, the arc will be flipped
     * over.
     *
     * @param aa arcAngle, in radians, between 0-PI.
     * @param putArcUp arc peak above points.
     */
    public ArcCalc(double aa, boolean putArcUp) {
        arcAngle = aa;
        arcUp = putArcUp;

        // If it's negative, flip it over...
        if (aa < 0) {
            arcAngle *= -1.0;
            arcUp = !arcUp;
        }

        if (arcAngle > Math.PI) {
            arcAngle = Math.PI;
        }
    }

    /**
     * Return the arc angle set for this line. Will only be set if it
     * was set externally.
     *
     * @return arc angle in radians.
     */
    public double getArcAngle() {
        return arcAngle;
    }

    /**
     * Returns true if the arc direction setting is upward, meaning
     * that the peak of the arc is above (or more so) the line that
     * goes between the two points.
     */
    public boolean isArcUp() {
        return arcUp;
    }

    /**
     * Generate the points that will generate the curved line between
     * two points. The arcAngle is the number of radians of a circle
     * that the arc should represent. Math.PI is the Max. The
     * setArcAngle should be called before this method is called, so
     * that the method knoes what to create.
     */
    public void generate(int x1, int y1, int x2, int y2) {

        // The algorithm.
        //
        // Draw a straight line between the points, and figure out the
        // center point between them on the line. Then, on another
        // line that is perpendicular to the first line, figure out
        // where the point is that will act as a center of a circle.
        // That circle needs to pass through both points, and the
        // radius is such that the arc angle of the circle between the
        // points is the same as the arcAngle set for the ArcCalc.
        // Then, the arc needs to be generated. This is done by
        // looking at the circle, and figuring out the angle (from 0
        // to 2PI) that the line from the center to point 1, and then
        // the center to point 2. This gives us the angular extents
        // of the arc. Then we need to figure out the angle
        // increments needed to get good coordinates for the arc.
        // Then, starting at the low arc angle, we increment it to get
        // the coordinates for the arced line, a given radius away
        // from the circle center, between the arc angle extents.

        Point midPoint = new Point();
        Point arcCenter = new Point();
        Point peakPoint = new Point();

        // pixel distance between points.
        double distance = Math.sqrt(Math.pow(Math.abs(y2 - y1), 2.0)
                + Math.pow(Math.abs(x2 - x1), 2.0));
        // slope of straight line between points.
        double straightLineSlope = Math.atan((double) (y2 - y1)
                / (double) (x2 - x1));

        // slope of line that the arc focus will reside on.
        double inverseSlope = straightLineSlope - (Math.PI / 2.0);

        if (Debug.debugging("arc")) {
            Debug.output("ArcCalc.generate: Slope is "
                    + (straightLineSlope * 180.0 / Math.PI)
                    + " degrees, distance = " + distance + " pixels.");
        }

        // centerX/Y is the midpoint between the two points.
        midPoint.x = x1 + ((x2 - x1) / 2);
        midPoint.y = y1 + ((y2 - y1) / 2);

        if (Debug.debugging("arc")) {
            Debug.output("ArcCalc.generate: Center point for (" + x1 + ", "
                    + y1 + ") to (" + x2 + ", " + y2 + ") is (" + midPoint.x
                    + ", " + midPoint.y + ")");
        }

        double arccos = Math.cos(arcAngle);
        double arcRadius;

        if (arccos != 1.0) {
            arcRadius = distance / Math.sqrt(2.0 * (1.0 - Math.cos(arcAngle)));
        } else {
            arcRadius = distance / Math.sqrt(2.0);
        }

        if (Debug.debugging("arc")) {
            Debug.output("ArcCalc.generate: radius of arc = " + arcRadius);
        }

        // R' is the distance down the inverse negative slope of the
        // line that the focus of the arc is located.

        // x is the distance along the right leg of the arc that is
        // left over after Rcos(arcAngle) is subtracted from it, in
        // order to derive the angle of the straight line between the
        // two points.

        double x = arcRadius - arcRadius * Math.cos(arcAngle);

        double rPrime = (distance / 2.0)
                * (Math.sqrt(1.0 - Math.pow(x / distance, 2.0)))
                / Math.sin(arcAngle / 2.0);

        if (Debug.debugging("arc")) {
            Debug.output("ArcCalc.generate: rPrime = " + rPrime);
        }

        int direction = 1;
        if (arcUp)
            direction = -1;

        // arcCenter.x and arcCenter.y are the coordinates of the
        // focus of the Arc.
        arcCenter.x = midPoint.x
                + (direction * (int) (rPrime * Math.cos(inverseSlope)));
        arcCenter.y = midPoint.y
                + (direction * (int) (rPrime * Math.sin(inverseSlope)));

        if (Debug.debugging("arc")) {

            Debug.output("ArcCalc.generateArc: creating supplimental graphics list");
            arcGraphics = new OMGraphicList();

            double dist1 = Math.sqrt(Math.pow((double) (arcCenter.x - x1), 2.0)
                    + Math.pow((double) (arcCenter.y - y1), 2.0));
            double dist2 = Math.sqrt(Math.pow((double) (arcCenter.x - x2), 2.0)
                    + Math.pow((double) (arcCenter.y - y2), 2.0));

            Debug.output("ArcCalc.generate: Center focus for arc is ("
                    + arcCenter.x + ", " + arcCenter.y
                    + ") along slope line of "
                    + (inverseSlope * 180.0 / Math.PI) + " degrees).");
            Debug.output("ArcCalc.generate: Distance to point 1 from arc focus = "
                    + dist1
                    + "\n                    Distance to point 2 from arc focus = "
                    + dist2);

            // Let's hightlight the end points.
            OMRect point1 = new OMRect(x1 - 1, y1 - 1, x1 + 1, y1 + 1);
            OMRect point2 = new OMRect(x2 - 1, y2 - 1, x2 + 1, y2 + 1);
            OMRect arcPoint = new OMRect(arcCenter.x - 1, arcCenter.y - 1, arcCenter.x + 1, arcCenter.y + 1);

            point1.setLinePaint(OMColor.red);
            point2.setLinePaint(OMColor.red);
            arcPoint.setLinePaint(OMColor.blue);
            arcGraphics.add(point1);
            arcGraphics.add(point2);
            arcGraphics.add(arcPoint);

            OMLine line1 = new OMLine(x1, y1, x2, y2);
            OMLine line2 = new OMLine(midPoint.x, midPoint.y, arcCenter.x, arcCenter.y);
            arcGraphics.add(line1);
            arcGraphics.add(line2);
        }

        int realCount = 0;

        // Figure out the arc extents for each endpoint. I think
        // it's easier to keep track of the angles if they are always
        // positive, and we always go from smaller to larger.
        double startSlope = getRealAngle(arcCenter.x, arcCenter.y, x1, y1);
        double endSlope = getRealAngle(arcCenter.x, arcCenter.y, x2, y2);

        double smallSlope, largeSlope;
        double angleIncrement;

        smallSlope = (startSlope > endSlope) ? endSlope : startSlope;
        largeSlope = (smallSlope == startSlope) ? endSlope : startSlope;

        // Have to make sure we take the smaller arc around the
        // circle.
        while (Math.abs(smallSlope - largeSlope) > Math.PI) {
            if (Math.abs(largeSlope - smallSlope - Math.PI) < .001) {
                // Catch 180 degree angles that are close enough...
                break;
            }

            Debug.message("arc",
                    "ArcCalc.generate: Modifying the starting slope.");
            double tmpSlope = smallSlope + MoreMath.TWO_PI;
            smallSlope = largeSlope;
            largeSlope = tmpSlope;
        }

        // Experienced some trouble with vertical and horizonal half
        // circles. This took care of that.
        if (arcAngle == Math.PI && arcUp) {
            Debug.message("arc",
                    "ArcCalc.generate: Modifying 180 angle points.");
            double tmpSlope = smallSlope + MoreMath.TWO_PI;
            smallSlope = largeSlope;
            largeSlope = tmpSlope;
        }

        // Figure out the angle increment for grabbing coordinates -
        // use the larger dimension of the arc end point differences.
        if (Math.abs(y2 - y1) < Math.abs(x2 - x1)) {
            angleIncrement = Math.PI / Math.abs(x2 - x1);
        } else {
            angleIncrement = Math.PI / Math.abs(y2 - y1);
        }

        int numPoints = (int) (Math.abs(smallSlope - largeSlope)
                / angleIncrement + 2);
        int[] xPoints = new int[numPoints];
        int[] yPoints = new int[numPoints];

        if (Debug.debugging("arc")) {
            Debug.output("ArcCalc.generate: angle to x1, y1 is " + startSlope
                    + " (" + (startSlope * 180.0 / Math.PI)
                    + " degrees), angle to x2, y2 is " + endSlope + " ("
                    + (endSlope * 180.0 / Math.PI) + " degrees)");

            Debug.output("ArcCalc.generate: Starting angle is " + smallSlope
                    + "(" + (smallSlope * 180.0 / Math.PI)
                    + " degrees), end angle is " + largeSlope + " ("
                    + (largeSlope * 180.0 / Math.PI)
                    + " degrees), incrementing by " + angleIncrement + " ("
                    + (angleIncrement * 180.0 / Math.PI) + " degrees)");
        }

        reversed = false;
        // Get the coordinates of the arc from the arc extents.
        while (smallSlope < largeSlope && realCount < numPoints) {

            xPoints[realCount] = arcCenter.x
                    + (int) (arcRadius * Math.cos(smallSlope));
            yPoints[realCount] = arcCenter.y
                    + (int) (arcRadius * Math.sin(smallSlope));

            if (realCount == 0 && xPoints[realCount] == x2) {
                Debug.message("arc", "ArcCalc: line reversed");
                reversed = true;
            }

            if (Debug.debugging("arc") && realCount == 0) {
                OMLine startLine = new OMLine(arcCenter.x, arcCenter.y, xPoints[0], yPoints[0]);
                startLine.setLinePaint(OMColor.white);
                arcGraphics.add(startLine);
            } else if (Debug.debugging("arcdetail")) {
                Debug.output("  angle " + smallSlope + " (" + smallSlope * 180
                        / Math.PI + " degrees)  = " + xPoints[realCount] + ", "
                        + yPoints[realCount]);
            }

            if (Math.abs(largeSlope - smallSlope - (arcAngle / 2.0)) < angleIncrement) {
                // Found the halfway point, mark it...
                peakPoint.x = xPoints[realCount];
                peakPoint.y = yPoints[realCount];
                Debug.message("arc", "ArcCalc: Found a midpoint.");
            }

            smallSlope += angleIncrement;
            realCount++;
        }

        // Give the coordinates to the OMLine.
        xpoints = new int[realCount];
        ypoints = new int[realCount];

        System.arraycopy(xPoints, 0, xpoints, 0, realCount);
        System.arraycopy(yPoints, 0, ypoints, 0, realCount);
    }

    /**
     * Given the straight line between two points, figure out the
     * angle, in radians, of that line in relation to the coordinate
     * system on the screen. Always returns a positive value, and the
     * angle is from point 1 to point 2.
     */
    protected double getRealAngle(int x1, int y1, int x2, int y2) {
        double angle = 0;

        double horDiff = (double) (x2 - x1);
        double vertDiff = (double) (y2 - y1);

        // If there is no horizontal difference, then it's pointing
        // up or down.
        if (horDiff == 0) {
            if (vertDiff > 0) {
                angle = MoreMath.HALF_PI;
            } else if (vertDiff < 0) {
                angle = -MoreMath.HALF_PI;
            }
        } else {
            angle = Math.atan(vertDiff / horDiff);

            // It's pointed in the wrong direction... fix it here.
            if (horDiff < 0) {
                angle += Math.PI;
            }
        }

        // Either way, I think we want to make the angle positive.
        while (angle < 0) {
            angle += MoreMath.TWO_PI;
        }
        return angle;
    }

    public int[] getXPoints() {
        return xpoints;
    }

    public int[] getYPoints() {
        return ypoints;
    }

    public void generate(Projection proj) {
        if (proj != null && arcGraphics != null) {
            arcGraphics.generate(proj);
        }
    }

    public void render(Graphics g) {
        if (arcGraphics != null) {
            Debug.output("OMLine rendering " + arcGraphics.size()
                    + " arcGraphics.");
            arcGraphics.render(g);
        }
    }

    public OMGraphicList getArcGraphics() {
        if (arcGraphics == null) {
            return new OMGraphicList();
        } else {
            return arcGraphics;
        }
    }

    public boolean getReversed() {
        return reversed;
    }
}
TOP

Related Classes of com.bbn.openmap.omGraphics.util.ArcCalc

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.