/** * */ package cz.cuni.mff.peckam.java.origamist.math; import static cz.cuni.mff.peckam.java.origamist.math.MathHelper.EPSILON; import static java.lang.Math.abs; import java.util.Iterator; import javax.vecmath.Point2d; import javax.vecmath.Vector2d; /** * A line in 2D space. * * @author Martin Pecka */ public class Line2d implements Cloneable, Vector<Double> { /** */ private static final long serialVersionUID = -3849433499881284033L; /** * This is a coefficient in the general equation of the defining line (ax + by + c = 0). */ protected double a = 0; /** * This is a coefficient in the general equation of the defining line (ax + by + c = 0). */ protected double b = 0; /** * This is a coefficient in the general equation of the defining line (ax + by + c = 0). */ protected double c = 0; /** A point on the line. */ protected Point2d p; /** The direction vector of the line. */ protected Vector2d v; /** * Constructs the line going through the given points. * * A line with zero direction vector is allowed here, then it stands for a single point. * * @param p1 First point of the line. * @param p2 Second point of the line. */ public Line2d(Point2d p1, Point2d p2) { a = p1.y - p2.y; b = p2.x - p1.x; c = p1.x * p2.y - p1.y * p2.x; p = p1; v = new Vector2d(p2); v.sub(p1); } /** * Constructs the line going through the given point with the given direction. * * @param p A point on the line. * @param v The direction vector of the line. */ public Line2d(Point2d p, Vector2d v) { this(p, new Point2d(p.x + v.x, p.y + v.y)); } /** * Constructs a clone of the given line. * * @param line */ public Line2d(Line2d line) { this(new Point2d(line.p), new Vector2d(line.v)); } /** * @return A point lying on the line. */ public Point2d getPoint() { return p; } /** * @return The direction vector of the line. */ public Vector2d getVector() { return v; } /** * Return the intersection with the line. * * @param line The line to find intersection with. * @return The intersection point (as a line with zero direction vector); <code>null</code> if no intersection point * was found; <code>this</code> if the lines are epsilon-equal. * * @see http://sputsoft.com/blog/2010/03/line-line-intersection.html */ public Line2d getIntersection(Line2d line) { double a = line.v.x * v.y - line.v.y * v.x; double b = line.v.x * (line.p.y - p.y) - line.v.y * (line.p.x - p.x); if (abs(a) < EPSILON) { if (abs(b) < EPSILON) { return this; } else { return null; } } return new Line2d(new Point2d(p.x + b / a * v.x, p.y + b / a * v.y), new Vector2d()); } /** * Returns the parameter <code>t</code> such that <code>this.p + t*this.v == point</code>. If the point doesn't lie * on this line, return <code>null</code>. * * @param point The point to find parameter for. * @return The parameter <code>t</code> such that <code>this.p + t*this.v == point</code>. If the point doesn't lie * on this line, return <code>null</code>. */ public Double getParameterForPoint(Point2d point) { Vector2d v = new Vector2d(); v.sub(point, this.p); return MathHelper.vectorQuotient2d(v, this.v); } /** * Return the point on this line that corresponds to <code>this.p + param*this.v</code>. * * @param param The parameter of the point (scale of the direction vector needed to be added to this.p to get the * desired point). * @return The point (note that for a line with zero direction vector, this.p will be returned always). */ public Point2d getPointForParameter(double param) { return new Point2d(this.p.x + param * this.v.x, this.p.y + param * this.v.y); } /** * @return True if this segment is just a single point. */ public boolean isSinglePoint() { return v.epsilonEquals(new Vector2d(), EPSILON); } /** * @return True if this segment isn't trivial (has non-zero length). */ protected boolean isNonTrivial() { return !isSinglePoint(); } /** * Returns true if this line contains the given point. * * @param p The point to check. * @return Whether the given point lies on this line. */ public boolean contains(Point2d p) { return abs(p.x * a + p.y * b + c) <= EPSILON; } /** * Returns true if this line contains all the given points. * * @param points The points to check. * @return true if this line contains all the given points. */ public boolean containsAll(Point2d... points) { if (points == null) return true; for (Point2d p : points) { if (!contains(p)) return false; } return true; } /** * Return the point on this line that is the nearest to the given point. * * @param p The point to find the nearest one for. * @return The point on this line that is the nearest to the given point (the passed-in instance is returned if it * lies on this line). */ public Point2d getNearestPoint(Point2d p) { if (contains(p)) return p; if (getVector().epsilonEquals(new Vector2d(), EPSILON)) return getPoint(); Vector2d perp = new Vector2d(getVector().y, -getVector().x); Line2d perpLine = new Line2d(p, perp); Line2d intPoint = this.getIntersection(perpLine); return intPoint != null ? intPoint.getPoint() : null; } /** * Mirror the given point around this line (axis symmetry). * * @param p The point to mirror. * @return The mirrored point (the passed-in instance is returned if it lies on this line). */ public Point2d mirror(Point2d p) { Point2d nearest = getNearestPoint(p); if (nearest == p) // p lies on this line return p; // direction vector p->nearest Vector2d vect = new Vector2d(nearest); vect.sub(p); Point2d mirrored = new Point2d(nearest); mirrored.add(vect); return mirrored; } /** * Mirror the given line around this line (axis symmetry). * * @param l The line to mirror. * @return The mirrored line (the passed-in instance is returned if it lies on this line). */ public Line2d mirror(Line2d l) { if (l.epsilonEquals(this)) return l; Point2d p1 = l.getPoint(); Point2d p2 = new Point2d(p1); p2.add(l.getVector()); return new Line2d(mirror(p1), mirror(p2)); } /** * Mirror the given segment around this line (axis symmetry). * * @param l The segment to mirror. * @return The mirrored segment (the passed-in instance is returned if it lies on this line). */ public Segment2d mirror(Segment2d l) { Segment2d intPoint = l.getIntersection(this); if (intPoint != null && !intPoint.getVector().epsilonEquals(new Vector2d(), EPSILON)) return l; return new Segment2d(mirror(l.getP1()), mirror(l.getP2())); } /** * Return true if these lines are parallel. * * @param other The other line to check. * @return true if these lines are parallel. */ public boolean isParallelTo(Line2d other) { return MathHelper.vectorQuotient2d(this.v, other.v) != null; } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(a); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(b); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(c); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Line2d other = (Line2d) obj; if (Double.doubleToLongBits(a) != Double.doubleToLongBits(other.a)) return false; if (Double.doubleToLongBits(b) != Double.doubleToLongBits(other.b)) return false; if (Double.doubleToLongBits(c) != Double.doubleToLongBits(other.c)) return false; return true; } /** * Return <code>true</code> if the given line is almost equal to this one. * * @param other The halfplane to compare. * @return <code>true</code> if the given line is almost equal to this one. */ public boolean epsilonEquals(Line2d other) { if (other == null) return false; if (!getClass().equals(other.getClass())) return false; return getIntersection(other) == this; } @Override public String toString() { return "Line2d [" + a + "x + " + b + "y + " + c + " = 0]"; } /** * @return If the direction vector is zero, print only the point; else this behaves as {@link #toString()}. */ public String toStringAsIntersection() { if (v.epsilonEquals(new Vector2d(), EPSILON)) return p.toString(); else return toString(); } @Override protected Line2d clone() { return new Line2d(this); } @Override public Iterator<Double> iterator() { return new Iterator<Double>() { private int i = 0; @Override public boolean hasNext() { return i + 1 < getDimension(); } @Override public Double next() { return get(i++); } @Override public void remove() { throw new UnsupportedOperationException("Cannot remove from a vector's iterator."); } }; } @Override public int getDimension() { return 4; } @Override public Double get(int index) { switch (index) { case 0: return v.x; case 1: return v.y; case 2: return p.x; case 3: return p.y; default: throw new IndexOutOfBoundsException(); } } @Override public Double set(int index, Double value) { Double oldVal; switch (index) { case 0: oldVal = v.x; v.x = value; break; case 1: oldVal = v.y; v.y = value; break; case 2: oldVal = p.x; p.x = value; break; case 3: oldVal = p.y; p.y = value; break; default: throw new IndexOutOfBoundsException(); } return oldVal; } }