/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.geom;
import gov.nasa.worldwind.util.Logging;
/**
* A <code>Plane</code> object represents a mathematical plane in an arbitrary cartesian co-ordinate system. A
* <code>Plane</code> is defined by a normal vector and a distance along that vector from the origin, where the distance
* represents the distance from the origin to the <code>Plane</code> rather than from the <code>Plane</code> to the
* origin.
* <p/>
* <p/>
* Instances of <code>Plane</code> are immutable. </p>
*
* @author Tom Gaskins
* @version $Id: Plane.java 3252 2007-10-10 15:20:12Z tgaskins $
*/
public final class Plane
{
/**
* Represents all the information about this <code>Plane</code>. The first three values (<code>x, y, z</code>) of
* <code>v</code> represent a normal <code>Vector</code> to the <code>Plane</code>, while the fourth
* (<code>w</code>) represents the signed distance this <code>Plane</code> has been shifted along that normal.
*/
private final Vec4 n;
/**
* Obtains a new instance of a <code>Plane</code> whose information is contained in <code>Vector</code>
* <code>vec</code>.
*
* @param vec the <code>Vector</code> containing information about this <code>Plane</code>'s normal and distance
* @throws IllegalArgumentException if passed a null or zero-length <code>Vector</code>
*/
public Plane(Vec4 vec)
{
if (vec == null)
{
String message = Logging.getMessage("nullValue.VectorIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (vec.getLengthSquared3() == 0.0)
{
String message = Logging.getMessage("Geom.Plane.VectorIsZero");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.n = vec;
}
/**
* Obtains a new <code>Plane</code> whose normal is defined by the vector (a,b,c) and whose disance from that vector
* is d. The vector may not have zero length.
*
* @param a the x-parameter of the normal to this <code>Plane</code>
* @param b the y-parameter of the normal to this <code>Plane</code>
* @param c the z-parameter of the normal to this <code>Plane</code>
* @param d the distance of this <code>Plane</code> from the origin along its normal.
* @throws IllegalArgumentException if <code>0==a==b==c</code>
*/
public Plane(double a, double b, double c, double d)
{
if (a == 0.0 && b == 0.0 && c == 0.0)
{
String message = Logging.getMessage("Geom.Plane.VectorIsZero");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.n = new Vec4(a, b, c, d);
}
/**
* Retrieves a <code>Vec4</code> representing the normal to this <code>Plane</code>.
*
* @return a <code>Vec4</code> representing the normal to this <code>Plane</code>
*/
public final Vec4 getNormal()
{
return new Vec4(this.n.x, this.n.y, this.n.z);
}
/**
* Retrieves the distance from the origin to this <code>Plane</code>. Two options exist for defining distance - the
* first represents the distance from the origin to the <code>Plane</code>, the second represents the distance from
* the <code>Plane</code> to the origin. This function uses the first method. The outcome of this is that depending
* on the caller's view of this method, the sign of distances may appear to be reversed.
*
* @return the distance between this <code>Plane</code> and the origin
*/
public final double getDistance()
{
return this.n.w;
}
/**
* Retrieves a vector representing the normal and distance to this <code>Plane</code>. The vector has the structure
* (x, y, z, distance), where (x, y, z) represents the normal, and distance represents the distance from the
* origin.
*
* @return a <code>Vector</code> representation of this <code>Plane</code>
*/
public final Vec4 getVector()
{
return this.n;
}
/**
* Calculates the dot product of this <code>Plane</code> with Vec4 <code>p</code>.
*
* @param p the Vec4 to dot with this <code>Plane</code>
* @return the dot product of <code>p</code> and this <code>Plane</code>
* @throws IllegalArgumentException if <code>p</code> is null
*/
public final double dot(Vec4 p)
{
if (p == null)
{
String message = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return this.n.x * p.x + this.n.y * p.y + this.n.z * p.z + this.n.w * p.w;
}
public final Plane computeParallelPlaneAtDistance(double distance)
{
return new Plane(this.n.x, this.n.y, this.n.z, distance);
}
/**
* Determine the point of intersection of a line with this plane.
*
* @param line the line to test
* @return The point on the line at which it intersects the plane. null is returned if the line does not intersect
* the plane. The line's origin is returned if the line is coincident with the plane.
*/
public Vec4 intersect(Line line)
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
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.
*/
public double intersectDistance(Line line)
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double ldotv = this.n.dot3(line.getDirection());
if (ldotv == 0) // are line and plane perpendicular
{
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)
{
String message = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
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);
}
/**
* 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 that are 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)
{
String message = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// 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);
}
public int onSameSide(Vec4 pa, Vec4 pb)
{
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;
}
public int onSameSide(Vec4[] pts)
{
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++)
{
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;
}
@Override
public final boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final gov.nasa.worldwind.geom.Plane plane = (gov.nasa.worldwind.geom.Plane) o;
//noinspection RedundantIfStatement
if (!this.n.normalize3().equals(plane.n.normalize3()))
return false;
return true;
}
@Override
public final int hashCode()
{
return this.n.hashCode();
}
@Override
public final String toString()
{
return this.n.toString();
}
}