/*
* Copyright (C) 2011 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.geom;
/**
* Represents a <code>Plane</code> in Cartesian coordinates, defined by a normal vector to the plane and a signed scalar
* value proportional to the distance of the plane from the origin. The sign of the value is relative to the direction
* of the plane normal.
*
* @author Tom Gaskins
* @version $Id$
*/
public final class Plane
{
private final Vec4 n; // the plane normal and proportional distance. The vector is not necessarily a unit vector.
/**
* Constructs a plane from a 4-D vector giving the plane normal vector and distance.
*
* @param vec a 4-D vector indicating the plane's normal vector and distance. The normal need not be unit length.
*
* @throws IllegalArgumentException if the vector is null.
*/
public Plane(Vec4 vec)
{
if (vec == null)
{
throw new IllegalArgumentException("Vector Is Null");
}
if (vec.getLengthSquared3() == 0.0)
{
throw new IllegalArgumentException("Vector Is Zero");
}
this.n = vec;
}
/**
* Constructs a plane from four values giving the plane normal vector and distance.
*
* @param nx the X component of the plane normal vector.
* @param ny the Y component of the plane normal vector.
* @param nz the Z component of the plane normal vector.
* @param d the plane distance.
*
* @throws IllegalArgumentException if the normal vector components define the zero vector (all values are zero).
*/
public Plane(double nx, double ny, double nz, double d)
{
if (nx == 0.0 && ny == 0.0 && nz == 0.0)
{
throw new IllegalArgumentException("Vector Is Zero");
}
this.n = new Vec4(nx, ny, nz, d);
}
/**
* Returns the plane that passes through the specified three points. The plane's normal is the cross product of the
* two vectors from <code>pb</code> to </code>pa</code> and <code>pc</code> to </code>pa</code>, respectively. The
* returned plane is undefined if any of the specified points are colinear.
*
* @param pa the first point.
* @param pb the second point.
* @param pc the third point.
*
* @return a <code>Plane</code> passing through the specified points.
*
* @throws IllegalArgumentException if <code>pa</code>, <code>pb</code>, or <code>pc</code> is <code>null</code>.
*/
public static Plane fromPoints(Vec4 pa, Vec4 pb, Vec4 pc)
{
if (pa == null || pb == null || pc == null)
{
throw new IllegalArgumentException("Vec4 Is Null");
}
Vec4 vab = pb.subtract3(pa);
Vec4 vac = pc.subtract3(pa);
Vec4 n = vab.cross3(vac);
double d = -n.dot3(pa);
return new Plane(n.x, n.y, n.z, d);
}
/**
* Returns the plane's normal vector.
*
* @return the plane's normal vector.
*/
public final Vec4 getNormal()
{
return this.n;//new Vec4(this.n.x, this.n.y, this.n.z);
}
/**
* Returs the plane distance.
*
* @return the plane distance.
*/
public final double getDistance()
{
return this.n.w;
}
/**
* Returns a 4-D vector holding the plane's normal and distance.
*
* @return a 4-D vector indicating the plane's normal vector and distance.
*/
public final Vec4 getVector()
{
return this.n;
}
/**
* Returns a normalized version of this plane. The normalized plane's normal vector is unit length and its distance
* is D/|N| where |N| is the length of this plane's normal vector.
*
* @return a normalized copy of this Plane.
*/
public final Plane normalize()
{
double length = this.n.getLength3();
if (length == 0) // should not happen, but check to be sure.
return this;
return new Plane(new Vec4(
this.n.x / length,
this.n.y / length,
this.n.z / length,
this.n.w / length));
}
/**
* Calculates the 4-D dot product of this plane with a vector.
*
* @param p the vector.
*
* @return the dot product of the plane and the vector.
*
* @throws IllegalArgumentException if the vector is null.
*/
public final double dot(Vec4 p)
{
if (p == null)
{
throw new IllegalArgumentException("Point Is Null");
}
return this.n.x * p.x + this.n.y * p.y + this.n.z * p.z + this.n.w * p.w;
}
/**
* Determine the intersection point of a line with this plane.
*
* @param line the line to intersect.
*
* @return the intersection point if the line intersects the plane, otherwise null.
*
* @throws IllegalArgumentException if the line is null.
*/
public Vec4 intersect(Line line)
{
if (line == null)
{
throw new IllegalArgumentException("Line Is Null");
}
double t = this.intersectDistance(line);
if (Double.isNaN(t))
return null;
if (Double.isInfinite(t))
return line.getOrigin();
return line.getPointAt(t);
}
/**
* Determine the parametric point of intersection of a line with this plane.
*
* @param line the line to test
*
* @return The parametric value of the point on the line at which it intersects the plane. {@link Double#NaN} is
* returned if the line does not intersect the plane. {@link Double#POSITIVE_INFINITY} is returned if the
* line is coincident with the plane.
*
* @throws IllegalArgumentException if the line is null.
*/
public double intersectDistance(Line line)
{
if (line == null)
{
throw new IllegalArgumentException("Line Is Null");
}
double ldotv = this.n.dot3(line.getDirection());
if (ldotv == 0) // are line and plane parallel
{
double ldots = this.n.dot4(line.getOrigin());
if (ldots == 0)
return Double.POSITIVE_INFINITY; // line is coincident with the plane
else
return Double.NaN; // line is not coincident with the plane
}
return -this.n.dot4(line.getOrigin()) / ldotv; // ldots / ldotv
}
/**
* Test a line segment for intersection with this plane. If it intersects, return the point of intersection.
*
* @param pa the first point of the line segment.
* @param pb the second point of the line segment.
*
* @return The point of intersection with the plane. Null is returned if the segment does not instersect this plane.
* {@link gov.nasa.worldwind.geom.Vec4#INFINITY} coincident with the plane.
*
* @throws IllegalArgumentException if either input point is null.
*/
public Vec4 intersect(Vec4 pa, Vec4 pb)
{
if (pa == null || pb == null)
{
throw new IllegalArgumentException("Point Is Null");
}
try
{
// Test if line segment is in fact a point
if (pa.equals(pb))
{
double d = this.distanceTo(pa);
if (d == 0)
return pa;
else
return null;
}
Line l = Line.fromSegment(pa, pb);
double t = this.intersectDistance(l);
if (Double.isInfinite(t))
return Vec4.INFINITY;
if (Double.isNaN(t) || t < 0 || t > 1)
return null;
return l.getPointAt(t);
}
catch (IllegalArgumentException e)
{
return null;
}
}
/**
* Clip a line segment to this plane.
*
* @param pa the first point of the segment.
* @param pb the second point of the segment.
*
* @return An array of two points both on the positive side of the plane. If the direction of the line formed by the
* two points is positive with respect to this plane's normal vector, the first point in the array will be
* the intersection point on the plane, and the second point will be the original segment end point. If the
* direction of the line is negative with respect to this plane's normal vector, the first point in the
* array will be the original segment's begin point, and the second point will be the intersection point on
* the plane. If the segment does not intersect the plane, null is returned. If the segment is coincident
* with the plane, the input points are returned, in their input order.
*
* @throws IllegalArgumentException if either input point is null.
*/
public Vec4[] clip(Vec4 pa, Vec4 pb)
{
if (pa == null || pb == null)
{
throw new IllegalArgumentException("Point Is Null");
}
if (pa.equals(pb))
return null;
// Get the projection of the segment onto the plane.
Line line = Line.fromSegment(pa, pb);
double ldotv = this.n.dot3(line.getDirection());
// Are the line and plane parallel?
if (ldotv == 0) // line and plane are parallel and maybe coincident
{
double ldots = this.n.dot4(line.getOrigin());
if (ldots == 0)
return new Vec4[] {pa, pb}; // line is coincident with the plane
else
return null; // line is not coincident with the plane
}
// Not parallel so the line intersects. But does the segment intersect?
double t = -this.n.dot4(line.getOrigin()) / ldotv; // ldots / ldotv
if (t < 0 || t > 1) // segment does not intersect
return null;
Vec4 p = line.getPointAt(t);
if (ldotv > 0)
return new Vec4[] {p, pb};
else
return new Vec4[] {pa, p};
}
public double distanceTo(Vec4 p)
{
return this.n.dot4(p);
}
/**
* Determines whether two points are on the same side of a plane.
*
* @param pa the first point.
* @param pb the second point.
*
* @return true if the points are on the same side of the plane, otherwise false.
*
* @throws IllegalArgumentException if either point is null.
*/
public int onSameSide(Vec4 pa, Vec4 pb)
{
if (pa == null || pb == null)
{
throw new IllegalArgumentException("Point Is Null");
}
double da = this.distanceTo(pa);
double db = this.distanceTo(pb);
if (da < 0 && db < 0)
return -1;
if (da > 0 && db > 0)
return 1;
return 0;
}
/**
* Determines whether multiple points are on the same side of a plane.
*
* @param pts the array of points.
*
* @return true if the points are on the same side of the plane, otherwise false.
*
* @throws IllegalArgumentException if the points array is null or any point within it is null.
*/
public int onSameSide(Vec4[] pts)
{
if (pts == null)
{
throw new IllegalArgumentException("Points Array Is Null");
}
double d = this.distanceTo(pts[0]);
int side = d < 0 ? -1 : d > 0 ? 1 : 0;
if (side == 0)
return 0;
for (int i = 1; i < pts.length; i++)
{
if (pts[i] == null)
{
throw new IllegalArgumentException("Point Is Null");
}
d = this.distanceTo(pts[i]);
if ((side == -1 && d < 0) || (side == 1 && d > 0))
continue;
return 0; // point is not on same side as the others
}
return side;
}
/**
* Compute the intersection of three planes.
*
* @param pa the first plane.
* @param pb the second plane.
* @param pc the third plane.
*
* @return the Cartesian coordinates of the intersection, or null if the three planes to not intersect at a point.
*
* @throws IllegalArgumentException if any of the planes are null.
*/
public static Vec4 intersect(Plane pa, Plane pb, Plane pc)
{
if (pa == null || pb == null || pc == null)
{
throw new IllegalArgumentException("Plane Is Null");
}
Vec4 na = pa.getNormal();
Vec4 nb = pb.getNormal();
Vec4 nc = pc.getNormal();
Matrix m = new Matrix(
na.x, na.y, na.z, 0,
nb.x, nb.y, nb.z, 0,
nc.x, nc.y, nc.z, 0,
0, 0, 0, 1, true
);
Matrix mInverse = m.getInverse();
Vec4 D = new Vec4(-pa.getDistance(), -pb.getDistance(), -pc.getDistance());
return D.transformBy3(mInverse);
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof Plane))
return false;
Plane plane = (Plane) o;
return !(n != null ? !n.equals(plane.n) : plane.n != null);
}
@Override
public int hashCode()
{
return n != null ? n.hashCode() : 0;
}
@Override
public final String toString()
{
return this.n.toString();
}
}