/**
*
*/
package cz.cuni.mff.peckam.java.origamist.math;
import static cz.cuni.mff.peckam.java.origamist.math.MathHelper.EPSILON;
import java.util.ArrayList;
import java.util.List;
import javax.vecmath.Point2d;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;
import org.apache.log4j.Logger;
/**
* A representation of a 2D triangle.
*
* @author Martin Pecka
*/
public class Triangle2d implements Cloneable
{
/** A vertex of the triangle. */
protected Point2d p1, p2, p3;
/** A halfplane defined by one edge of the triangle. */
protected HalfPlane2d hp1, hp2, hp3;
/** Edge of the triangle. s_i = (p_(i+1 mod 3) - p_i). */
protected Segment2d s1, s2, s3;
/** Precomputed values for obtaining barycentric coordinates of points. */
protected double b1, b2, b3, b4;
/**
* Create a triangle from the given points.
*
* @param p1 A vertex.
* @param p2 A vertex.
* @param p3 A vertex.
* @throws IllegalArgumentException If the points are colinear.
*/
public Triangle2d(Point2d p1, Point2d p2, Point2d p3) throws IllegalArgumentException
{
setPoints(p1, p2, p3);
}
/**
* Create a triangle from the given points.
*
* @param p1x A vertex's x coordinte.
* @param p1y A vertex's y coordinte.
* @param p2x A vertex's x coordinte.
* @param p2y A vertex's y coordinte.
* @param p3x A vertex's x coordinte.
* @param p3y A vertex's y coordinte.
* @throws IllegalArgumentException If the points are colinear.
*/
public Triangle2d(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y)
throws IllegalArgumentException
{
this(new Point2d(p1x, p1y), new Point2d(p2x, p2y), new Point2d(p3x, p3y));
}
/**
* Change the points of this triangle. Only non-null points will be changed.
*
* @param p1 A vertex or <code>null</code> if that vertex should be left untouched.
* @param p2 A vertex or <code>null</code> if that vertex should be left untouched.
* @param p3 A vertex or <code>null</code> if that vertex should be left untouched.
*
* @throws IllegalArgumentException If the points are colinear.
*/
public void setPoints(Point2d p1, Point2d p2, Point2d p3) throws IllegalArgumentException
{
if (p1 != null)
this.p1 = p1;
if (p2 != null)
this.p2 = p2;
if (p3 != null)
this.p3 = p3;
if (new Line2d(this.p1, this.p2).contains(this.p3)) {
throw new IllegalArgumentException("Trying to define a triangle by three colinear points.");
}
s1 = new Segment2d(p1, p2);
s2 = new Segment2d(p2, p3);
s3 = new Segment2d(p3, p1);
hp1 = new HalfPlane2d(p1, p2, p3);
hp2 = new HalfPlane2d(p2, p3, p1);
hp3 = new HalfPlane2d(p3, p1, p2);
// see http://en.wikipedia.org/wiki/Barycentric_coordinates_%28mathematics%29
double oneOverDetT = 1.0 / ((this.p1.x - this.p3.x) * (this.p2.y - this.p3.y) - (this.p2.x - this.p3.x)
* (this.p1.y - this.p3.y));
b1 = (this.p2.y - this.p3.y) * oneOverDetT;
b2 = (this.p3.x - this.p2.x) * oneOverDetT;
b3 = (this.p3.y - this.p1.y) * oneOverDetT;
b4 = (this.p1.x - this.p3.x) * oneOverDetT;
}
/**
* Return the coordinates of the first point.
*
* @return The coordinates of the first point.
*/
public Point2d getP1()
{
return p1;
}
/**
* Return the coordinates of the second point.
*
* @return The coordinates of the second point.
*/
public Point2d getP2()
{
return p2;
}
/**
* Return the coordinates of the third point.
*
* @return The coordinates of the third point.
*/
public Point2d getP3()
{
return p3;
}
/**
* @return The vertices of the triangle.
*/
public Point2d[] getVertices()
{
return new Point2d[] { p1, p2, p3 };
}
/**
* @return The edge of the triangle.
*/
public Segment2d getS1()
{
return s1;
}
/**
* @return The edge of the triangle.
*/
public Segment2d getS2()
{
return s2;
}
/**
* @return The edge of the triangle.
*/
public Segment2d getS3()
{
return s3;
}
/**
* @return The edges of the triangle.
*/
public Segment2d[] getEdges()
{
return new Segment2d[] { s1, s2, s3 };
}
/**
* True if this triangle contains the given point (even on its border).
*
* @param point The point to check.
* @return Whether the given point lies inside this triangle or not.
*/
public boolean contains(Point2d point)
{
return hp1.contains(point) && hp2.contains(point) && hp3.contains(point);
}
/**
* Returns true if the given triangle has a common edge with this triangle.
*
* If <code>strict</code> is true, then the edges must match exactly. If it is false, it is sufficient that the
* edges overlap.
*
* @param t The triangle to try to find common edge with.
* @param strict If true, then the edges must match exactly. If it is false, it is sufficient that the edges
* overlap.
* @return true if the given triangle has a common edge with this triangle.
*/
public boolean hasCommonEdge(Triangle2d t, boolean strict)
{
for (Segment2d edge1 : getEdges()) {
for (Segment2d edge2 : t.getEdges()) {
if (strict) {
if (edge1.epsilonEquals(edge2, true))
return true;
} else {
if (edge1.overlaps(edge2))
return true;
}
}
}
return false;
}
/**
* Return <code>true</code> if the given point lies in one of the sides of this triangle.
*
* @param point The point to check.
* @return <code>true</code> if the given point lies in one of the sides of this triangle.
*/
public boolean sidesContain(Point2d point)
{
return s1.contains(point) || s2.contains(point) || s3.contains(point);
}
/**
* Return the intersection points of this triangle and the given line.
* <p>
* Note that some heuristic is performed (such as "magnetizing" edges and vertices), so it generally doesn't hold
* that the returned segment is a subsegment of the argument (it can be probably 10*EPSILON-different).
*
* @param line The line to get intersections with.
* @return A segment that defines the intersection of the given line (or segment) and this triangle (the segment can
* be zero-length), or <code>null</code>, if no intersection exists. A segment start or end inside the
* triangle is also taken as an intersection.
*/
public Segment2d getIntersection(Line2d line)
{
Segment2d intersection = null;
List<Point2d> intersections = new ArrayList<Point2d>(3);
for (Segment2d s : getEdges()) { // find intersections with edges
intersection = s.getIntersection(line);
if (intersection != null && intersection.v.epsilonEquals(new Vector2d(), EPSILON)) {
// the point->param->point conversion is done to be as close to the edge as possible
double param = s.getParameterForPoint(intersection.p);
// make vertices magnetic
if (param < 1 * EPSILON) {
intersections.add(s.getPointForParameter(0));
} else if (param > 1 - 1 * EPSILON) {
intersections.add(s.getPointForParameter(1));
} else {
intersections.add(s.getPointForParameter(param));
}
} else if (intersection != null) {
// the line lies on the same line as the edge and they have nonempty intersection - we can return
return intersection.getIntersection(s);
}
}
if (line instanceof Segment2d) {
// a segment can start or end inside the triangle
for (Point2d p : ((Segment2d) line).getPoints()) {
if (this.contains(p)/* && !sidesContain(p) */) {
intersections.add(p);
}
}
}
// rounding erros may affect the method a lot, so ensure it is a little more tolerant
MathHelper.removeEpsilonEqualPoints2d(intersections, 2d * EPSILON);
// if there is a nearby point contained in an edge of the triangle, then retain only that on-edge point and
// remove all nearby points
for (int i = 0; i < intersections.size(); i++) {
if (!sidesContain(intersections.get(i))) {
Point2d substitution = null;
for (int j = 0; j < intersections.size(); j++) {
if (j == i)
continue;
if (sidesContain(intersections.get(j))
&& intersections.get(i).distance(intersections.get(j)) < 10d * EPSILON) {
substitution = intersections.get(j);
break;
}
}
if (substitution != null) {
intersections.remove(i--);
}
}
}
double i = 2d;
while (intersections.size() > 2 && i < 10d) {
MathHelper.removeEpsilonEqualPoints2d(intersections, i++ * EPSILON);
}
if (i > 2d)
Logger.getLogger(getClass()).warn(
"Used " + (i - 1)
+ "*EPSILON for joining intersection points. The resulting intersection points are "
+ intersections);
if (intersections.size() == 2) {
return new Segment2d(intersections.get(0), intersections.get(1));
} else if (intersections.size() == 1) {
return new Segment2d(intersections.get(0), intersections.get(0));
} else if (intersections.size() == 0) {
return null;
} else {
throw new IllegalStateException("Illegal count of intersections of a line and triangle: "
+ intersections.size());
}
}
/**
* Return the point corresponding to the given barycentric coordinates in this triangle.
*
* @param b The barycentric coordinates to convert.
* @return The point corresponding to the given barycentric coordinates in this triangle.
*/
public Point2d interpolatePointFromBarycentric(Vector3d b)
{
Point2d result = new Point2d();
result.x = b.x * p1.x + b.y * p2.x + b.z * p3.x;
result.y = b.x * p1.y + b.y * p2.y + b.z * p3.y;
return result;
}
/**
* Returns the barycentric coordinates of the given point in this triangle.
*
* @param p The point to get barycentric coordinates for.
* @return The barycentric coordinates of the given point.
*/
public Vector3d getBarycentricCoords(Point2d p)
{
Vector3d result = new Vector3d();
result.x = b1 * (p.x - p3.x) + b2 * (p.y - p3.y);
result.y = b3 * (p.x - p3.x) + b4 * (p.y - p3.y);
result.z = 1 - result.x - result.y;
return result;
}
@Override
public Triangle2d clone()
{
return new Triangle2d(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((p1 == null) ? 0 : p1.hashCode());
result = prime * result + ((p2 == null) ? 0 : p2.hashCode());
result = prime * result + ((p3 == null) ? 0 : p3.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;
Triangle2d other = (Triangle2d) obj;
if (p1 == null) {
if (other.p1 != null)
return false;
} else if (!p1.equals(other.p1))
return false;
if (p2 == null) {
if (other.p2 != null)
return false;
} else if (!p2.equals(other.p2))
return false;
if (p3 == null) {
if (other.p3 != null)
return false;
} else if (!p3.equals(other.p3))
return false;
return true;
}
/**
* Return <code>true</code> if the given triangle is almost equal to this one.
*
* @param other The triangle to compare.
* @return <code>true</code> if the given triangle is almost equal to this one.
*/
public boolean epsilonEquals(Triangle2d other)
{
if (other == null)
return false;
return p1.epsilonEquals(other.p1, EPSILON) && p2.epsilonEquals(other.p2, EPSILON)
&& p3.epsilonEquals(other.p3, EPSILON);
}
@Override
public String toString()
{
return "Triangle2d [" + p1 + ", " + p2 + ", " + p3 + "]";
}
}