/**
*
*/
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.ArrayList;
import java.util.List;
import javax.vecmath.Point3d;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;
/**
* A rectangle in 3D space.
*
* @author Martin Pecka
*/
public class Rectangle3d implements Cloneable
{
/** A vertex of the rectangle. */
protected Point3d p1, p2, p3, p4;
/** An edge of the rectangle (s_i is the edge between p_(i+1 mod 4) and p_i). */
protected Segment3d s1, s2, s3, s4;
/**
* A halfspace defined by edge s_i, so that the halfspace's border plane is perpendicular to this rectangle's plane.
*/
protected HalfSpace3d hs1, hs2, hs3, hs4;
/** The plane the rectangle lies in. */
protected Plane3d plane;
/**
* Create a rectangle with the given vertices.
*
* @param p1 A vertex.
* @param p2 A vertex.
* @param p3 A vertex.
* @param p4 A vertex.
*
* @throws IllegalArgumentException If the given points don't define a rectangle.
*/
public Rectangle3d(Point3d p1, Point3d p2, Point3d p3, Point3d p4) throws IllegalArgumentException
{
setVertices(p1, p2, p3, p4);
}
/**
* Create a rectangle with the given vertices.
*
* @param p1x A vertex's x coordinate.
* @param p1y A vertex's y coordinate.
* @param p1z A vertex's z coordinate.
* @param p2x A vertex's x coordinate.
* @param p2y A vertex's y coordinate.
* @param p2z A vertex's z coordinate.
* @param p3x A vertex's x coordinate.
* @param p3y A vertex's y coordinate.
* @param p3z A vertex's z coordinate.
* @param p4x A vertex's x coordinate.
* @param p4y A vertex's y coordinate.
* @param p4z A vertex's z coordinate.
*
* @throws IllegalArgumentException If the given points don't define a rectangle.
*/
public Rectangle3d(double p1x, double p1y, double p1z, double p2x, double p2y, double p2z, double p3x, double p3y,
double p3z, double p4x, double p4y, double p4z) throws IllegalArgumentException
{
this(new Point3d(p1x, p1y, p1z), new Point3d(p2x, p2y, p2z), new Point3d(p3x, p3y, p3z), new Point3d(p4x, p4y,
p4z));
}
/**
* Compute new values of all the helper fields such as plane, s1, s2, s3, s4 and so...
*
* @throws IllegalArgumentException If the rectangle's points don't define a rectangle.
*/
protected void recomputeDerivedItems() throws IllegalArgumentException
{
plane = new Plane3d(p1, p2, p3);
if (!plane.contains(p4)) {
throw new IllegalArgumentException("Trying to construct an invalid rectangle.");
}
s1 = new Segment3d(p1, p2);
s2 = new Segment3d(p2, p3);
s3 = new Segment3d(p3, p4);
s4 = new Segment3d(p4, p1);
if (abs(s1.getVector().dot(s2.getVector())) > EPSILON || abs(s2.getVector().dot(s3.getVector())) > EPSILON
|| abs(s3.getVector().dot(s4.getVector())) > EPSILON
|| abs(s4.getVector().dot(s1.getVector())) > EPSILON) {
// the edges aren't perpendicular
throw new IllegalArgumentException("Trying to construct an invalid rectangle.");
}
// we don't need to check if opposite sides are equal length - the 90° condition above is sufficient
hs1 = HalfSpace3d.createPerpendicularToTriangle(p1, p2, p3);
hs2 = HalfSpace3d.createPerpendicularToTriangle(p2, p3, p4);
hs3 = HalfSpace3d.createPerpendicularToTriangle(p3, p4, p1);
hs4 = HalfSpace3d.createPerpendicularToTriangle(p4, p1, p2);
}
/**
* Tell whether this rectangle contains the given point.
*
* @param point The point to check.
* @return <code>true</code> if this rectangle contains the given point.
*/
public boolean contains(Point3d point)
{
return plane.contains(point) && hs1.contains(point) && hs2.contains(point) && hs3.contains(point)
&& hs4.contains(point);
}
/**
* Return the intersection of the given line and this rectangle.
*
* @param line The line to find intersection with.
* @return The intersection of the given line and this rectangle. Returns <code>null</code> if no intersection
* exists and returns a segment with zero direction vector if the intersection is only one point.
*/
public Segment3d getIntersection(Line3d line)
{
List<Point3d> intList = new ArrayList<Point3d>(2);
for (Segment3d s : getEdges()) {
Segment3d intersection = s.getIntersection(line);
if (intersection != null && intersection.v.epsilonEquals(new Vector3d(), EPSILON))
intList.add(intersection.p);
else if (intersection != null) // a whole side intersection, we can return it
return intersection;
}
if (intList.size() == 0)
return null;
MathHelper.removeEpsilonEqualPoints(intList);
Point3d p2 = (intList.size() == 1 ? intList.get(0) : intList.get(1));
return new Segment3d(intList.get(0), p2);
}
/**
* @return The width and height of the rectangle.
*/
public Vector2d getDimension()
{
return new Vector2d(s1.getLength(), s2.getLength());
}
/**
* @return the p1
*/
public Point3d getP1()
{
return p1;
}
/**
* @return the p2
*/
public Point3d getP2()
{
return p2;
}
/**
* @return the p3
*/
public Point3d getP3()
{
return p3;
}
/**
* @return the p4
*/
public Point3d getP4()
{
return p4;
}
/**
* @return An array of all vertices of this rectangle.
*/
public Point3d[] getVertices()
{
return new Point3d[] { p1, p2, p3, p4 };
}
/**
* Set new vertices of the rectangle.
*
* @param p1 A vertex.
* @param p2 A vertex.
* @param p3 A vertex.
* @param p4 A vertex.
*
* @throws IllegalArgumentException If the given points don't define a rectangle.
*/
public void setVertices(Point3d p1, Point3d p2, Point3d p3, Point3d p4) throws IllegalArgumentException
{
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.p4 = p4;
recomputeDerivedItems();
}
/**
* @return the s1
*/
public Segment3d getS1()
{
return s1;
}
/**
* @return the s2
*/
public Segment3d getS2()
{
return s2;
}
/**
* @return the s3
*/
public Segment3d getS3()
{
return s3;
}
/**
* @return the s4
*/
public Segment3d getS4()
{
return s4;
}
/**
* @return An array of all edges of the rectangle.
*/
public Segment3d[] getEdges()
{
return new Segment3d[] { s1, s2, s3, s4 };
}
/**
* @return the hs1
*/
public HalfSpace3d getHs1()
{
return hs1;
}
/**
* @return the hs2
*/
public HalfSpace3d getHs2()
{
return hs2;
}
/**
* @return the hs3
*/
public HalfSpace3d getHs3()
{
return hs3;
}
/**
* @return the hs4
*/
public HalfSpace3d getHs4()
{
return hs4;
}
/**
* @return the plane
*/
public Plane3d getPlane()
{
return plane;
}
@Override
protected Rectangle3d clone() throws CloneNotSupportedException
{
return new Rectangle3d(new Point3d(p1), new Point3d(p2), new Point3d(p3), new Point3d(p4));
}
@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());
result = prime * result + ((p4 == null) ? 0 : p4.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;
Rectangle3d other = (Rectangle3d) 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;
if (p4 == null) {
if (other.p4 != null)
return false;
} else if (!p4.equals(other.p4))
return false;
return true;
}
/**
* Return <code>true</code> if the given rectangle is almost equal to this one.
*
* @param other The rectangle to compare.
* @return <code>true</code> if the given rectangle is almost equal to this one.
*/
public boolean epsilonEquals(Rectangle3d other)
{
if (other == null)
return false;
if (p1 == null) {
if (other.p1 != null)
return false;
} else if (!p1.epsilonEquals(other.p1, EPSILON))
return false;
if (p2 == null) {
if (other.p2 != null)
return false;
} else if (!p2.epsilonEquals(other.p2, EPSILON))
return false;
if (p3 == null) {
if (other.p3 != null)
return false;
} else if (!p3.epsilonEquals(other.p3, EPSILON))
return false;
if (p4 == null) {
if (other.p4 != null)
return false;
} else if (!p4.epsilonEquals(other.p4, EPSILON))
return false;
return true;
}
@Override
public String toString()
{
return "Rectangle3d [p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + ", p4=" + p4 + "]";
}
}