/*
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;
import javax.media.opengl.GL;
import javax.media.opengl.glu.*;
/**
* Represents a geometric cylinder. <code>Cylinder</code>s are immutable.
*
* @author Tom Gaskins
* @version $Id: Cylinder.java 2471 2007-07-31 21:50:57Z tgaskins $
*/
public class Cylinder implements Extent, Renderable
{
private final Vec4 bottomCenter; // point at center of cylinder base
private final Vec4 topCenter; // point at center of cylinder top
private final Vec4 axisUnitDirection; // axis as unit vector from bottomCenter to topCenter
private final double cylinderRadius;
private final double cylinderHeight;
/**
* Create a <code>Cylinder</code> from two points and a radius. Does not accept null arguments.
*
* @param bottomCenter represents the centrepoint of the base disc of the <code>Cylinder</code>
* @param topCenter represents the centrepoint of the top disc of the <code>Cylinder</code>
* @param cylinderRadius the radius of the <code>Cylinder</code>
* @throws IllegalArgumentException if either the top or bottom point is null
*/
public Cylinder(Vec4 bottomCenter, Vec4 topCenter, double cylinderRadius)
{
if (bottomCenter == null || topCenter == null)
{
String message = Logging.getMessage("nullValue.EndPointIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (cylinderRadius <= 0)
{
String message = Logging.getMessage("Geom.Cylinder.RadiusIsZeroOrNegative", cylinderRadius);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.bottomCenter = bottomCenter;
this.topCenter = topCenter;
this.cylinderHeight = this.bottomCenter.distanceTo3(this.topCenter);
this.cylinderRadius = cylinderRadius;
this.axisUnitDirection = this.topCenter.subtract3(this.bottomCenter).normalize3();
}
public Vec4 getAxisUnitDirection()
{
return axisUnitDirection;
}
public Vec4 getBottomCenter()
{
return bottomCenter;
}
public Vec4 getTopCenter()
{
return topCenter;
}
public double getCylinderRadius()
{
return cylinderRadius;
}
public double getCylinderHeight()
{
return cylinderHeight;
}
public String toString()
{
return this.cylinderRadius + ", " + this.bottomCenter.toString() + ", " + this.topCenter.toString() + ", "
+ this.axisUnitDirection.toString();
}
public Intersection[] intersect(Line line) // TODO: test this method
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Vec4 ld = line.getDirection();
Vec4 lo = line.getOrigin();
double a = ld.x * ld.x + ld.y * ld.y;
double b = 2 * (lo.x * ld.x + lo.y * ld.y);
double c = lo.x * lo.x + lo.y * lo.y - this.cylinderRadius * this.cylinderRadius;
double discriminant = Cylinder.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));
boolean n = false, f = false;
boolean nTangent = false, fTangent = false;
if (near.z >= 0 && near.z <= this.getHeight())
{
n = true;
nTangent = near.z == 0;
}
if (far.z >= 0 && far.z <= this.getHeight())
{
f = true;
fTangent = far.z == 0;
}
// TODO: Test for intersection with planes at cylinder's top and bottom
Intersection[] intersections = null;
if (n && f)
intersections = new Intersection[] {new Intersection(near, nTangent), new Intersection(far, fTangent)};
else if (n)
intersections = new Intersection[] {new Intersection(near, nTangent)};
else if (f)
intersections = new Intersection[] {new Intersection(far, fTangent)};
return intersections;
}
}
public boolean intersects(Line line)
{
if (line == null)
{
String message = Logging.getMessage("nullValue.LineIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Vec4 ld = line.getDirection();
Vec4 lo = line.getOrigin();
double a = ld.x * ld.x + ld.y * ld.y;
double b = 2 * (lo.x * ld.x + lo.y * ld.y);
double c = lo.x * lo.x + lo.y * lo.y - this.cylinderRadius * this.cylinderRadius;
double discriminant = Cylinder.discriminant(a, b, c);
return discriminant >= 0;
}
static private double discriminant(double a, double b, double c)
{
return b * b - 4 * a * c;
}
private double intersectsAt(Plane plane, double effectiveRadius, double parameter)
{
// Test the distance from the first cylinder end-point.
double dq1 = plane.dot(this.bottomCenter);
boolean bq1 = dq1 <= -effectiveRadius;
// Test the distance from the possibly reduced second cylinder end-point.
Vec4 newTop;
if (parameter < 1)
newTop = this.bottomCenter.add3(this.topCenter.subtract3(this.bottomCenter).multiply3(parameter));
else
newTop = this.topCenter;
double dq2 = plane.dot(newTop);
boolean bq2 = dq2 <= -effectiveRadius;
if (bq1 && bq2) // both <= effective radius; cylinder is on negative side of plane
return -1;
if (bq1 == bq2) // both >= effective radius; can't draw any conclusions
return parameter;
// Compute and return the parameter value at which the plane intersects the cylinder's axis.
return effectiveRadius + plane.dot(this.bottomCenter)
/ plane.getNormal().dot3(this.bottomCenter.subtract3(newTop));
}
private double getEffectiveRadius(Plane plane)
{
// Determine the effective radius of the cylinder axis relative to the plane.
double dot = plane.getNormal().dot3(this.axisUnitDirection);
double scale = 1d - dot * dot;
if (scale <= 0)
return 0;
else
return this.cylinderRadius * Math.sqrt(scale);
}
public boolean intersects(Plane plane)
{
if (plane == null)
{
String message = Logging.getMessage("nullValue.PlaneIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double effectiveRadius = this.getEffectiveRadius(plane);
double intersectionPoint = this.intersectsAt(plane, effectiveRadius, 1d);
return intersectionPoint >= 0;
}
public boolean intersects(Frustum frustum)
{
if (frustum == null)
{
String message = Logging.getMessage("nullValue.FrustumIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double intersectionPoint;
double effectiveRadius = this.getEffectiveRadius(frustum.getNear());
intersectionPoint = this.intersectsAt(frustum.getNear(), effectiveRadius, 1d);
if (intersectionPoint < 0)
return false;
// Near and far have the same effective radius.
intersectionPoint = this.intersectsAt(frustum.getFar(), effectiveRadius, intersectionPoint);
if (intersectionPoint < 0)
return false;
effectiveRadius = this.getEffectiveRadius(frustum.getLeft());
intersectionPoint = this.intersectsAt(frustum.getLeft(), effectiveRadius, intersectionPoint);
if (intersectionPoint < 0)
return false;
effectiveRadius = this.getEffectiveRadius(frustum.getRight());
intersectionPoint = this.intersectsAt(frustum.getRight(), effectiveRadius, intersectionPoint);
if (intersectionPoint < 0)
return false;
effectiveRadius = this.getEffectiveRadius(frustum.getTop());
intersectionPoint = this.intersectsAt(frustum.getTop(), effectiveRadius, intersectionPoint);
if (intersectionPoint < 0)
return false;
effectiveRadius = this.getEffectiveRadius(frustum.getBottom());
intersectionPoint = this.intersectsAt(frustum.getBottom(), effectiveRadius, intersectionPoint);
return intersectionPoint >= 0;
}
public Vec4 getCenter()
{
Vec4 b = this.bottomCenter;
Vec4 t = this.topCenter;
return new Vec4(
(b.x + t.x) / 2.0,
(b.y + t.y) / 2.0,
(b.z + t.z) / 2.0);
}
public double getDiameter()
{
return 2 * this.getRadius();
}
public double getRadius()
{
// return the radius of the enclosing sphere
double halfHeight = this.bottomCenter.distanceTo3(this.topCenter) / 2.0;
return Math.sqrt(halfHeight * halfHeight + this.cylinderRadius * this.cylinderRadius);
}
/**
* Obtain the height of this <code>Cylinder</code>.
*
* @return the distance between the bottom and top of this <code>Cylinder</code>
*/
public final double getHeight()
{
return this.cylinderHeight;
}
public void render(DrawContext dc)
{
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Vec4 center = this.getCenter();
PolarPoint p = PolarPoint.fromCartesian(center);
javax.media.opengl.GL gl = dc.getGL();
gl.glPushAttrib(GL.GL_ENABLE_BIT | GL.GL_TRANSFORM_BIT);
gl.glBegin(javax.media.opengl.GL.GL_LINES);
gl.glVertex3d(this.bottomCenter.x, this.bottomCenter.y, this.bottomCenter.z);
gl.glVertex3d(this.topCenter.x, this.topCenter.y, this.topCenter.z);
gl.glEnd();
gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST);
gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
gl.glPushMatrix();
gl.glTranslated(this.bottomCenter.x, this.bottomCenter.y, this.bottomCenter.z);
dc.getGL().glRotated(p.getLongitude().getDegrees(), 0, 1, 0);
dc.getGL().glRotated(Math.abs(p.getLatitude().getDegrees()), Math.signum(p.getLatitude().getDegrees()) * -1,
0, 0);
GLUquadric quadric = dc.getGLU().gluNewQuadric();
dc.getGLU().gluQuadricDrawStyle(quadric, GLU.GLU_LINE);
dc.getGLU().gluCylinder(quadric, this.cylinderRadius, this.cylinderRadius, this.cylinderHeight, 30, 30);
dc.getGLU().gluDeleteQuadric(quadric);
gl.glPopMatrix();
gl.glPopAttrib();
}
}