/** * */ package cz.cuni.mff.peckam.java.origamist.math; import static cz.cuni.mff.peckam.java.origamist.math.MathHelper.EPSILON; import java.util.Iterator; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; /** * A line in 3D. * * @author Martin Pecka */ public class Line3d implements Cloneable, Vector<Double> { /** */ private static final long serialVersionUID = -6793062246943777021L; /** * A point this line goes through. */ protected Point3d p; /** * The direction vector of the line. */ protected Vector3d v; /** * Create a standalone copy (clone) of the given line. * * @param line The line to copy. */ public Line3d(Line3d line) { this.p = new Point3d(line.p); this.v = new Vector3d(line.v); } /** * Create the line going through the given point and with given direction. * * A line with zero direction vector is allowed here, then it stands for a single point. * * @param p A point the line goes through. * @param v The direction vector of the line. */ public Line3d(Point3d p, Vector3d v) { this.p = p; this.v = v; } /** * Create a line going through the given points. * * @param p1 A point on the line. * @param p2 A point on the line. */ public Line3d(Point3d p1, Point3d p2) { this(p1, new Vector3d(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)); } /** * Get the intersection point of this line with the given 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://mathforum.org/library/drmath/view/62814.html */ public Line3d getIntersection(Line3d line) { Vector3d v1xv2 = new Vector3d(); v1xv2.cross(v, line.v); if (v1xv2.epsilonEquals(new Vector3d(), EPSILON)) { // the lines are parallel if (line.contains(p)) { // and equal return this; } else { // and different return null; } } Point3d p2_p1 = new Point3d(); p2_p1.sub(line.p, p); Vector3d p2_p1xv2 = new Vector3d(); p2_p1xv2.cross(new Vector3d(p2_p1), line.v); Double q = MathHelper.vectorQuotient3d(p2_p1xv2, v1xv2); if (q == null) // the lines are skew return null; Point3d result = new Point3d(p); Vector3d vt = new Vector3d(v); vt.scale(q); result.add(vt); return new Line3d(result, new Vector3d()); } /** * 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(Point3d point) { Vector3d v = new Vector3d(); v.sub(point, this.p); return MathHelper.vectorQuotient3d(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 Point3d getPointForParameter(double param) { return new Point3d(this.p.x + param * this.v.x, this.p.y + param * this.v.y, this.p.z + param * this.v.z); } /** * @return True if this segment is just a single point. */ public boolean isSinglePoint() { return v.epsilonEquals(new Vector3d(), EPSILON); } /** * @return True if this segment isn't trivial (has non-zero length). */ protected boolean isNonTrivial() { return !isSinglePoint(); } /** * Return true if this line is parallel to the other line. * * @param line The other line. * @return True if this line is parallel to the other line. */ public boolean isParallelTo(Line3d line) { Vector3d v1xv2 = new Vector3d(); v1xv2.cross(v, line.v); // the cross product is zero iff the vectors are parallel or one of them is a zero vector return v1xv2.epsilonEquals(new Vector3d(), EPSILON); } /** * Return a point on this line nearest to the given point. * * @param p The point to find the nearest one for. * @return A point on this line nearest to the given point. The passed-in instance is returned if it lies on this. */ public Point3d getNearestPoint(Point3d p) { if (contains(p)) return p; Plane3d plane = new Plane3d(this.getVector(), p); // intersection must be a single point because plane's normal is this, so the plane can't be parralel to this return plane.getIntersection(this).getPoint(); } /** * 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(Point3d point) { Point3d pt = new Point3d(); pt.sub(point, p); Vector3d vt = new Vector3d(pt); vt.cross(vt, v); // if vt and v are colinear (parallel), the point is contained in this line return vt.epsilonEquals(new Vector3d(), 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(Point3d... points) { if (points == null) return true; for (Point3d p : points) { if (!contains(p)) return false; } return true; } /** * @return A point on the line. */ public Point3d getPoint() { return p; } /** * @return The direction vector of the line. */ public Vector3d getVector() { return v; } /** * Return the distance of the given point from this line. * * @param point The point to get distance of. * @return Distance of this line and the given point. */ public double distance(Point3d point) { return point.distance(getNearestPoint(point)); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((p == null) ? 0 : p.hashCode()); result = prime * result + ((v == null) ? 0 : v.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Line3d other = (Line3d) obj; if (p == null) { if (other.p != null) return false; } else if (!p.equals(other.p)) return false; if (v == null) { if (other.v != null) return false; } else if (!v.equals(other.v)) return false; return true; } /** * Return <code>true</code> if the given line is almost equal to this one. * * @param other The line to compare. * @return <code>true</code> if the given line is almost equal to this one. */ public boolean epsilonEquals(Line3d other) { if (!getClass().equals(other.getClass())) return false; // getIntersection() returns this if the lines are epsilon-equal return getIntersection(other) == this; } @Override protected Line3d clone() { return new Line3d(this); } @Override public String toString() { return "Line3d [" + p + " + t*" + v + "]"; } /** * @return If the direction vector is zero, print only the point; else this behaves as {@link #toString()}. */ public String toStringAsIntersection() { if (v.epsilonEquals(new Vector3d(), EPSILON)) return p.toString(); else return toString(); } @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 6; } @Override public Double get(int index) { switch (index) { case 0: return v.x; case 1: return v.y; case 2: return v.z; case 3: return p.x; case 4: return p.y; case 5: return p.z; 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 = v.z; v.z = value; break; case 3: oldVal = p.x; p.x = value; break; case 4: oldVal = p.y; p.y = value; break; case 5: oldVal = p.z; p.z = value; break; default: throw new IndexOutOfBoundsException(); } return oldVal; } }