/*
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;
//todo: check javadoc accuracy,
/**
* Instances of <code>Frustum</code> are immutable. </p>
*
* @author Tom Gaskins
* @version $Id: Frustum.java 3301 2007-10-16 00:31:34Z dcollins $
*/
public class Frustum
{
private final Plane left;
private final Plane right;
private final Plane bottom;
private final Plane top;
private final Plane near;
private final Plane far;
private final Plane[] allPlanes;
/**
* Create a default frustum with six <code>Plane</code>s. This defines a box of dimension (2, 2, 2) centered at the
* origin.
*/
public Frustum()
{
this(
new Plane(1, 0, 0, 1), // Left
new Plane(-1, 0, 0, 1), // Right
new Plane(0, 1, 0, 1), // Bottom
new Plane(0, -1, 0, 1), // Top
new Plane(0, 0, -1, 1), // Near
new Plane(0, 0, 1, 1)); // Far
}
/**
* Create a frustum from six <code>Plane</code>s, which define its boundaries. Does not except null arguments.
*
* @param near the near plane
* @param far the far plane
* @param left the left side of the view frustum
* @param right the right side of the view frustm
* @param top the top of the view frustum
* @param bottom the bottom of the view frustum
* @throws IllegalArgumentException if any argument is null
*/
public Frustum(Plane left, Plane right, Plane bottom, Plane top, Plane near, Plane far)
{
if (left == null || right == null || bottom == null || top == null || near == null || far == null)
{
String message = Logging.getMessage("nullValue.PlaneIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.left = left;
this.right = right;
this.bottom = bottom;
this.top = top;
this.near = near;
this.far = far;
this.allPlanes = new Plane[] {this.left, this.right, this.bottom, this.top, this.near, this.far};
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Frustum that = (Frustum) obj;
return this.left.equals(that.left)
&& this.right.equals(that.right)
&& this.bottom.equals(that.bottom)
&& this.top.equals(that.top)
&& this.near.equals(that.near)
&& this.far.equals(that.far);
}
public final Plane getLeft()
{
return this.left;
}
public final Plane getRight()
{
return this.right;
}
public final Plane getBottom()
{
return this.bottom;
}
public final Plane getTop()
{
return this.top;
}
public final Plane getNear()
{
return this.near;
}
public final Plane getFar()
{
return this.far;
}
public Plane[] getAllPlanes()
{
return this.allPlanes;
}
public int hashCode()
{
int result;
result = this.left.hashCode();
result = 31 * result + this.right.hashCode();
result = 19 * result + this.bottom.hashCode();
result = 23 * result + this.top.hashCode();
result = 17 * result + this.near.hashCode();
result = 19 * result + this.far.hashCode();
return result;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("(");
sb.append("left=").append(this.near);
sb.append(", right=").append(this.near);
sb.append(", bottom=").append(this.near);
sb.append(", top=").append(this.near);
sb.append(", near=").append(this.near);
sb.append(", far=").append(this.near);
sb.append(")");
return sb.toString();
}
// ============== Factory Functions ======================= //
// ============== Factory Functions ======================= //
// ============== Factory Functions ======================= //
public static Frustum fromProjectionMatrix(Matrix projectionMatrix)
{
//noinspection UnnecessaryLocalVariable
Matrix m = projectionMatrix;
if (m == null)
{
String message = Logging.getMessage("nullValue.MatrixIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Extract the left clipping plane from the projection-matrix.
double leftMag = Math.sqrt((m.m41 + m.m11) * (m.m41 + m.m11) + (m.m42 + m.m12) * (m.m42 + m.m12)
+ (m.m43 + m.m13) * (m.m43 + m.m13));
Plane leftPlane = new Plane((m.m41 + m.m11) / leftMag, (m.m42 + m.m12) / leftMag, (m.m43 + m.m13) / leftMag,
m.m44 + m.m14);
// Extract the right clipping plane from the projection-matrix.
double rightMag = Math.sqrt((m.m41 - m.m11) * (m.m41 - m.m11) + (m.m42 - m.m12) * (m.m42 - m.m12)
+ (m.m43 - m.m13) * (m.m43 - m.m13));
Plane rightPlane = new Plane((m.m41 - m.m11) / rightMag, (m.m42 - m.m12) / rightMag, (m.m43 - m.m13) / rightMag,
m.m44 - m.m14);
// Extract the bottom clipping plane from the projection-matrix.
double bottomMag = Math.sqrt((m.m41 + m.m21) * (m.m41 + m.m21) + (m.m42 + m.m22) * (m.m42 + m.m22)
+ (m.m43 + m.m23) * (m.m43 + m.m23));
Plane bottomPlane = new Plane((m.m41 + m.m21) / bottomMag, (m.m42 + m.m22) / bottomMag,
(m.m43 + m.m23) / bottomMag,
m.m44 + m.m24);
// Extract the top clipping plane from the projection-matrix.
double topMag = Math.sqrt((m.m41 - m.m21) * (m.m41 - m.m21) + (m.m42 - m.m22) * (m.m42 - m.m22)
+ (m.m43 - m.m23) * (m.m43 - m.m23));
Plane topPlane = new Plane((m.m41 - m.m21) / topMag, (m.m42 - m.m22) / topMag, (m.m43 - m.m23) / topMag,
m.m44 - m.m24);
// Extract the near clipping plane from the projection-matrix.
double nearMag = Math.sqrt((m.m41 + m.m31) * (m.m41 + m.m31) + (m.m42 + m.m32) * (m.m42 + m.m32)
+ (m.m43 + m.m33) * (m.m43 + m.m33));
Plane nearPlane = new Plane((m.m41 + m.m31) / nearMag, (m.m42 + m.m32) / nearMag, (m.m43 + m.m33) / nearMag,
m.m44 + m.m34);
// Extract the far clipping plane from the projection-matrix.
double farMag = Math.sqrt((m.m41 - m.m31) * (m.m41 - m.m31) + (m.m42 - m.m32) * (m.m42 - m.m32)
+ (m.m43 - m.m33) * (m.m43 - m.m33));
Plane farPlane = new Plane((m.m41 - m.m31) / farMag, (m.m42 - m.m32) / farMag, (m.m43 - m.m33) / farMag,
m.m44 - m.m34);
return new Frustum(leftPlane, rightPlane, bottomPlane, topPlane, nearPlane, farPlane);
}
/**
* Creates a <code>Frustum</code> from a horizontal field-of-view, viewport aspect ratio and distance to near and
* far depth clipping planes. The near plane must be closer than the far plane, and both planes must be a positive
* distance away.
*
* @param horizontalFieldOfView horizontal field-of-view angle in the range (0, 180)
* @param viewportWidth the width of the viewport in screen pixels
* @param viewportHeight the height of the viewport in screen pixels
* @param near distance to the near depth clipping plane
* @param far distance to far depth clipping plane
* @throws IllegalArgumentException if fov is not in the range (0, 180), if either near or far are negative, or near
* is greater than or equal to far
*/
public static Frustum fromPerspective(Angle horizontalFieldOfView, int viewportWidth, int viewportHeight,
double near, double far)
{
if (horizontalFieldOfView == null)
{
String message = Logging.getMessage("Geom.ViewFrustum.FieldOfViewIsNull");
Logging.logger().fine(message);
throw new IllegalArgumentException(message);
}
double fov = horizontalFieldOfView.getDegrees();
double farMinusNear = far - near;
String message = null;
if (fov <= 0 || fov > 180)
message = Logging.getMessage("Geom.ViewFrustum.FieldOfViewOutOfRange", fov);
if (near <= 0 || farMinusNear <= 0)
message = Logging.getMessage("Geom.ViewFrusutm.ClippingDistanceOutOfRange");
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double focalLength = 1d / horizontalFieldOfView.tanHalfAngle();
double aspect = viewportHeight / (double) viewportWidth;
double lrLen = Math.sqrt(focalLength * focalLength + 1);
double btLen = Math.sqrt(focalLength * focalLength + aspect * aspect);
Plane leftPlane = new Plane(focalLength / lrLen, 0d, 0d - 1d / lrLen, 0);
Plane rightPlane = new Plane(0d - focalLength / lrLen, 0d, 0d - 1d / lrLen, 0d);
Plane bottomPlane = new Plane(0d, focalLength / btLen, 0d - aspect / btLen, 0d);
Plane topPlane = new Plane(0d, 0d - focalLength / btLen, 0d - aspect / btLen, 0d);
Plane nearPlane = new Plane(0d, 0d, 0d - 1d, 0d - near);
Plane farPlane = new Plane(0d, 0d, 1d, far);
return new Frustum(leftPlane, rightPlane, bottomPlane, topPlane, nearPlane, farPlane);
}
/**
* Creates a <code>Frustum</code> from three sets of parallel clipping planes (a parallel projectionMatrix). In this
* case, the near and far depth clipping planes may be a negative distance away.
*
* @param near distance to the near depth clipping plane
* @param far distance to far depth clipping plane
* @param width horizontal dimension of the near clipping plane
* @param height vertical dimension of the near clipping plane
* @throws IllegalArgumentException if the difference of any plane set (lright - left, top - bottom, far - near) is
* less than or equal to zero.
*/
public static Frustum fromPerspective(double width, double height, double near, double far)
{
double farMinusNear = far - near;
if (farMinusNear <= 0.0 || width <= 0.0 || height <= 0.0)
{
String message = Logging.getMessage("Geom.ViewFrusutm.ClippingDistanceOutOfRange");
Logging.logger().fine(message);
throw new IllegalArgumentException(message);
}
double width_over_2 = width / 2.0;
double height_over_2 = height / 2.0;
Plane leftPlane = new Plane(1.0, 0.0, 0.0, width_over_2);
Plane rightPlane = new Plane(-1.0, 0.0, 0.0, width_over_2);
Plane bottomPlane = new Plane(0.0, 1.0, 0.0, height_over_2);
Plane topPlane = new Plane(0.0, -1.0, 0.0, height_over_2);
Plane nearPlane = new Plane(0.0, 0.0, -1.0, (near < 0.0) ? near : -near);
Plane farPlane = new Plane(0.0, 0.0, 1.0, (far < 0.0) ? -far : far);
return new Frustum(leftPlane, rightPlane, bottomPlane, topPlane, nearPlane, farPlane);
}
// ============== Intersection Functions ======================= //
// ============== Intersection Functions ======================= //
// ============== Intersection Functions ======================= //
/**
* @param extent
* @return
* @throws IllegalArgumentException if <code>extent</code> is null
*/
public final boolean intersects(Extent extent)
{
if (extent == null)
{
String msg = Logging.getMessage("nullValue.ExtentIsNull");
Logging.logger().fine(msg);
throw new IllegalArgumentException(msg);
}
// See if the extent's bounding sphere is within or intersects the frustum.
Vec4 c = extent.getCenter();
double nr = -extent.getRadius();
if (this.far.dot(c) <= nr)
return false;
if (this.left.dot(c) <= nr)
return false;
if (this.right.dot(c) <= nr)
return false;
if (this.top.dot(c) <= nr)
return false;
if (this.bottom.dot(c) <= nr)
return false;
//noinspection RedundantIfStatement
if (this.near.dot(c) <= nr)
return false;
return true;
}
/**
* @param point
* @return
* @throws IllegalArgumentException if <code>point</code> is null
*/
public final boolean contains(Vec4 point)
{
if (point == null)
{
String msg = Logging.getMessage("nullValue.PointIsNull");
Logging.logger().fine(msg);
throw new IllegalArgumentException(msg);
}
if (this.far.dot(point) <= 0)
return false;
if (this.left.dot(point) <= 0)
return false;
if (this.right.dot(point) <= 0)
return false;
if (this.top.dot(point) <= 0)
return false;
if (this.bottom.dot(point) <= 0)
return false;
//noinspection RedundantIfStatement
if (this.near.dot(point) <= 0)
return false;
return true;
}
// ============== Geometric Functions ======================= //
// ============== Geometric Functions ======================= //
// ============== Geometric Functions ======================= //
/**
* @param matrix
* @return
* @throws IllegalArgumentException if <code>matrix</code> is null
*/
public Frustum transformBy(Matrix matrix)
{
ensureNonNull(matrix);
Plane left = new Plane(this.left.getVector().transformBy4(matrix));
Plane right = new Plane(this.right.getVector().transformBy4(matrix));
Plane bottom = new Plane(this.bottom.getVector().transformBy4(matrix));
Plane top = new Plane(this.top.getVector().transformBy4(matrix));
Plane near = new Plane(this.near.getVector().transformBy4(matrix));
Plane far = new Plane(this.far.getVector().transformBy4(matrix));
return new Frustum(left, right, bottom, top, near, far);
}
// ============== Helper Functions ======================= //
// ============== Helper Functions ======================= //
// ============== Helper Functions ======================= //
private static void ensureNonNull(Matrix matrix)
{
if (matrix == null)
{
String msg = Logging.getMessage("nullValue.MatrixIsNull");
Logging.logger().fine(msg);
throw new IllegalArgumentException(msg);
}
}
}