blob: d5aae2cff3ec53d12bfe35fc41f5c74438c90ca7 [file] [log] [blame]
/*******************************************************************************
* <copyright>
*
* Copyright (c) 2005, 2011 SAP AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API, implementation and documentation
* mwenz - Bug 352440 - Fixed deprecation warnings - contributed by Felix Velasco
*
* </copyright>
*
*******************************************************************************/
package org.eclipse.graphiti.ui.internal.util.draw2d;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.geometry.Translatable;
import org.eclipse.draw2d.geometry.Vector;
/**
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class LineSeg implements java.io.Serializable, Translatable {
/**
* Enumeration class for defining the keypoint along a line segment. Can be
* one of <code>ORIGIN</code>, <code>MIDPOINT</code> or
* <code>TERMINUS</code>.
*/
static public class KeyPoint {
/**
* Constant designating the origin point on the line segment.
*/
public static final KeyPoint ORIGIN = new KeyPoint("origin"); //$NON-NLS-1$
/**
* Constant designating the mid point on the line segment.
*/
public static final KeyPoint MIDPOINT = new KeyPoint("midpoint");//$NON-NLS-1$
/**
* Constant designating the terminal point on the line segment.
*/
public static final KeyPoint TERMINUS = new KeyPoint("terminus");//$NON-NLS-1$
private final String name;
private KeyPoint(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
/**
* Enumeration class for defining the orientations of a point relative to
* the line segment. The orientations can be one of <code>POSITIVE</code> or
* <code>NEGATIVE</code>.
*/
static public class Sign {
/**
* Constant designating an orientation that is position relative to the
* lineseg vector.
*/
public static final Sign POSITIVE = new Sign("positive");//$NON-NLS-1$
/**
* Constant designating an orientation that is negative relative to the
* lineseg vector.
*/
public static final Sign NEGATIVE = new Sign("negative");//$NON-NLS-1$
private final String name;
private Sign(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
/**
* Structure to hold onto trig values that represent an angle
*
* @author sshaw
*/
static public class TrigValues {
/**
* Sin theta value
*/
public double sinTheta;
/**
* Cos theta value.
*/
public double cosTheta;
}
static final long serialVersionUID = 1;
final private static int DEFAULT_INTERSECTION_TOLERANCE = 1;
/**
* Constant to avoid divide by zero errors.
*/
private static final float BIGSLOPE = 9999;
/**
* Returns the coefficients of the generalized equation of the line passing
* through points (x1,y1) and (x2,y2) Generalized line equation: ax+by=c =>
* a==result[0], b==result[1], c==result[2]
*
* @param x1
* - x coordinate of the 1st point
* @param y1
* - y coordinate of the 1st point
* @param x2
* - x coordinate of the 2nd point
* @param y2
* - y coordinate of the 2nd point
* @return the coefficients of the generalized equation of the line passing
* through points (x1,y1) and (x2,y2)
*/
public static double[] getLineEquation(double x1, double y1, double x2, double y2) {
double equation[] = new double[3];
for (int i = 0; i < 3; i++)
equation[i] = 0;
if (x1 == x2 && y1 == y2)
return equation;
if (x1 == x2) {
equation[0] = 1;
equation[1] = 0;
equation[2] = x1;
return equation;
}
equation[0] = (y1 - y2) / (x2 - x1);
equation[1] = 1.0;
equation[2] = y2 + equation[0] * x2;
return equation;
}
private Point origin;
private Point terminus;
/**
* Creates a segment using (fromX, fromY) as either the first point of the
* segment (start == Origin) or the midpoint of the segment (start ==
* Midpoint), and using slope as its new slope and len as the new length.
* xdir indicates which direction the segment should go in the x-axis.
*
* @param start
* <code>KeyPoint</code> from which the other parameters are
* relative to
* @param fromX
* int x value of start <code>KeyPoint</code>
* @param fromY
* int y value of start <code>KeyPoint</code>
* @param slope
* <code>float</code> slope of the line
* @param len
* <code>long</code> length of the line
* @param xdir
* direction
*/
public LineSeg(final KeyPoint start, final int fromX, final int fromY, final float slope, final long len, final int xdir) {
super();
origin = new Point();
terminus = new Point();
int dx, dy;
float dx_float;
double len_squared;
// Find the delta y and x needed to get to the end points. See
// pointOn() for explanation of these equations
if (start == KeyPoint.ORIGIN) {
len_squared = (float) len * (float) len;
} else // start == DirectedLine::Midpoint
{
len_squared = len / 2.0 * len / 2.0;
}
double slope_squared = slope * slope;
dx_float = (float) Math.sqrt(len_squared / (slope_squared + 1.0));
// Set which direction the segment should go in the x direction.
// The y direction will get set automatically based on slope
// and the dx.
dx_float *= xdir;
dx = (int) (dx_float + 0.5);
dy = (int) ((slope * dx_float) + 0.5);
if (start == KeyPoint.ORIGIN) {
origin.x = fromX;
origin.y = fromY;
} else // start == DirectedLine::Midpoint
{
origin.x = fromX - dx;
origin.y = fromY - dy;
}
terminus.x = fromX + dx;
terminus.y = fromY + dy;
}
/**
* Constructor
*
* @param ptStart
* Point indicating the start of the line segment
* @param ptEnd
* Point indicating the end of the line segment
*/
public LineSeg(Point ptStart, Point ptEnd) {
origin = new Point(ptStart);
terminus = new Point(ptEnd);
}
/**
* Checks if this line segment contains the given point within a tolerance
* value.
*
* @param aPoint
* <code>Point</code> to test if contained in this line.
* @param tolerance
* int tolerance value for detecting the intersection.
* @return <code>boolean</code> <code>true</code> if the given point lies on
* this segment, <code>false</code> otherwise.
*/
public final boolean containsPoint(final Point aPoint, final int tolerance) {
Point theOrigin = getOrigin();
Point theTerminus = getTerminus();
return (theOrigin.getDistance(aPoint) + aPoint.getDistance(theTerminus) <= length() + tolerance);
}
/**
* Finds the percentage distance along this line segement where the given
* point resides.
*
* @param coord
* <code>Point</code> to determine how far along the line segment
* it resides.
* @return <code>float</code> the distance along the line segment where the
* ptCoord is in a percentage from.
*/
public final float distanceAlong(Point coord) {
int xCoord = coord.x;
int yCoord = coord.y;
/*
* Use parametric form for equation of a line segment: p + td, where 0 <
* t < 1 and d = p2 - p (direction vector) To find out if point lies
* "inside" line segment (i.e. can draw perpendicular line from segment
* to point), use projection of point (q) to line (p + td): t =
* (q-p).d/length(d)^2 (. is dot product)
*/
/* get the direction vector */
long dirx = (long) terminus.x - (long) origin.x;
long diry = (long) terminus.y - (long) origin.y;
/* get q - p */
long qpx = (long) xCoord - (long) origin.x;
long qpy = (long) yCoord - (long) origin.y;
/* dot product of (q-p) and d */
long dotprod = qpx * dirx + qpy * diry;
/*
* avoid divide by 0 - check if point1 equals point2. If so, there is no
* segment - return a value which indicates projection falls outside the
* segment.
*/
if (dirx == 0 && diry == 0)
return -1;
/*
* length (magnitude) of d is sqrt(dirx^2 + diry^2). Don't bother taking
* square root since we want the length squared.
*/
return ((float) dotprod / (float) (dirx * dirx + diry * diry));
}
/**
* Finds the perpendicular distance from a point coordinates to this line
* segment. If point is "inside" line segment, then use distance from point
* to the line, otherwise use distance to nearest endpoint of segment
*
* @param xCoord
* the x coordinate of the point.
* @param yCoord
* the y coordinate of the point.
* @return <code>long</code> the distance from the line segment to the given
* point.
*/
public final long distanceToPoint(final int xCoord, final int yCoord) {
double proj = projection(xCoord, yCoord);
if (proj > 0 && proj < 1) {
Point pt = perpIntersect(xCoord, yCoord);
return Math.round(pt.getDistance(new Point(xCoord, yCoord)));
}
long d1 = Math.round(getOrigin().getDistance(new Point(xCoord, yCoord)));
long d2 = Math.round(getTerminus().getDistance(new Point(xCoord, yCoord)));
return (d1 < d2 ? d1 : d2);
/*
* There are 2 general forms to equations of a line: 1. y = mx + c,
* where c = y1 - m(x1), and 2. ax + by + c = 0 We know m and c, so
* putting first version in the form of the second version, we get: mx -
* y + c = 0 So, for form 2, a = m, b = -1, and c = y1 - m(x1). Distance
* from point (x, y) to line (using form 2) is: |ax + by + c| / sqrt(a^2
* + b^2) or |mx - y + y1 - m(x1)| / sqrt(m^2 + 1)
*/
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object seg) {
if (!(seg instanceof LineSeg))
return false;
LineSeg ls = (LineSeg) seg;
return getOrigin().equals(ls.getOrigin()) && getTerminus().equals(ls.getTerminus());
}
/**
* Returns array with 3 numbers in it, which are the coefficients of the
* generalized line equation of the line corresponding to this line segment
* a*x+b*y=c is the equation => result[0]=a, result[1]=b, result[2]=c
*
* @return an array with 3 numbers in it, which are the coefficients of the
* generalized line equation
*/
public double[] getEquation() {
return getLineEquation(origin.x, origin.y, terminus.x, terminus.y);
}
/**
* Get a <code>Point</code> representing the lowest point value for this
* line segment.
*
* @return <code>Point</code> Representing the lowest point value.
*/
public final Point getInfimum() {
return new Point(Math.min(origin.x, terminus.x), Math.min(origin.y, terminus.y));
}
/**
* Calculates intersection points of the line of the line segment and
* ellipse
*
* @param ellipseBounds
* - width and height of the ellipse
* @return - <Code>PointList</Code> containing all intersection points
*/
public PointList getLineIntersectionsWithEllipse(Rectangle ellipseBounds) {
PointList intersections = new PointList();
if (ellipseBounds.width == 0 || ellipseBounds.height == 0)
return intersections;
double xl1 = getOrigin().x - ellipseBounds.getCenter().x;
double xl2 = getTerminus().x - ellipseBounds.getCenter().x;
double yl1 = getOrigin().y - ellipseBounds.getCenter().y;
double yl2 = getTerminus().y - ellipseBounds.getCenter().y;
double[] equation = getLineEquation(xl1, yl1, xl2, yl2);
if (equation.length < 3 || (equation[0] == 0 && equation[1] == 0))
return intersections;
double a = equation[0];
double b = equation[1];
double c = equation[2];
double w = ellipseBounds.width;
double h = ellipseBounds.height;
// Ellipse with a cneter at the origin has an equation:
// (h*x)^2+(w*y)^2=(h*w/2)^2
// Line equation: a*x+b*y=c
if (b == 0) {
// b==0 is a special case since in general case we will express
// y in terms of x, i.e. we need to divide by b, which should not
// be 0
// b==0 => a*x=c +> x=c/a;
double x = c / a;
// y^2 = (h/2)^2-((h*c)/(a*w))^2
double y = Math.pow(h / 2, 2) - Math.pow((h * c) / (a * w), 2);
if (y < 0)
return intersections;
intersections.addPoint(new PrecisionPoint(x + ellipseBounds.getCenter().x, Math.sqrt(y)
+ ellipseBounds.getCenter().y));
intersections.addPoint(new PrecisionPoint(x + ellipseBounds.getCenter().x, -Math.sqrt(y)
+ ellipseBounds.getCenter().y));
} else {
// y = (c-a*x)/b => we get quadratic equation for x
// x^2*(h^2+(w*a/b)^2)-x*(2*w^2*a*c)/(b^2)+((w*c/b)^2-(h*w/2)^2)=0
// or
// x^2*xA+x*xB+xC=0
double xA = Math.pow(h, 2) + Math.pow((w * a) / b, 2);
double xB = (-2) * Math.pow(w, 2) * a * c / Math.pow(b, 2);
double xC = Math.pow(w * c / b, 2) - Math.pow(h * w / 2, 2);
double xD = Math.pow(xB, 2) - 4 * xA * xC;
if (xD < 0)
return intersections;
double x1 = (-xB + Math.sqrt(xD)) / (2 * xA);
double x2 = (-xB - Math.sqrt(xD)) / (2 * xA);
intersections.addPoint(new PrecisionPoint(x1 + ellipseBounds.getCenter().x, (c - a * x1) / b
+ ellipseBounds.getCenter().y));
intersections.addPoint(new PrecisionPoint(x2 + ellipseBounds.getCenter().x, (c - a * x2) / b
+ ellipseBounds.getCenter().y));
}
return intersections;
}
/**
* Calculates intersection points of the line that contains this line
* segment with a list of other line segments. If the list of points (line
* segments) form a closed <Code>PolyLine</Code>, i.e form a closed polygon
* figure, then the method will claculate intersections of a line and a
* figure
*
* @param points
* - list of points that form linesegments, i.e the
* <Code>PolyLine</Code>
* @return the intersection points of the line that contains this line
* segment with a list of other line segments.
*/
public PointList getLineIntersectionsWithLineSegs(final PointList points) {
double temp[] = getEquation();
double a = temp[0];
double b = temp[1];
double c = temp[2];
PointList intersections = new PointList();
if (points.size() < 2) {
if (a * points.getPoint(0).x + b * points.getPoint(0).y == c) {
intersections.addPoint(points.getPoint(0).getCopy());
}
return intersections;
}
for (int i = 0; i < points.size() - 1; i++) {
LineSeg line = new LineSeg(points.getPoint(i).getCopy(), points.getPoint(i + 1).getCopy());
PointList currentIntersections = getLinesIntersections(line);
for (int j = 0; j < currentIntersections.size(); j++) {
Point intersection = currentIntersections.getPoint(j);
if (line.containsPoint(intersection, DEFAULT_INTERSECTION_TOLERANCE))
intersections.addPoint(currentIntersections.getPoint(j));
}
}
return intersections;
}
/**
* Returns intersection points of two lines that contain this line segment
* and the argumet line segment. The list of intersection points may contain
* at most two points and will contain 2 points if and only if the lines are
* equal. The 2 points will be the end points of the parameter line segment
*
* @param line
* - the line segment
* @return intersection points of two lines that contain this line segment
* and the argumet line segment.
*/
public PointList getLinesIntersections(LineSeg line) {
PointList intersections = new PointList();
double temp[] = getEquation();
double a1 = temp[0];
double b1 = temp[1];
double c1 = temp[2];
temp = line.getEquation();
double a2 = temp[0];
double b2 = temp[1];
double c2 = temp[2];
// Cramer's rule for the system of linear equations
double det = a1 * b2 - b1 * a2;
if (det == 0) {
if (a1 == a2 && b1 == b2 && c1 == c2) {
// if lines are the same, then instead of infinite number of
// intersections
// we will put the end points of the line segment passed as an
// argument
intersections.addPoint(new Point(line.getOrigin().getCopy()));
intersections.addPoint(new Point(line.getTerminus().getCopy()));
}
} else {
intersections.addPoint(new PrecisionPoint((c1 * b2 - b1 * c2) / det, (a1 * c2 - c1 * a2) / det));
}
return intersections;
}
/**
* Accesssor to retrieve the origin point of the line segement.
*
* @return <code>Point</code> the origin of the line segment.
*/
public Point getOrigin() {
return new Point(origin);
}
/**
* Returns a new <code>LineSeg</code> that is parallel to this by the given
* distance. Orientation is relative to the start and end. Negative implies
* to the left and Position implies to the right.
*
* @param ptLoc
* <code>Point</code> value to constrain the line to.
* @return <code>LineSeg</code> line that was calculated going through the
* given point
*/
public final LineSeg getParallelLineSegThroughPoint(Point ptLoc) {
if (isHorizontal()) {
return new LineSeg(new Point(getOrigin().x, ptLoc.y), new Point(getTerminus().x, ptLoc.y));
} else if (isVertical()) {
return new LineSeg(new Point(ptLoc.x, getOrigin().y), new Point(ptLoc.x, getTerminus().y));
} else {
Point ptProj = perpIntersect(ptLoc.x, ptLoc.y);
long nHeight = Math.round(ptProj.getDistance(ptLoc));
Sign position = positionRelativeTo(ptLoc);
return new LineSeg(locatePoint(0.0, nHeight, position), locatePoint(1.0, nHeight, position));
}
}
/**
* Get points representing the highest point value for this line segment.
*
* @return <code>Point</code> Representing the highest point value.
*/
public final Point getSupremum() {
return new Point(Math.max(origin.x, terminus.x), Math.max(origin.y, terminus.y));
}
/**
* Accesssor to retrieve the terminal point of the line segement.
*
* @return <code>Point</code> the terminating point of the line segment
*/
public Point getTerminus() {
return new Point(terminus);
}
/**
* Gets the trig values associated with the angle from this line segment to
* the given vector.
*
* @param ptToVector
* <code>Ray</code> value to calculate trig values of.
* @return <code>TrigValues</code> object representing the trigonometry
* values for the angle of the passed in <code>Ray</code> relative
* to <code>this</code> or null if calculation is not possible,
*/
public TrigValues getTrigValues(final Vector ptToVector) {
double dFromLength = length();
double dToLength = ptToVector.getLength();
Vector ptFromVector = new Vector(new PrecisionPoint(getOrigin()), new PrecisionPoint(getTerminus()));
if (dFromLength <= 0 || dToLength <= 0) {
return null;
}
// 1. find angle for ptToVector relative to the origin.
double dAlpha;
double dCosAlpha, dSinAlpha;
dCosAlpha = ptFromVector.x / dFromLength;
dSinAlpha = ptFromVector.y / dFromLength;
dAlpha = Math.atan2(dSinAlpha, dCosAlpha);
// 2. inverse the angle to get the rotation
dCosAlpha = Math.cos(-dAlpha);
dSinAlpha = Math.sin(-dAlpha);
// 3. rotate vector 2 by angle above so that it's angle relative to
// vector 1 can
// be calculated
double dRotateX = (ptToVector.x * dCosAlpha) - (ptToVector.y * dSinAlpha);
double dRotateY = (ptToVector.x * dSinAlpha) + (ptToVector.y * dCosAlpha);
// 4. Now calculate the Theta trig values
TrigValues val = new TrigValues();
val.cosTheta = dRotateX / dToLength;
val.sinTheta = dRotateY / dToLength;
return val;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return getOrigin().hashCode() ^ getTerminus().hashCode();
}
/**
* Determines the intersect point between this line and the line passed in
* as a parameter. If they intersect, then true is returned and the point
* reference passed in will be set to the intersect point. If they don't
* intersect, then the method returns <code>false</code>.
*
* @param line
* <code>LineSeg</code> to test the intersection against.
* @param nTolerance
* int tolerance value for detecting the intersection.
* @return <code>Point</code> that represents the intersection with this
* line, or <code>null</code> if the calculation is not possible.
*/
public Point intersect(final LineSeg line, final int nTolerance) {
PointList intersections = getLinesIntersections(line);
if (intersections.size() > 1) {
intersections.addPoint(getOrigin().getCopy());
intersections.addPoint(getTerminus().getCopy());
}
for (int i = 0; i < intersections.size(); i++) {
Point result = intersections.getPoint(i).getCopy();
if (containsPoint(result, nTolerance) && line.containsPoint(result, nTolerance)) {
return result;
}
}
return null;
}
/**
* Determines if this a horizontal segment
*
* @return <code>boolean</code> <code>true</code> if horizontal,
* <code>false</code> otherwise.
*/
public final boolean isHorizontal() {
return (origin.y == terminus.y);
}
/**
* Determines if this a vertical segment
*
* @return <code>boolean</code> <code>true</code> if vertical,
* <code>false</code> otherwise.
*/
public final boolean isVertical() {
return (origin.x == terminus.x);
}
/**
* Calculate the length of the line segment.
*
* @return the <code>double</code> length of the line segment.
*/
public final double length() {
return getOrigin().getDistance(getTerminus());
}
/**
* Locates a point at a given height and distance along the line segment. B
* (the point we are looking for) + | dist |h this segment
* P1-----------+-------------------> A get point A (on picture above)
*
* @param pctDist
* <code>double</code> distance along the line
* @param theHeight
* <code>long</code> height above the line
* @param asOriented
* <code>Sign</code> indicating relative position of the point to
* be located
* @return <code>Point</code> value that was located on the line.
*/
public final Point locatePoint(final double pctDist, final long theHeight, final Sign asOriented) {
int xdir;
int dist = (int) (pctDist * length());
Point pt = new Point();
pointOn(dist, KeyPoint.ORIGIN, pt); // (x,y) now = A
// get linesegment AB
// first determine the direction AB should go in the x axis. Don't ask-
// just have faith.
if (getOrigin().y > getTerminus().y || (getOrigin().y == getTerminus().y && getOrigin().x < getTerminus().x)) {
xdir = (asOriented == Sign.POSITIVE ? -1 : 1);
} else {
xdir = (asOriented == Sign.POSITIVE ? 1 : -1);
}
LineSeg linesegAB = new LineSeg(KeyPoint.ORIGIN, pt.x, pt.y, perpSlope(), theHeight, xdir);
return (new Point(linesegAB.getTerminus().x, linesegAB.getTerminus().y));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.draw2d.geometry.Translatable#performScale(double)
*/
public void performScale(double factor) {
setOrigin(getOrigin().scale(factor));
setTerminus(getTerminus().scale(factor));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.draw2d.geometry.Translatable#performTranslate(int, int)
*/
public void performTranslate(int dx, int dy) {
setOrigin(getOrigin().translate(dx, dy));
setTerminus(getTerminus().translate(dx, dy));
}
/**
* Calculates the perpendicular intersection point on the line segment from
* the given point.
*
* @param startX
* the x coordinate of the point
* @param startY
* the y coordinate of the point
* @return <code>Point</code> value containment the perpendicular
* intersection point.
*/
public final Point perpIntersect(final int startX, final int startY) {
float fx;
// The following equations are based on solving 2 equations with
// 2 unknowns (x and y). The 2 equations are equations for the
// slope of each line segment where the slope and 1 point in the
// segment are known:
// (y1 - y) / (x1 - x) = m
// (sy - y) / (sx - x) = -1/m (-1/m is slope of perp. line)
//
Point ptResult = new Point();
float m = slope();
fx = (m * startY - m * getOrigin().y + m * m * getOrigin().x + startX) / (float) (m * m + 1.0);
if (m == 0) {
ptResult.y = getOrigin().y; // segment is horizontal - avoid divide
// by 0
} else {
ptResult.y = (int) (startY + ((startX - fx) / m) + 0.5);
}
ptResult.x = Math.round(fx); // add .5 for rounding
return ptResult;
}
/**
* Calculates the perpendicular slope of this line segment. This calculates
* the slope and then inverts it. Again, to avoid divide by zero errors, the
* constant <code>BIGSLOPE</code> is returned if the calculated slope before
* inverting it was zero.
*
* @return <code>float</code> the perpendicular slope value of the line
* segment.
*/
public final float perpSlope() {
float m = slope();
if (m == 0.0)
return BIGSLOPE;
else
return -(1.0F / m);
}
/**
* Gets the point on the line segment at the given distance away from the
* key point.
*
* @param theDistance
* <code>long</code> distance along the line
* @param fromKeyPoint
* <code>KeyPoint</code> to calculate the distance from
* @param ptResult
* <code>Point</code> where the resulting calculating value is
* stored.
* @return <code>boolean</code> <code>true</code> if point can be
* calculated, <code>false</code> otherwise.
*/
public final boolean pointOn(final long theDistance, final KeyPoint fromKeyPoint, Point ptResult) {
float m, dx_float;
int dx, dy, startX = 0, startY = 0, otherX = 0, otherY = 0;
// Set the point to offset from and the other point used to determine
// which direction dx and dy should be applied to get a point on the
// line.
if (fromKeyPoint == KeyPoint.ORIGIN) {
startX = getOrigin().x;
startY = getOrigin().y;
otherX = getTerminus().x;
otherY = getTerminus().y;
} else if (fromKeyPoint == KeyPoint.TERMINUS) {
startX = getTerminus().x;
startY = getTerminus().y;
otherX = getOrigin().x;
otherY = getOrigin().y;
} else if (fromKeyPoint == KeyPoint.MIDPOINT) {
startX = (getOrigin().x + getTerminus().x) / 2;
startY = (getOrigin().y + getTerminus().y) / 2;
otherX = getTerminus().x;
otherY = getTerminus().y;
} else {
return false;
}
m = slope(); // get the slope of this line
// Find dx and dy - the delta x and y to get from the endpoint to the
// point on the line at the specified distance away.
// The following is based on solving 2 equations with 2 unknowns:
// dy/dx = m (m is slope of line)
// dy^2 + dx^2 = dist^2
//
double d_squared = (float) theDistance * (float) theDistance;
double m_squared = m * m;
// Add .5 so result is rounded to nearest integer when cast
dx_float = (float) Math.sqrt(d_squared / (m_squared + 1.0));
dx = (int) (dx_float + 0.5);
dy = (int) (Math.sqrt(d_squared * m_squared / (m_squared + 1.0)) + 0.5);
/* negative distance means we want point off the line */
if (theDistance < 0) {
dx = -dx;
dy = -dy;
}
ptResult.x = ((startX > otherX) ? startX - dx : startX + dx);
ptResult.y = ((startY > otherY) ? startY - dy : startY + dy);
boolean in_line;
if (startX > otherX)
in_line = ptResult.x >= otherX;
else
in_line = ptResult.x <= otherX;
if (in_line) {
if (startY > otherY)
in_line = ptResult.y >= otherY;
else
in_line = ptResult.y <= otherY;
}
return in_line;
}
/**
* Returns out a positive or negative value (Positive / Negative) depending
* on the orientation of the given point to the line. Point on this side:
* Positive. P1------------------------------> this segment Point on this
* side: Negative.
*
* @param rel
* <code>Point</code> to test the relative position against this
* line.
* @return <code>Sign</code> value indicating the relative position of the
* given point.
*/
public final Sign positionRelativeTo(Point rel) {
Vector ptRelRay = new Vector(new PrecisionPoint(getOrigin()), new PrecisionPoint(rel));
TrigValues val = getTrigValues(ptRelRay);
double dNewAngle = Math.atan2(-val.sinTheta, -val.cosTheta);
if (dNewAngle > 0)
return Sign.POSITIVE;
return Sign.NEGATIVE;
}
/**
* Calculates the projection of the given point onto the line segment.
*
* @param xCoord
* the x coordinate of the point.
* @param yCoord
* the y coordinate of the point.
* @return <code>double</code> value of the calculated projection.
*/
public final double projection(final int xCoord, final int yCoord) {
/*
* Use parametric form for equation of a line segment: p + td, where 0 <
* t < 1 and d = p2 - p (direction vector) To find out if point lies
* "inside" line segment (i.e. can draw perpendicular line from segment
* to point), use projection of point (q) to line (p + td): t =
* (q-p).d/length(d)^2 (. is dot product)
*/
/* get the direction vector */
long dirx = (long) getTerminus().x - (long) getOrigin().x;
long diry = (long) getTerminus().y - (long) getOrigin().y;
/* get q - p */
long qpx = (long) xCoord - (long) getOrigin().x;
long qpy = (long) yCoord - (long) getOrigin().y;
/* dot product of (q-p) and d */
long dotprod = qpx * dirx + qpy * diry;
/*
* avoid divide by 0 - check if point1 equals point2. If so, there is no
* segment - return a value which indicates projection falls outside the
* segment.
*/
if (dirx == 0 && diry == 0)
return -1.0F;
/*
* length (magnitude) of d is sqrt(dirx^2 + diry^2). Don't bother taking
* square root since we want the length squared.
*/
return ((double) dotprod / (double) (dirx * dirx + diry * diry));
}
/**
* Sets the origin point of the line segment
*
* @param origin
* Point to set as origin
*/
public void setOrigin(Point origin) {
this.origin = new Point(origin);
}
/**
* Sets the terminating point of the line segment.
*
* @param terminus
* Point to set as terminus
*/
public void setTerminus(Point terminus) {
this.terminus = new Point(terminus);
}
/**
* Calculates the slope of this line segment (y=mx+b)
*
* @return <code>float</code> the slope of this segment. If the slope is not
* defined such as when the line segment is vertical, then the
* constant <code>BIGSLOPE</code> is returned to avoid divide by
* zero errors.
*/
public final float slope() {
if (isVertical())
return BIGSLOPE;
return (float) (terminus.y - origin.y) / (float) (terminus.x - origin.x);
}
}