/* File AbstractLine2D.java
*
* Project : Java Geometry Library
*
* ===========================================
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. if not, write to :
* The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
// package
package math.geom2d.line;
//Imports
import java.util.ArrayList;
import java.util.Collection;
import math.geom2d.*;
import math.geom2d.circulinear.CirculinearDomain2D;
import math.geom2d.circulinear.CirculinearElement2D;
import math.geom2d.circulinear.buffer.BufferCalculator;
import math.geom2d.conic.Circle2D;
import math.geom2d.conic.CircleArc2D;
import math.geom2d.curve.*;
import math.geom2d.domain.SmoothOrientedCurve2D;
import math.geom2d.transform.CircleInversion2D;
/**
*
* Base class for straight curves, such as straight lines, rays, or edges.
*
*
* Internal representation of straight objects is parametric: (x0, y0) is a
* point in the object, and (dx, dy) is a direction vector of the line.
*
*
* If the line is defined by two point, we can set (x0,y0) to the first point,
* and (dx,dy) to the vector (p1, p2).
*
*
* Then, coordinates for a point (x,y) such as x=x0+t*dx and y=y0+t=dy, t
* between 0 and 1 give a point inside p1 and p2, t<0 give a point 'before' p1,
* and t>1 give a point 'after' p2, so it is convenient to easily manage edges,
* rays and straight lines.
*
*/
public abstract class AbstractLine2D extends AbstractSmoothCurve2D
implements SmoothOrientedCurve2D, LinearElement2D {
// ===================================================================
// static methods
/**
* Returns the unique intersection of two straight objects. If the
* intersection doesn't exist (parallel lines, short edge), return null.
*/
public static Point2D getIntersection(AbstractLine2D line1,
AbstractLine2D line2) {
// Compute denominator, and tests its validity
double denom = line1.dx * line2.dy - line1.dy * line2.dx;
if (Math.abs(denom) < Shape2D.ACCURACY)
return null;
// Compute position of intersection point
double t = ((line1.y0 - line2.y0) * line2.dx -
(line1.x0 - line2.x0) * line2.dy) / denom;
return new Point2D(line1.x0 + t * line1.dx, line1.y0 + t * line1.dy);
}
/**
* Tests if the two linear objects are located on the same straight line.
*/
public static boolean isColinear(AbstractLine2D line1, AbstractLine2D line2) {
// test if the two lines are parallel
if (Math.abs(line1.dx * line2.dy - line1.dy * line2.dx) > ACCURACY)
return false;
// test if the two lines share at least one point (see the contains()
// method for details on tests)
return (Math.abs((line2.y0 - line1.y0) * line2.dx
- (line2.x0 - line1.x0) * line2.dy)
/ Math.hypot(line2.dx, line2.dy) < Shape2D.ACCURACY);
}
/**
* Tests if the two linear objects are parallel.
*/
public static boolean isParallel(AbstractLine2D line1, AbstractLine2D line2) {
return (Math.abs(line1.dx * line2.dy - line1.dy * line2.dx) < Shape2D.ACCURACY);
}
// ===================================================================
// class variables
/**
* Coordinates of starting point of the line
*/
protected double x0, y0;
/**
* Direction vector of the line. dx and dy should not be both zero.
*/
protected double dx, dy;
// ===================================================================
// Protected constructors
protected AbstractLine2D(double x0, double y0, double dx, double dy) {
this.x0 = x0;
this.y0 = y0;
this.dx = dx;
this.dy = dy;
}
protected AbstractLine2D(Point2D point, Vector2D vector) {
this.x0 = point.x();
this.y0 = point.y();
this.dx = vector.x();
this.dy = vector.y();
}
// ===================================================================
// Methods specific to Line shapes
/**
* Tests if the given linear shape is parallel to this shape.
*/
public boolean isColinear(LinearShape2D linear) {
// test if the two lines are parallel
if (!isParallel(linear))
return false;
// test if the two lines share at least one point (see the contains()
// method for details on tests)
StraightLine2D line = linear.supportingLine();
if (Math.abs(dx) > Math.abs(dy)) {
if (Math.abs((line.x0 - x0) * dy / dx + y0 - line.y0) > Shape2D.ACCURACY)
return false;
else
return true;
} else {
if (Math.abs((line.y0 - y0) * dx / dy + x0 - line.x0) > Shape2D.ACCURACY)
return false;
else
return true;
}
}
/**
* Tests if the this object is parallel to the given one.
*/
public boolean isParallel(LinearShape2D line) {
return Vector2D.isColinear(this.direction(), line.direction());
}
/**
* Returns true if the point (x, y) lies on the line covering the object,
* with precision given by Shape2D.ACCURACY.
*/
protected boolean supportContains(double x, double y) {
double denom = Math.hypot(dx, dy);
if (denom < Shape2D.ACCURACY)
throw new DegeneratedLine2DException(this);
return (Math.abs((x - x0) * dy - (y - y0) * dx) / denom < Shape2D.ACCURACY);
}
/**
* Returns the matrix of parametric representation of the line. Result has
* the form:
*
[ x0 dx ]
*
[ y0 dy ]
*
* It can be easily extended to higher dimensions and/or higher polynomial
* forms.
*/
public double[][] parametric() {
double tab[][] = new double[2][2];
tab[0][0] = x0;
tab[0][1] = dx;
tab[1][0] = y0;
tab[1][1] = dy;
return tab;
}
/**
* Returns the coefficient of the Cartesian representation of the line.
* Cartesian equation has the form: ax+by+c=0
*
* @return the array {a, b, c}.
*/
public double[] cartesianEquation() {
double tab[] = new double[3];
tab[0] = dy;
tab[1] = -dx;
tab[2] = dx*y0-dy*x0;
return tab;
}
/**
* Returns polar coefficients of this linear shape.
*
* @return an array of 2 elements, the first one is the distance to the
* origin, the second one is the angle with horizontal, between 0
* and 2*PI.
*/
public double[] polarCoefficients() {
double tab[] = new double[2];
double d = signedDistance(0, 0);
tab[0] = Math.abs(d);
if (d > 0)
tab[1] = (horizontalAngle() + Math.PI) % (2 * Math.PI);
else
tab[1] = horizontalAngle();
return tab;
}
/**
* Returns the signed polar coefficients. Distance to origin can be
* negative: this allows representation of directed lines.
*
* @return an array of 2 elements, the first one is the signed distance to
* the origin, the second one is the angle with horizontal, between
* 0 and 2*PI.
*/
public double[] polarCoefficientsSigned() {
double tab[] = new double[2];
tab[0] = signedDistance(0, 0);
tab[1] = horizontalAngle();
return tab;
}
public double positionOnLine(Point2D point) {
return positionOnLine(point.x(), point.y());
}
/**
* Computes position on the line of the point given by (x,y).
* The position is the number t such that if the point
* belong to the line, it location is given by x=x0+t*dx and y=y0+t*dy.
*
* If the point does not belong to the line, the method returns the position
* of its projection on the line.
*/
public double positionOnLine(double x, double y) {
double denom = dx * dx + dy * dy;
if (Math.abs(denom) < Shape2D.ACCURACY)
throw new DegeneratedLine2DException(this);
return ((y - y0) * dy + (x - x0) * dx) / denom;
}
/**
* Returns the projection of point p on the line. The returned point can be
* used to compute distance from point to line.
*
* @param p a point outside the line (if point p lies on the line, it is
* returned)
* @return the projection of the point p on the line
*/
public Point2D projectedPoint(Point2D p) {
return projectedPoint(p.x(), p.y());
}
/**
* Returns the projection of point p on the line. The returned point can be
* used to compute distance from point to line.
*
* @param x
* coordinate x of point to be projected
* @param y
* coordinate y of point to be projected
* @return the projection of the point p on the line
*/
public Point2D projectedPoint(double x, double y) {
if (contains(x, y))
return new Point2D(x, y);
// compute position on the line
double t = positionOnLine(x, y);
// compute position of intersection point
return new Point2D(x0 + t * dx, y0 + t * dy);
}
/**
* Return the symmetric of point p relative to this straight line.
*
* @param p a point outside the line (if point p lies on the line, it is
* returned)
* @return the projection of the point p on the line
*/
public Point2D getSymmetric(Point2D p) {
return getSymmetric(p.x(), p.y());
}
/**
* Return the symmetric of point with coordinate (x, y) relative to this
* straight line.
*
* @param x x-coordinate of point to be projected
* @param y y-coordinate of point to be projected
* @return the projection of the point (x,y) on the line
*/
public Point2D getSymmetric(double x, double y) {
// compute position on the line
double t = 2 * positionOnLine(x, y);
// compute position of intersection point
return new Point2D(2 * x0 + t * dx - x, 2 * y0 + t * dy - y);
}
/**
* Creates a straight line parallel to this object, and going through the
* given point.
*
* @param point the point to go through
* @return the parallel through the point
*/
public StraightLine2D parallel(Point2D point) {
return new StraightLine2D(point, this.dx, this.dy);
}
/**
* Creates a straight line perpendicular to this object, and going through
* the given point.
*
* @param point the point to go through
* @return the perpendicular through the point
*/
public StraightLine2D perpendicular(Point2D point) {
return new StraightLine2D(point, -this.dy, this.dx);
}
// ===================================================================
// Methods implementing the LinearShape2D interface
/**
* Returns the origin point of this linear shape.
*/
public Point2D origin() {
return new Point2D(x0, y0);
}
/**
* Returns the direction vector of this linear shape.
*/
public Vector2D direction() {
return new Vector2D(dx, dy);
}
/**
* Gets Angle with axis (O,i), counted counter-clockwise. Result is given
* between 0 and 2*pi.
*/
public double horizontalAngle() {
return (Math.atan2(dy, dx) + 2 * Math.PI) % (2 * Math.PI);
}
/**
* Returns the unique intersection with a linear shape. If the intersection
* doesn't exist (parallel lines, short edges), return null.
*/
public Point2D intersection(LinearShape2D line) {
Vector2D vect = line.direction();
double dx2 = vect.x();
double dy2 = vect.y();
// test if two lines are parallel
double denom = this.dx * dy2 - this.dy * dx2;
if (Math.abs(denom) < Shape2D.ACCURACY)
return null;
// compute position on the line
Point2D origin = line.origin();
double x2 = origin.x();
double y2 = origin.y();
double t = ((y0 - y2) * dx2 - (x0 - x2) * dy2) / denom;
// compute position of intersection point
Point2D point = new Point2D(x0 + t * dx, y0 + t * dy);
// check if point is inside the bounds of the object. This test
// is left to derived classes.
if (contains(point) && line.contains(point))
return point;
return null;
}
/* (non-Javadoc)
* @see math.geom2d.line.AbstractLine2D#supportingLine()
*/
public StraightLine2D supportingLine() {
return new StraightLine2D(this);
}
// ===================================================================
// methods implementing the CirculinearCurve2D interface
/* (non-Javadoc)
* @see math.geom2d.circulinear.CirculinearCurve2D#length()
*/
public double length() {
if (!this.isBounded())
return Double.POSITIVE_INFINITY;
return (t1() - t0()) * Math.hypot(dx, dy);
}
/* (non-Javadoc)
* @see math.geom2d.circulinear.CirculinearCurve2D#length(double)
*/
public double length(double pos) {
return pos * Math.hypot(dx, dy);
}
/*
* (non-Javadoc)
*
* @see math.geom2d.circulinear.CirculinearCurve2D#position(double)
*/
public double position(double distance) {
double delta = Math.hypot(dx, dy);
if (delta < Shape2D.ACCURACY)
throw new DegeneratedLine2DException(this);
return distance / delta;
}
// ===================================================================
// methods implementing the CirculinearShape2D interface
/* (non-Javadoc)
* @see math.geom2d.circulinear.CirculinearCurve2D#transform(math.geom2d.transform.CircleInversion2D)
*/
public CirculinearElement2D transform(CircleInversion2D inv) {
// Extract inversion parameters
Point2D center = inv.center();
double r = inv.radius();
// compute distance of line to inversion center
StraightLine2D line = this.supportingLine();
Point2D po = line.projectedPoint(center);
double d = line.distance(center);
// flag for indicating if line extremities are finite
boolean inf0 = Double.isInfinite(this.t0());
boolean inf1 = Double.isInfinite(this.t1());
// Degenerate case of a line passing through the center.
// returns the line itself.
if (Math.abs(d) < Shape2D.ACCURACY) {
if (inf0) {
if (inf1) {
// case of a straight line, which transform into itself
return new StraightLine2D(this);
} else {
// case of an inverted ray, which transform into another
// inverted ray
Point2D p2 = this.lastPoint().transform(inv);
return new InvertedRay2D(p2, this.direction());
}
} else {
Point2D p1 = this.firstPoint().transform(inv);
if (inf1) {
// case of a ray, which transform into another ray
return new Ray2D(p1, this.direction());
} else {
// case of an line segment
Point2D p2 = this.lastPoint().transform(inv);
return new LineSegment2D(p1, p2);
}
}
}
// angle from center to line
double angle = Angle2D.horizontalAngle(center, po);
// center of transformed circle
double r2 = r * r / d / 2;
Point2D c2 = Point2D.createPolar(center, r2, angle);
// choose direction of circle arc
boolean direct = this.isInside(center);
// case of a straight line
if (inf0 && inf1) {
return new Circle2D(c2, r2, direct);
}
// Compute the transform of the end points, which can be the center of
// the inversion circle in the case of an infinite line.
Point2D p1 = inf0 ? center : this.firstPoint().transform(inv);
Point2D p2 = inf1 ? center : this.lastPoint().transform(inv);
// compute angle between center of transformed circle and end points
double theta1 = Angle2D.horizontalAngle(c2, p1);
double theta2 = Angle2D.horizontalAngle(c2, p2);
// create the new circle arc
return new CircleArc2D(c2, r2, theta1, theta2, direct);
}
/* (non-Javadoc)
* @see math.geom2d.circulinear.CirculinearShape2D#buffer(double)
*/
public CirculinearDomain2D buffer(double dist) {
BufferCalculator bc = BufferCalculator.getDefaultInstance();
return bc.computeBuffer(this, dist);
}
// ===================================================================
// methods of OrientedCurve2D interface
/* (non-Javadoc)
* @see math.geom2d.domain.OrientedCurve2D#windingAngle(Point2D)
*/
public double windingAngle(Point2D point) {
double t0 = this.t0();
double t1 = this.t1();
double angle1, angle2;
if (t0==Double.NEGATIVE_INFINITY)
angle1 = Angle2D.horizontalAngle(-dx, -dy);
else
angle1 = Angle2D.horizontalAngle(point.x(), point.y(),
x0 + t0 * dx, y0 + t0 * dy);
if (t1 == Double.POSITIVE_INFINITY)
angle2 = Angle2D.horizontalAngle(dx, dy);
else
angle2 = Angle2D.horizontalAngle(point.x(), point.y(),
x0 + t1 * dx, y0 + t1 * dy);
if (this.isInside(point)) {
if (angle2 > angle1)
return angle2 - angle1;
else
return 2 * Math.PI - angle1 + angle2;
} else {
if (angle2 > angle1)
return angle2 - angle1 - 2 * Math.PI;
else
return angle2 - angle1;
}
}
/**
* Returns the signed distance of the StraightObject2d to the given point.
* The signed distance is positive if point lies 'to the right' of the
* line, when moving in the direction given by direction vector.
* This method is not designed to be used directly, because AbstractLine2D
* is an abstract class, but it can be used by subclasses to help computations.
*/
public double signedDistance(Point2D p) {
return signedDistance(p.x(), p.y());
}
/**
* Returns the signed distance of the StraightObject2d to the given point.
* The signed distance is positive if point lies 'to the right' of the
* line, when moving in the direction given by direction vector.
* This method is not designed to be used directly, because AbstractLine2D
* is an abstract class, but it can be used by subclasses to help
* computations.
*/
public double signedDistance(double x, double y) {
double delta = Math.hypot(dx, dy);
if (delta < Shape2D.ACCURACY)
throw new DegeneratedLine2DException(this);
return ((x - x0) * dy - (y - y0) * dx) / delta;
}
/**
* Returns true if the given point lies to the left of the line when
* traveling along the line in the direction given by its direction vector.
*
* @param p the point to test
* @return true if point p lies on the 'left' of the line.
*/
public boolean isInside(Point2D p) {
return ((p.x() - x0) * dy - (p.y() - y0) * dx < 0);
}
// ===================================================================
// methods implementing the SmoothCurve2D interface
/* (non-Javadoc)
* @see math.geom2d.curve.SmoothCurve2D#tangent(double)
*/
public Vector2D tangent(double t) {
return new Vector2D(dx, dy);
}
/**
* Returns 0 as for every straight object.
*/
public double curvature(double t) {
return 0.0;
}
// ===================================================================
// methods implementing the ContinuousCurve2D interface
/**
* Always returns false, because we can not come back to starting point if
* we always go straight...
*/
public boolean isClosed() {
return false;
}
// ===================================================================
// methods implementing the Curve2D interface
/**
* Returns a collection of lines containing only this line.
*/
@Override
public Collection extends AbstractLine2D> smoothPieces() {
return wrapCurve(this);
}
/**
* Returns the intersection points of the curve with the specified line.
* The length of the result array is the number of intersection points.
*/
public Collection intersections(LinearShape2D line) {
if (this.isParallel(line))
return new ArrayList(0);
ArrayList points = new ArrayList(1);
Point2D point = intersection(line);
if (point != null)
points.add(point);
// return array with the intersection point.
return points;
}
/**
* Returns the position of the point on the line arc.
* If the point belongs to the line, this position is defined by the ratio:
*
* t = (xp - x0)/dx <\code>, or equivalently:
* t = (yp - y0)/dy <\code>.
* If point does not belong to line, returns Double.NaN.
*/
public double position(Point2D point) {
double pos = this.positionOnLine(point);
// compute a threshold depending on line slope
double eps = Math.hypot(dx, dy) * Shape2D.ACCURACY;
// return either pos or NaN
if (pos < this.t0() - eps)
return Double.NaN;
if (pos > this.t1() + eps)
return Double.NaN;
return pos;
}
/**
* Returns the position of the closest point on the line arc.
* If the point belongs to the line, this position is defined by the ratio:
*
* t = (xp - x0)/dx <\code>, or equivalently:
* t = (yp - y0)/dy <\code>.
* If point does not belong to line, returns t0, or t1, depending on which
* one is the closest.
*/
public double project(Point2D point) {
double pos = this.positionOnLine(point);
// Bounds between t0 and t1
return Math.min(Math.max(pos, this.t0()), this.t1());
}
/**
* Returns a new AbstractLine2D, which is the portion of this AbstractLine2D
* delimited by parameters t0 and t1. Casts the result to StraightLine2D,
* Ray2D or LineSegment2D when appropriate.
*/
public AbstractLine2D subCurve(double t0, double t1) {
// keep min and max of bounds
t0 = Math.max(t0, this.t0());
t1 = Math.min(t1, this.t1());
// check for special cases
if (Double.isInfinite(t1)) {
if (Double.isInfinite(t0))
return new StraightLine2D(this);
else
return new Ray2D(this.point(t0), this.direction());
}
if (Double.isInfinite(t0))
return new InvertedRay2D(this.point(t1), this.direction());
else
return new LineSegment2D(this.point(t0), this.point(t1));
}
/**
* Returns a collection of lines containing only this line.
*/
@Override
public Collection extends AbstractLine2D> continuousCurves() {
return wrapCurve(this);
}
// ===================================================================
// methods implementing the Shape2D
/**
* Returns the distance of the StraightObject2d to the given point.
* This method is not designed to be used directly, because AbstractLine2D
* is an abstract class, but it can be called by subclasses to help
* computations.
*/
public double distance(Point2D p) {
return distance(p.x(), p.y());
}
/**
* Returns the distance of the StraightObject2d to the given point.
* This method is not designed to be used directly, because AbstractLine2D
* is an abstract class, but it can be called by subclasses to help
* computations.
*
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return distance between this object and the point (x,y)
*/
public double distance(double x, double y) {
// first project on the line
Point2D proj = projectedPoint(x, y);
// if the line contains the projection, returns the distance
if (contains(proj))
return proj.distance(x, y);
// otherwise, returns the distance to the closest singular point
double dist = Double.POSITIVE_INFINITY;
if(!Double.isInfinite(t0()))
dist = firstPoint().distance(x, y);
if(!Double.isInfinite(t1()))
dist = Math.min(dist, lastPoint().distance(x, y));
return dist;
}
/* (non-Javadoc)
* @see math.geom2d.Shape2D#contains(Point2D)
*/
public boolean contains(Point2D p) {
return this.contains(p.x(), p.y());
}
/**
* Returns false, unless both dx and dy equal 0.
*/
public boolean isEmpty() {
return Math.hypot(dx, dy) < Shape2D.ACCURACY;
}
/* (non-Javadoc)
* @see math.geom2d.Shape2D#transform(AffineTransform2D)
*/
public abstract AbstractLine2D transform(AffineTransform2D transform);
public CurveSet2D extends AbstractLine2D> clip(Box2D box) {
// Clip the curve
CurveSet2D set = Curves2D.clipContinuousCurve(
this, box);
// Stores the result in appropriate structure
CurveArray2D result =
new CurveArray2D(set.size());
// convert the result
for (Curve2D curve : set.curves()) {
if (curve instanceof AbstractLine2D)
result.add((AbstractLine2D) curve);
}
return result;
}
/**
* @deprecated use copy constructor instead (0.11.2)
*/
@Deprecated
@Override
public abstract AbstractLine2D clone();
}
Ads help maintain this website.