/*
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.render.*;
import gov.nasa.worldwind.util.Logging;
/**
* Represents a sphere in three dimensional space.
* <p/>
* Instances of <code>Sphere</code> are immutable. </p>
*
* @author Tom Gaskins
* @version $Id: Sphere.java 2471 2007-07-31 21:50:57Z tgaskins $
*/
public final class Sphere implements Extent, Renderable
{
public final static Sphere UNIT_SPHERE = new Sphere(Vec4.ZERO, 1);
private final Vec4 center;
private final double radius;
/**
* Creates a sphere that completely contains a set of points.
*
* @param points the <code>Vec4</code>s to be enclosed by the new Sphere
* @return a <code>Sphere</code> encompassing the given array of <code>Vec4</code>s
* @throws IllegalArgumentException if <code>points</code> is null or empty
*/
public static Sphere createBoundingSphere(Vec4 points[])
{
if (points == null)
{
String message = Logging.getMessage("nullValue.PointsArrayIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (points.length < 1)
{
String message = Logging.getMessage("Geom.Sphere.NoPointsSpecified");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Creates the sphere around the axis aligned bounding box of the input points.
Vec4[] extrema = composeExtrema(points);
Vec4 center = new Vec4(
(extrema[0].x + extrema[1].x) / 2.0,
(extrema[0].y + extrema[1].y) / 2.0,
(extrema[0].z + extrema[1].z) / 2.0);
double radius = extrema[0].distanceTo3(extrema[1]) / 2.0;
return new Sphere(center, radius);
}
/**
* Creates a new <code>Sphere</code> from a given center and radius. <code>radius</code> must be positive (that is,
* greater than zero), and <code>center</code> may not be null.
*
* @param center the center of the new sphere
* @param radius the radius of the new sphere
* @throws IllegalArgumentException if <code>center</code> is null or if <code>radius</code> is non-positive
*/
public Sphere(Vec4 center, double radius)
{
if (center == null)
{
String message = Logging.getMessage("nullValue.CenterIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (radius <= 0)
{
String message = Logging.getMessage("Geom.Sphere.RadiusIsZeroOrNegative", radius);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.center = center;
this.radius = radius;
}
/**
* Calculate the extrema of a given array of <code>Vec4</code>s. The resulting array is always of length 2, with the
* first element containing the minimum extremum, and the second containing the maximum. The minimum extremum is
* composed by taking the smallest x, y and z values from all the <code>Vec4</code>s in the array. These values are
* not necessarily taken from the same <code>Vec4</code>. The maximum extrema is composed in the same fashion.
*
* @param points any array of <code>Vec4</code>s
* @return a array with length of 2, comprising the most extreme values in the given array
* @throws IllegalArgumentException if <code>points</code> is null
*/
public static Vec4[] composeExtrema(Vec4 points[])
{
if (points == null)
{
String message = Logging.getMessage("nullValue.PointsArrayIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (points.length == 0)
return null;
double xmin = points[0].x;
double ymin = points[0].y;
double zmin = points[0].z;
double xmax = xmin;
double ymax = ymin;
double zmax = zmin;
for (int i = 1; i < points.length; i++)
{
double x = points[i].x;
if (x > xmax)
{
xmax = x;
}
else if (x < xmin)
{
xmin = x;
}
double y = points[i].y;
if (y > ymax)
{
ymax = y;
}
else if (y < ymin)
{
ymin = y;
}
double z = points[i].z;
if (z > zmax)
{
zmax = z;
}
else if (z < zmin)
{
zmin = z;
}
}
return new Vec4[] {new Vec4(xmin, ymin, zmin), new Vec4(xmax, ymax, zmax)};
}
/**
* Obtains the radius of this <code>Sphere</code>. The radus is the distance from the center to the surface. If an
* object's distance to this sphere's center is less than or equal to the radius, then that object is at least
* partially within this <code>Sphere</code>.
*
* @return the radius of this sphere
*/
public final double getRadius()
{
return this.radius;
}
/**
* Obtains the diameter of this <code>Sphere</code>. The diameter is twice the radius.
*
* @return the diameter of this <code>Sphere</code>
*/
public final double getDiameter()
{
return 2 * this.radius;
}
/**
* Obtains the center of this <code>Sphere</code>.
*
* @return the <code>Vec4</code> situated at the center of this <code>Sphere</code>
*/
public final Vec4 getCenter()
{
return this.center;
}
/**
* Obtains the intersections of this sphere with a line. The returned array may be either null or of zero length if
* no intersections are discovered. It does not contain null elements and will have a size of 2 at most. Tangential
* intersections are marked as such. <code>line</code> is considered to have infinite length in both directions.
*
* @param line the <code>Line</code> with which to intersect this <code>Sphere</code>
* @return an array containing all the intersections of this <code>Sphere</code> and <code>line</code>
* @throws IllegalArgumentException if <code>line</code> is null
*/
public final Intersection[] intersect(Line line)
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double a = line.getDirection().getLengthSquared3();
double b = 2 * line.selfDot();
double c = line.getOrigin().getLengthSquared3() - this.radius * this.radius;
double discriminant = Sphere.discriminant(a, b, c);
if (discriminant < 0)
return null;
double discriminantRoot = Math.sqrt(discriminant);
if (discriminant == 0)
{
Vec4 p = line.getPointAt((-b - discriminantRoot) / (2 * a));
return new Intersection[] {new Intersection(p, true)};
}
else // (discriminant > 0)
{
Vec4 near = line.getPointAt((-b - discriminantRoot) / (2 * a));
Vec4 far = line.getPointAt((-b + discriminantRoot) / (2 * a));
return new Intersection[] {new Intersection(near, false), new Intersection(far, false)};
}
}
/**
* Calculates a discriminant. A discriminant is useful to determine the number of roots to a quadratic equation. If
* the discriminant is less than zero, there are no roots. If it equals zero, there is one root. If it is greater
* than zero, there are two roots.
*
* @param a the coefficient of the second order pronumeral
* @param b the coefficient of the first order pronumeral
* @param c the constant parameter in the quadratic equation
* @return the discriminant "b squared minus 4ac"
*/
private static double discriminant(double a, double b, double c)
{
return b * b - 4 * a * c;
}
/**
* tests for intersetion with a <code>Frustum</code>. This operation is commutative, so
* <code>someSphere.intersects(frustum)</code> and <code>frustum.intersects(someSphere)</code> are equivalent.
*
* @param frustum the <code>Frustum</code> with which to test for intersection
* @return true if either <code>frustum</code> or this <code>Sphere</code> wholly or partially contain the other,
* false otherwise.
* @throws IllegalArgumentException if <code>frustum</code> is null
*/
public boolean intersects(Frustum frustum)
{
if (frustum == null)
{
String message = Logging.getMessage("nullValue.FrustumIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return frustum.intersects(this);
}
/**
* Tests for intersection with a <code>Line</code>.
*
* @param line the <code>Line</code> with which to test for intersection
* @return true if <code>line</code> intersects or makes a tangent with the surface of this <code>Sphere</code>
* @throws IllegalArgumentException if <code>line</code> is null
*/
public boolean intersects(Line line)
{
if (line == null)
{
String msg = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return line.distanceTo(this.center) <= this.radius;
}
/**
* Tests for intersection with a <code>Plane</code>.
*
* @param plane the <code>Plane</code> with which to test for intersection
* @return true if <code>plane</code> intersects or makes a tangent with the surface of this <code>Sphere</code>
* @throws IllegalArgumentException if <code>plane</code> is null
*/
public boolean intersects(Plane plane)
{
if (plane == null)
{
String msg = Logging.getMessage("nullValue.PlaneIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
double dq1 = plane.dot(this.center);
return dq1 <= this.radius;
}
/**
* Causes this <code>Sphere</code> to render itself using the <code>DrawContext</code> provided. <code>dc</code> may
* not be null.
*
* @param dc the <code>DrawContext</code> to be used
* @throws IllegalArgumentException if <code>dc</code> is null
*/
public void render(DrawContext dc)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
javax.media.opengl.GL gl = dc.getGL();
gl.glPushAttrib(javax.media.opengl.GL.GL_TEXTURE_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
| javax.media.opengl.GL.GL_CURRENT_BIT);
gl.glDisable(javax.media.opengl.GL.GL_TEXTURE_2D);
gl.glColor3d(1, 1, 0);
gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
gl.glPushMatrix();
gl.glTranslated(this.center.x, this.center.y, this.center.z);
javax.media.opengl.glu.GLUquadric quadric = dc.getGLU().gluNewQuadric();
dc.getGLU().gluQuadricDrawStyle(quadric, javax.media.opengl.glu.GLU.GLU_LINE);
dc.getGLU().gluSphere(quadric, this.radius, 10, 10);
gl.glPopMatrix();
dc.getGLU().gluDeleteQuadric(quadric);
gl.glPopAttrib();
}
@Override
public String toString()
{
return "Sphere: center = " + this.center.toString() + " radius = " + Double.toString(this.radius);
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final gov.nasa.worldwind.geom.Sphere sphere = (gov.nasa.worldwind.geom.Sphere) o;
if (Double.compare(sphere.radius, radius) != 0)
return false;
//noinspection RedundantIfStatement
if (!center.equals(sphere.center))
return false;
return true;
}
@Override
public int hashCode()
{
int result;
long temp;
result = center.hashCode();
temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
result = 29 * result + (int) (temp ^ (temp >>> 32));
return result;
}
// public final boolean intersects(Line line)
// {
// if (line == null)
// {
// String message = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
// WorldWind.logger().logger(Level.SEVERE, message);
// throw new IllegalArgumentException(message);
// }
//
// double a = line.getDirection().getLengthSquared();
// double b = 2 * line.selfDot();
// double c = line.getOrigin().selfDot() - this.radius * this.radius;
//
// double discriminant = Sphere.discriminant(a, b, c);
// if (discriminant < 0)
// {
// return false;
// }
//
// return true;
//
// }
}