/*
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;
/**
* @author Tom Gaskins
* @version $Id: Line.java 3209 2007-10-06 21:57:53Z tgaskins $
*/
public final class Line// Instances are immutable
{
private final Vec4 origin;
private final Vec4 direction;
/**
* Create the line containing a line segement between two points.
*
* @param pa the first point of the line segment.
* @param pb the second point of the line segment.
* @return The line containing the two points.
* @throws IllegalArgumentException if either point is null or they are coincident.
*/
public static Line fromSegment(Vec4 pa, Vec4 pb)
{
return new Line(pa, new Vec4(pb.x - pa.x, pb.y - pa.y, pb.z - pa.z, 0));
}
/**
* @param origin
* @param direction
* @throws IllegalArgumentException if <code>origin</code> is null, or <code>direction</code> is null or has zero
* length
*/
public Line(Vec4 origin, Vec4 direction)
{
String message = null;
if (origin == null)
message = "nullValue.OriginIsNull";
else if (direction == null)
message = "nullValue.DirectionIsNull";
else if (direction.getLength3() <= 0)
message = "Geom.Line.DirectionIsZeroVector";
if (message != null)
{
message = Logging.getMessage(message);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.origin = origin;
this.direction = direction;
}
public final Vec4 getDirection()
{
return direction;
}
public final Vec4 getOrigin()
{
return origin;
}
public final Vec4 getPointAt(double t)
{
return Vec4.fromLine3(this.origin, t, this.direction);
}
public final double selfDot()
{
return this.origin.dot3(this.direction);
}
/**
* Performs a comparison to test whether this Object is internally identical to the other Object <code>o</code>.
* This method takes into account both direction and origin, so two lines which may be equivalent may not be
* considered equal.
*
* @param o the object to be compared against.
* @return true if these two objects are equal, false otherwise
*/
@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.Line line = (gov.nasa.worldwind.geom.Line) o;
if (!direction.equals(line.direction))
return false;
//noinspection RedundantIfStatement
if (!line.origin.equals(origin))
return false;
return true;
}
@Override
public final int hashCode()
{
int result;
result = origin.hashCode();
result = 29 * result + direction.hashCode();
return result;
}
public String toString()
{
return "Origin: " + this.origin + ", Direction: " + this.direction;
}
//
// public final double distanceToOld(Vec4 p)
// {
// if (p == null)
// {
// String message = Logging.getMessage("nullValue.PointIsNull");
// Logging.logger().severe(message);
// throw new IllegalArgumentException(message);
// }
//
// Vec4 origin = this.origin;
// Vec4 sideB = origin.subtract3(p); // really a vector
//
// double distanceToOrigin = sideB.dot3(this.direction);
// double divisor = distanceToOrigin / this.direction.getLengthSquared3();
//
// Vec4 sideA = this.direction.multiply3(divisor);
//
// double aSquared = sideA.getLengthSquared3();
// double bSquared = sideB.getLengthSquared3();
//
// return Math.sqrt(bSquared - aSquared);
// }
public final Vec4 nearestPointTo(Vec4 p)
{
Vec4 w = p.subtract3(this.origin);
double c1 = w.dot3(this.direction);
double c2 = this.direction.dot3(this.direction);
return this.origin.add3(this.direction.multiply3(c1 / c2));
}
/**
* Calculate the shortests distance between this line and a specified <code>Vec4</code>. This method returns a
* positive distance.
*
* @param p the <code>Vec4</code> whose distance from this <code>Line</code> will be calculated
* @return the distance between this <code>Line</code> and the specified <code>Vec4</code>
* @throws IllegalArgumentException if <code>p</code> is null
*/
public final double distanceTo(Vec4 p)
{
return p.distanceTo3(this.nearestPointTo(p));
}
public static Vec4 nearestPointOnSegment(Vec4 p0, Vec4 p1, Vec4 p)
{
Vec4 v = p1.subtract3(p0);
Vec4 w = p.subtract3(p0);
double c1 = w.dot3(v);
double c2 = v.dot3(v);
if (c1 <= 0)
return p0;
if (c2 <= c1)
return p1;
return p0.add3(v.multiply3(c1 / c2));
}
public static double distanceToSegment(Vec4 p0, Vec4 p1, Vec4 p)
{
Vec4 pb = nearestPointOnSegment(p0, p1, p);
return p.distanceTo3(pb);
}
/**
* Clip a line segment to a frustum, returning the end points of the portion of the segment that is within the
* frustum.
*
* @param pa the first point of the segment.
* @param pb the second point of the segment.
* @param frustum the frustum.
* @return The two points at which the segment intersects the frustum, or null if the segment does not intersect and
* the frustum does not fully contain it. If the segment is coincident with a plane of the frustum, the
* returned segment is the portion of the original segment on that plane, clipped to the other frustum
* planes.
*/
public static Vec4[] clipToFrustum(Vec4 pa, Vec4 pb, Frustum frustum)
{
return clipToFrustum(pa, pb, frustum, 1);
}
private static Vec4[] clipToFrustum(Vec4 pa, Vec4 pb, Frustum frustum, int maxRecursionCount)
{
if (pa == null || pb == null)
{
String message = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (frustum == null)
{
String message = Logging.getMessage("nullValue.FrustumIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// First do a trivial accept test.
if (frustum.contains(pa) && frustum.contains(pb))
return new Vec4[] {pa, pb};
Vec4[] segment = new Vec4[] {pa, pb};
Vec4[] ipts;
for (Plane p : frustum.getAllPlanes())
{
// See if both points are behind the plane and therefore not in the frustum.
if (p.onSameSide(segment[0], segment[1]) < 0)
return null;
// Clip the segment to the plane if they intersect.
ipts = p.clip(segment[0], segment[1]);
if (ipts != null)
{
segment = ipts;
}
}
// If one of the initial points was in the frustum then the segment must have been clipped.
if (frustum.contains(pa) || frustum.contains(pb))
return segment;
// The segment was clipped by an infinite frustum plane but may still lie outside the frustum.
// So recurse using the clipped segment.
if (maxRecursionCount > 0)
return clipToFrustum(segment[0], segment[1], frustum, --maxRecursionCount);
else
return segment;
}
}