All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.geom.Cylinder Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 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.View;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import java.util.*;

/**
 * Represents a geometric cylinder, most often used as a bounding volume. Cylinders are immutable.
 *
 * @author Tom Gaskins
 * @version $Id: Cylinder.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class Cylinder implements Extent, Renderable
{
    protected final Vec4 bottomCenter; // point at center of cylinder base
    protected final Vec4 topCenter; // point at center of cylinder top
    protected final Vec4 axisUnitDirection; // axis as unit vector from bottomCenter to topCenter
    protected final double cylinderRadius;
    protected final double cylinderHeight;

    /**
     * Create a Cylinder from two points and a radius.
     *
     * @param bottomCenter   the center point of of the cylinder's base.
     * @param topCenter      the center point of the cylinders top.
     * @param cylinderRadius the cylinder's radius.
     *
     * @throws IllegalArgumentException if the radius is zero or the top or bottom point is null or they are
     *                                  coincident.
     */
    public Cylinder(Vec4 bottomCenter, Vec4 topCenter, double cylinderRadius)
    {
        if (bottomCenter == null || topCenter == null || bottomCenter.equals(topCenter))
        {
            String message = Logging.getMessage(
                bottomCenter == null || topCenter == null ? "nullValue.EndPointIsNull" : "generic.EndPointsCoincident");

            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);
        }

        // Convert the bottom center and top center points to points in four-dimensional homogeneous coordinates to
        // ensure that their w-coordinates are 1. Cylinder's intersection tests compute a dot product between these
        // points and each frustum plane, which depends on a w-coordinate of 1. We convert each point at construction to
        // avoid the additional overhead of converting them during every intersection test.
        this.bottomCenter = bottomCenter.toHomogeneousPoint3();
        this.topCenter = topCenter.toHomogeneousPoint3();
        this.cylinderHeight = this.bottomCenter.distanceTo3(this.topCenter);
        this.cylinderRadius = cylinderRadius;
        this.axisUnitDirection = this.topCenter.subtract3(this.bottomCenter).normalize3();
    }

    /**
     * Create a Cylinder from two points, a radius and an axis direction. Provided for use when unit axis is know and
     * computation of it can be avoided.
     *
     * @param bottomCenter   the center point of of the cylinder's base.
     * @param topCenter      the center point of the cylinders top.
     * @param cylinderRadius the cylinder's radius.
     * @param unitDirection  the unit-length axis of the cylinder.
     *
     * @throws IllegalArgumentException if the radius is zero or the top or bottom point is null or they are
     *                                  coincident.
     */
    public Cylinder(Vec4 bottomCenter, Vec4 topCenter, double cylinderRadius, Vec4 unitDirection)
    {
        if (bottomCenter == null || topCenter == null || bottomCenter.equals(topCenter))
        {
            String message = Logging.getMessage(
                bottomCenter == null || topCenter == null ? "nullValue.EndPointIsNull" : "generic.EndPointsCoincident");

            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);
        }

        // Convert the bottom center and top center points to points in four-dimensional homogeneous coordinates to
        // ensure that their w-coordinates are 1. Cylinder's intersection tests compute a dot product between these
        // points and each frustum plane, which depends on a w-coordinate of 1. We convert each point at construction to
        // avoid the additional overhead of converting them during every intersection test.
        this.bottomCenter = bottomCenter.toHomogeneousPoint3();
        this.topCenter = topCenter.toHomogeneousPoint3();
        this.cylinderHeight = this.bottomCenter.distanceTo3(this.topCenter);
        this.cylinderRadius = cylinderRadius;
        this.axisUnitDirection = unitDirection;
    }

    /**
     * Returns the unit-length axis of this cylinder.
     *
     * @return the unit-length axis of this cylinder.
     */
    public Vec4 getAxisUnitDirection()
    {
        return axisUnitDirection;
    }

    /**
     * Returns the this cylinder's bottom-center point.
     *
     * @return this cylinder's bottom-center point.
     */
    public Vec4 getBottomCenter()
    {
        return bottomCenter;
    }

    /**
     * Returns the this cylinder's top-center point.
     *
     * @return this cylinder's top-center point.
     */
    public Vec4 getTopCenter()
    {
        return topCenter;
    }

    /**
     * Returns this cylinder's radius.
     *
     * @return this cylinder's radius.
     */
    public double getCylinderRadius()
    {
        return cylinderRadius;
    }

    /**
     * Returns this cylinder's height.
     *
     * @return this cylinder's height.
     */
    public double getCylinderHeight()
    {
        return cylinderHeight;
    }

    /**
     * Return this cylinder's center point.
     *
     * @return this cylinder's center point.
     */
    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);
    }

    /** {@inheritDoc} */
    public double getDiameter()
    {
        return 2 * this.getRadius();
    }

    /** {@inheritDoc} */
    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);
    }

    /**
     * Return this cylinder's volume.
     *
     * @return this cylinder's volume.
     */
    public double getVolume()
    {
        return Math.PI * this.cylinderRadius * this.cylinderRadius * this.cylinderHeight;
    }

    /**
     * Compute a bounding cylinder for a collection of points.
     *
     * @param points the points to compute a bounding cylinder for.
     *
     * @return a cylinder bounding all the points. The axis of the cylinder is the longest principal axis of the
     *         collection. (See {@link WWMath#computePrincipalAxes(Iterable)}.
     *
     * @throws IllegalArgumentException if the point list is null or empty.
     * @see #computeVerticalBoundingCylinder(gov.nasa.worldwind.globes.Globe, double, Sector)
     */
    public static Cylinder computeBoundingCylinder(Iterable points)
    {
        if (points == null)
        {
            String message = Logging.getMessage("nullValue.PointListIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Vec4[] axes = WWMath.computePrincipalAxes(points);
        if (axes == null)
        {
            String message = Logging.getMessage("generic.ListIsEmpty");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Vec4 r = axes[0];
        Vec4 s = axes[1];

        List sPlanePoints = new ArrayList();
        double minDotR = Double.MAX_VALUE;
        double maxDotR = -minDotR;

        for (Vec4 p : points)
        {
            double pdr = p.dot3(r);
            sPlanePoints.add(p.subtract3(r.multiply3(p.dot3(r))));

            if (pdr < minDotR)
                minDotR = pdr;
            if (pdr > maxDotR)
                maxDotR = pdr;
        }

        Vec4 minPoint = sPlanePoints.get(0);
        Vec4 maxPoint = minPoint;
        double minDotS = Double.MAX_VALUE;
        double maxDotS = -minDotS;
        for (Vec4 p : sPlanePoints)
        {
            double d = p.dot3(s);
            if (d < minDotS)
            {
                minPoint = p;
                minDotS = d;
            }
            if (d > maxDotS)
            {
                maxPoint = p;
                maxDotS = d;
            }
        }

        Vec4 center = minPoint.add3(maxPoint).divide3(2);
        double radius = center.distanceTo3(minPoint);

        for (Vec4 h : sPlanePoints)
        {
            Vec4 hq = h.subtract3(center);
            double d = hq.getLength3();
            if (d > radius)
            {
                Vec4 g = center.subtract3(hq.normalize3().multiply3(radius));
                center = g.add3(h).divide3(2);
                radius = d;
            }
        }

        Vec4 bottomCenter = center.add3(r.multiply3(minDotR));
        Vec4 topCenter = center.add3((r.multiply3(maxDotR)));

        if (radius == 0)
            radius = 1;

        if (bottomCenter.equals(topCenter))
            topCenter = bottomCenter.add3(new Vec4(1, 0, 0));

        return new Cylinder(bottomCenter, topCenter, radius);
    }

    /** {@inheritDoc} */
    public Intersection[] intersect(Line line)
    {
        if (line == null)
        {
            String message = Logging.getMessage("nullValue.LineIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double[] tVals = new double[2];
        if (!intcyl(line.getOrigin(), line.getDirection(), this.bottomCenter, this.axisUnitDirection,
            this.cylinderRadius, tVals))
            return null;

        if (!clipcyl(line.getOrigin(), line.getDirection(), this.bottomCenter, this.topCenter,
            this.axisUnitDirection, tVals))
            return null;

        if (!Double.isInfinite(tVals[0]) && !Double.isInfinite(tVals[1]) && tVals[0] >= 0.0 && tVals[1] >= 0.0)
            return new Intersection[] {new Intersection(line.getPointAt(tVals[0]), false),
                new Intersection(line.getPointAt(tVals[1]), false)};
        if (!Double.isInfinite(tVals[0]) && tVals[0] >= 0.0)
            return new Intersection[] {new Intersection(line.getPointAt(tVals[0]), false)};
        if (!Double.isInfinite(tVals[1]) && tVals[1] >= 0.0)
            return new Intersection[] {new Intersection(line.getPointAt(tVals[1]), false)};
        return null;
    }

    /** {@inheritDoc} */
    public boolean intersects(Line line)
    {
        if (line == null)
        {
            String message = Logging.getMessage("nullValue.LineIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return intersect(line) != null;
    }

    // Taken from "Graphics Gems IV", Section V.2, page 356.

    protected boolean intcyl(Vec4 raybase, Vec4 raycos, Vec4 base, Vec4 axis, double radius, double[] tVals)
    {
        boolean hit; // True if ray intersects cyl
        Vec4 RC; // Ray base to cylinder base
        double d; // Shortest distance between the ray and the cylinder
        double t, s; // Distances along the ray
        Vec4 n, D, O;
        double ln;

        RC = raybase.subtract3(base);
        n = raycos.cross3(axis);

        // Ray is parallel to the cylinder's axis.
        if ((ln = n.getLength3()) == 0.0)
        {
            d = RC.dot3(axis);
            D = RC.subtract3(axis.multiply3(d));
            d = D.getLength3();
            tVals[0] = Double.NEGATIVE_INFINITY;
            tVals[1] = Double.POSITIVE_INFINITY;
            // True if ray is in cylinder.
            return d <= radius;
        }

        n = n.normalize3();
        d = Math.abs(RC.dot3(n)); // Shortest distance.
        hit = (d <= radius);

        // If ray hits cylinder.
        if (hit)
        {
            O = RC.cross3(axis);
            t = -O.dot3(n) / ln;
            O = n.cross3(axis);
            O = O.normalize3();
            s = Math.abs(Math.sqrt(radius * radius - d * d) / raycos.dot3(O));
            tVals[0] = t - s; // Entering distance.
            tVals[1] = t + s; // Exiting distance.
        }

        return hit;
    }

    // Taken from "Graphics Gems IV", Section V.2, page 356.

    protected boolean clipcyl(Vec4 raybase, Vec4 raycos, Vec4 bot, Vec4 top, Vec4 axis, double[] tVals)
    {
        double dc, dwb, dwt, tb, tt;
        double in, out; // Object intersection distances.

        in = tVals[0];
        out = tVals[1];

        dc = axis.dot3(raycos);
        dwb = axis.dot3(raybase) - axis.dot3(bot);
        dwt = axis.dot3(raybase) - axis.dot3(top);

        // Ray is parallel to the cylinder end-caps.
        if (dc == 0.0)
        {
            if (dwb <= 0.0)
                return false;
            if (dwt >= 0.0)
                return false;
        }
        else
        {
            // Intersect the ray with the bottom end-cap.
            tb = -dwb / dc;
            // Intersect the ray with the top end-cap.
            tt = -dwt / dc;

            // Bottom is near cap, top is far cap.
            if (dc >= 0.0)
            {
                if (tb > out)
                    return false;
                if (tt < in)
                    return false;
                if (tb > in && tb < out)
                    in = tb;
                if (tt > in && tt < out)
                    out = tt;
            }
            // Bottom is far cap, top is near cap.
            else
            {
                if (tb < in)
                    return false;
                if (tt > out)
                    return false;
                if (tb > in && tb < out)
                    out = tb;
                if (tt > in && tt < out)
                    in = tt;
            }
        }

        tVals[0] = in;
        tVals[1] = out;
        return in < out;
    }

    protected double intersects(Plane plane, double effectiveRadius)
    {
        // Test the distance from the first cylinder end-point. Assumes that bottomCenter's w-coordinate is 1.
        double dq1 = plane.dot(this.bottomCenter);
        boolean bq1 = dq1 <= -effectiveRadius;

        // Test the distance from the top of the cylinder. Assumes that topCenter's w-coordinate is 1.
        double dq2 = plane.dot(this.topCenter);
        boolean bq2 = dq2 <= -effectiveRadius;

        if (bq1 && bq2) // both beyond effective radius; cylinder is on negative side of plane
            return -1;

        if (bq1 == bq2) // both within effective radius; can't draw any conclusions
            return 0;

        return 1; // Cylinder almost certainly intersects
    }

    protected double intersectsAt(Plane plane, double effectiveRadius, Vec4[] endpoints)
    {
        // Test the distance from the first end-point. Assumes that the first end-point's w-coordinate is 1.
        double dq1 = plane.dot(endpoints[0]);
        boolean bq1 = dq1 <= -effectiveRadius;

        // Test the distance from the possibly reduced second cylinder end-point. Assumes that the second end-point's
        // w-coordinate is 1.
        double dq2 = plane.dot(endpoints[1]);
        boolean bq2 = dq2 <= -effectiveRadius;

        if (bq1 && bq2) // endpoints more distant from plane than effective radius; cylinder is on neg. side of plane
            return -1;

        if (bq1 == bq2) // endpoints less distant from plane than effective radius; can't draw any conclusions
            return 0;

        // Compute and return the endpoints of the cylinder on the positive side of the plane.
        double t = (effectiveRadius + dq1) / plane.getNormal().dot3(endpoints[0].subtract3(endpoints[1]));

        Vec4 newEndPoint = endpoints[0].add3(endpoints[1].subtract3(endpoints[0]).multiply3(t));
        if (bq1) // Truncate the lower end of the cylinder
            endpoints[0] = newEndPoint;
        else // Truncate the upper end of the cylinder
            endpoints[1] = newEndPoint;

        return t;
    }

    /** {@inheritDoc} */
    public double getEffectiveRadius(Plane plane)
    {
        if (plane == null)
            return 0;

        // 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);
    }

    /** {@inheritDoc} */
    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);
        return this.intersects(plane, effectiveRadius) >= 0;
    }

    /** {@inheritDoc} */
    public boolean intersects(Frustum frustum)
    {
        if (frustum == null)
        {
            String message = Logging.getMessage("nullValue.FrustumIsNull");

            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double intersectionPoint;
        Vec4[] endPoints = new Vec4[] {this.bottomCenter, this.topCenter};

        double effectiveRadius = this.getEffectiveRadius(frustum.getNear());
        intersectionPoint = this.intersectsAt(frustum.getNear(), effectiveRadius, endPoints);
        if (intersectionPoint < 0)
            return false;

        // Near and far have the same effective radius.
        intersectionPoint = this.intersectsAt(frustum.getFar(), effectiveRadius, endPoints);
        if (intersectionPoint < 0)
            return false;

        effectiveRadius = this.getEffectiveRadius(frustum.getLeft());
        intersectionPoint = this.intersectsAt(frustum.getLeft(), effectiveRadius, endPoints);
        if (intersectionPoint < 0)
            return false;

        effectiveRadius = this.getEffectiveRadius(frustum.getRight());
        intersectionPoint = this.intersectsAt(frustum.getRight(), effectiveRadius, endPoints);
        if (intersectionPoint < 0)
            return false;

        effectiveRadius = this.getEffectiveRadius(frustum.getTop());
        intersectionPoint = this.intersectsAt(frustum.getTop(), effectiveRadius, endPoints);
        if (intersectionPoint < 0)
            return false;

        effectiveRadius = this.getEffectiveRadius(frustum.getBottom());
        intersectionPoint = this.intersectsAt(frustum.getBottom(), effectiveRadius, endPoints);
        return intersectionPoint >= 0;
    }

    /** {@inheritDoc} */
    public double getProjectedArea(View view)
    {
        if (view == null)
        {
            String message = Logging.getMessage("nullValue.ViewIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // TODO: compute a more exact projected screen area for Cylinder.
        return WWMath.computeSphereProjectedArea(view, this.getCenter(), this.getRadius());
    }

    /**
     * Returns a cylinder that minimally surrounds the specified minimum and maximum elevations in the sector at a
     * specified vertical exaggeration, and is oriented such that the cylinder axis is perpendicular to the globe's
     * surface.
     *
     * @param globe                The globe associated with the sector.
     * @param verticalExaggeration the vertical exaggeration to apply to the minimum and maximum elevations when
     *                             computing the cylinder.
     * @param sector               the sector to return the bounding cylinder for.
     *
     * @return The minimal bounding cylinder in Cartesian coordinates.
     *
     * @throws IllegalArgumentException if sector is null
     * @see #computeBoundingCylinder(Iterable)
     */
    static public Cylinder computeVerticalBoundingCylinder(Globe globe, double verticalExaggeration, Sector sector)
    {
        if (globe == null)
        {
            String msg = Logging.getMessage("nullValue.GlobeIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double[] minAndMaxElevations = globe.getMinAndMaxElevations(sector);
        return computeVerticalBoundingCylinder(globe, verticalExaggeration, sector,
            minAndMaxElevations[0], minAndMaxElevations[1]);
    }

    /**
     * Returns a cylinder that minimally surrounds the specified minimum and maximum elevations in the sector at a
     * specified vertical exaggeration, and is oriented such that the cylinder axis is perpendicular to the globe's
     * surface.
     *
     * @param globe                The globe associated with the sector.
     * @param verticalExaggeration the vertical exaggeration to apply to the minimum and maximum elevations when
     *                             computing the cylinder.
     * @param sector               the sector to return the bounding cylinder for.
     * @param minElevation         the minimum elevation of the bounding cylinder.
     * @param maxElevation         the maximum elevation of the bounding cylinder.
     *
     * @return The minimal bounding cylinder in Cartesian coordinates.
     *
     * @throws IllegalArgumentException if sector is null
     * @see #computeBoundingCylinder(Iterable)
     */
    public static Cylinder computeVerticalBoundingCylinder(Globe globe, double verticalExaggeration, Sector sector,
        double minElevation, double maxElevation)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Compute the exaggerated minimum and maximum heights.
        double minHeight = minElevation * verticalExaggeration;
        double maxHeight = maxElevation * verticalExaggeration;

        if (minHeight == maxHeight)
            maxHeight = minHeight + 1; // ensure the top and bottom of the cylinder won't be coincident

        // If the sector spans both poles in latitude, or spans greater than 180 degrees in longitude, we cannot use the
        // sector's Cartesian quadrilateral to compute a bounding cylinde. This is because the quadrilateral is either
        // smaller than the geometry defined by the sector (when deltaLon >= 180), or the quadrilateral degenerates to
        // two points (when deltaLat >= 180). So we compute a bounging cylinder that spans the equator and covers the
        // sector's latitude range. In some cases this cylinder may be too large, but we're typically not interested
        // in culling these cylinders since the sector will span most of the globe.
        if (sector.getDeltaLatDegrees() >= 180d || sector.getDeltaLonDegrees() >= 180d)
        {
            return computeVerticalBoundsFromSectorLatitudeRange(globe, sector, minHeight, maxHeight);
        }
        // Otherwise, create a standard bounding cylinder that minimally surrounds the specified sector and elevations.
        else
        {
            return computeVerticalBoundsFromSectorQuadrilateral(globe, sector, minHeight, maxHeight);
        }
    }

    /**
     * Compute the Cylinder that surrounds the equator, and has height defined by the sector's minumum and maximum
     * latitudes (including maxHeight).
     *
     * @param globe     The globe associated with the sector.
     * @param sector    the sector to return the bounding cylinder for.
     * @param minHeight the minimum height to include in the bounding cylinder.
     * @param maxHeight the maximum height to include in the bounding cylinder.
     *
     * @return the minimal bounding cylinder in Cartesianl coordinates.
     *
     * @throws IllegalArgumentException if sector is null
     */
    @SuppressWarnings({"UnusedDeclaration"})
    protected static Cylinder computeVerticalBoundsFromSectorLatitudeRange(Globe globe, Sector sector, double minHeight,
        double maxHeight)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        Vec4 centerPoint = Vec4.ZERO;
        Vec4 axis = Vec4.UNIT_Y;
        double radius = globe.getEquatorialRadius() + maxHeight;

        // Compute the sector's lowest projection along the cylinder axis. This will be a point of minimum latitude
        // with maxHeight.
        Vec4 extremePoint = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(),
            maxHeight);
        double minProj = extremePoint.subtract3(centerPoint).dot3(axis);
        // Compute the sector's lowest highest along the cylinder axis. This will be a point of maximum latitude
        // with maxHeight.
        extremePoint = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight);
        double maxProj = extremePoint.subtract3(centerPoint).dot3(axis);

        Vec4 bottomCenterPoint = axis.multiply3(minProj).add3(centerPoint);
        Vec4 topCenterPoint = axis.multiply3(maxProj).add3(centerPoint);

        if (radius == 0)
            radius = 1;

        if (bottomCenterPoint.equals(topCenterPoint))
            topCenterPoint = bottomCenterPoint.add3(new Vec4(1, 0, 0));

        return new Cylinder(bottomCenterPoint, topCenterPoint, radius);
    }

    /**
     * Returns a cylinder that minimally surrounds the specified height range in the sector.
     *
     * @param globe     The globe associated with the sector.
     * @param sector    the sector to return the bounding cylinder for.
     * @param minHeight the minimum height to include in the bounding cylinder.
     * @param maxHeight the maximum height to include in the bounding cylinder.
     *
     * @return The minimal bounding cylinder in Cartesian coordinates.
     *
     * @throws IllegalArgumentException if sector is null
     */
    protected static Cylinder computeVerticalBoundsFromSectorQuadrilateral(Globe globe, Sector sector, double minHeight,
        double maxHeight)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Get three non-coincident points on the sector's quadrilateral. We choose the north or south pair that is
        // closest to the equator, then choose a third point from the opposite pair. We use maxHeight as elevation
        // because we want to bound the largest potential quadrilateral for the sector.
        Vec4 p0, p1, p2;
        if (Math.abs(sector.getMinLatitude().degrees) <= Math.abs(sector.getMaxLatitude().degrees))
        {
            p0 = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), maxHeight); // SE
            p1 = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight); // SW
            p2 = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight); // NW
        }
        else
        {
            p0 = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight); // NW
            p1 = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight); // NE
            p2 = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight); // SW
        }

        // Compute the center, axis, and radius of the circle that circumscribes the three points.
        // This circle is guaranteed to circumscribe all four points of the sector's Cartesian quadrilateral.
        Vec4[] centerOut = new Vec4[1];
        Vec4[] axisOut = new Vec4[1];
        double[] radiusOut = new double[1];
        if (!WWMath.computeCircleThroughPoints(p0, p1, p2, centerOut, axisOut, radiusOut))
        {
            // If the computation failed, then two of the points are coincident. Fall back to creating a bounding
            // cylinder based on the vertices of the sector. This bounding cylinder won't be as tight a fit, but
            // it will be correct.
            return computeVerticalBoundsFromSectorVertices(globe, sector, minHeight, maxHeight);
        }
        Vec4 centerPoint = centerOut[0];
        Vec4 axis = axisOut[0];
        double radius = radiusOut[0];

        // Compute the sector's lowest projection along the cylinder axis. We test opposite corners of the sector
        // using minHeight. One of these will be the lowest point in the sector.
        Vec4 extremePoint = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(),
            minHeight);
        double minProj = extremePoint.subtract3(centerPoint).dot3(axis);
        extremePoint = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), minHeight);
        minProj = Math.min(minProj, extremePoint.subtract3(centerPoint).dot3(axis));
        // Compute the sector's highest projection along the cylinder axis. We only need to use the point at the
        // sector's centroid with maxHeight. This point is guaranteed to be the highest point in the sector.
        LatLon centroid = sector.getCentroid();
        extremePoint = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(), maxHeight);
        double maxProj = extremePoint.subtract3(centerPoint).dot3(axis);

        Vec4 bottomCenterPoint = axis.multiply3(minProj).add3(centerPoint);
        Vec4 topCenterPoint = axis.multiply3(maxProj).add3(centerPoint);

        if (radius == 0)
            radius = 1;

        if (bottomCenterPoint.equals(topCenterPoint))
            topCenterPoint = bottomCenterPoint.add3(new Vec4(1, 0, 0));

        return new Cylinder(bottomCenterPoint, topCenterPoint, radius);
    }

    /**
     * Returns a cylinder that surrounds the specified height range in the zero-area sector. The returned cylinder won't
     * be as tight a fit as computeBoundsFromSectorQuadrilateral.
     *
     * @param globe     The globe associated with the sector.
     * @param sector    the sector to return the bounding cylinder for.
     * @param minHeight the minimum height to include in the bounding cylinder.
     * @param maxHeight the maximum height to include in the bounding cylinder.
     *
     * @return The minimal bounding cylinder in Cartesian coordinates.
     *
     * @throws IllegalArgumentException if sector is null
     */
    protected static Cylinder computeVerticalBoundsFromSectorVertices(Globe globe, Sector sector, double minHeight,
        double maxHeight)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Compute the top center point as the surface point with maxHeight at the sector's centroid.
        LatLon centroid = sector.getCentroid();
        Vec4 topCenterPoint = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(),
            maxHeight);
        // Compute the axis as the surface normal at the sector's centroid.
        Vec4 axis = globe.computeSurfaceNormalAtPoint(topCenterPoint);

        // Compute the four corner points of the sector with minHeight.
        Vec4 southwest = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), minHeight);
        Vec4 southeast = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), minHeight);
        Vec4 northeast = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), minHeight);
        Vec4 northwest = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), minHeight);

        // Compute the bottom center point as the lowest projection along the axis.
        double minProj = southwest.subtract3(topCenterPoint).dot3(axis);
        minProj = Math.min(minProj, southeast.subtract3(topCenterPoint).dot3(axis));
        minProj = Math.min(minProj, northeast.subtract3(topCenterPoint).dot3(axis));
        minProj = Math.min(minProj, northwest.subtract3(topCenterPoint).dot3(axis));
        Vec4 bottomCenterPoint = axis.multiply3(minProj).add3(topCenterPoint);

        // Compute the radius as the maximum distance from the top center point to any of the corner points.
        double radius = topCenterPoint.distanceTo3(southwest);
        radius = Math.max(radius, topCenterPoint.distanceTo3(southeast));
        radius = Math.max(radius, topCenterPoint.distanceTo3(northeast));
        radius = Math.max(radius, topCenterPoint.distanceTo3(northwest));

        if (radius == 0)
            radius = 1;

        if (bottomCenterPoint.equals(topCenterPoint))
            topCenterPoint = bottomCenterPoint.add3(new Vec4(1, 0, 0));

        return new Cylinder(bottomCenterPoint, topCenterPoint, radius);
    }

    /**
     * Display the cylinder.
     *
     * @param dc the current draw context.
     *
     * @throws IllegalArgumentException if the draw context is null.
     */
    public void render(DrawContext dc)
    {
        if (dc == null)
        {
            String msg = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Compute a matrix that will transform world coordinates to cylinder coordinates. The negative z-axis
        // will point from the cylinder's bottomCenter to its topCenter. The y-axis will be a vector that is
        // perpendicular to the cylinder's axisUnitDirection. Because the cylinder is symmetric, it does not matter
        // in what direction the y-axis points, as long as it is perpendicular to the z-axis.
        double tolerance = 1e-6;
        Vec4 upVector = (this.axisUnitDirection.cross3(Vec4.UNIT_Y).getLength3() <= tolerance) ?
            Vec4.UNIT_NEGATIVE_Z : Vec4.UNIT_Y;
        Matrix transformMatrix = Matrix.fromModelLookAt(this.bottomCenter, this.topCenter, upVector);
        double[] matrixArray = new double[16];
        transformMatrix.toArray(matrixArray, 0, false);

        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        OGLStackHandler ogsh = new OGLStackHandler();
        ogsh.pushAttrib(gl, GL2.GL_CURRENT_BIT | GL2.GL_ENABLE_BIT | GL2.GL_TRANSFORM_BIT | GL2.GL_DEPTH_BUFFER_BIT);
        try
        {
            // The cylinder is drawn with as a wireframe plus a center axis. It's drawn in two passes in order to
            // visualize the portions of the cylinder above and below an intersecting surface.

            gl.glEnable(GL.GL_BLEND);
            OGLUtil.applyBlending(gl, false);
            gl.glEnable(GL.GL_DEPTH_TEST);

            // Draw the axis
            gl.glDepthFunc(GL.GL_LEQUAL); // draw the part that would normally be visible
            gl.glColor4f(1f, 1f, 1f, 0.4f);
            gl.glBegin(GL2.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.glDepthFunc(GL.GL_GREATER); // draw the part that is behind an intersecting surface
            gl.glColor4f(1f, 0f, 1f, 0.4f);
            gl.glBegin(GL2.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();

            // Draw the exterior wireframe
            ogsh.pushModelview(gl);
            gl.glMultMatrixd(matrixArray, 0);

            GLUquadric quadric = dc.getGLU().gluNewQuadric();
            dc.getGLU().gluQuadricDrawStyle(quadric, GLU.GLU_LINE);

            gl.glDepthFunc(GL.GL_LEQUAL);
            gl.glColor4f(1f, 1f, 1f, 0.5f);
            dc.getGLU().gluCylinder(quadric, this.cylinderRadius, this.cylinderRadius, this.cylinderHeight, 30, 30);

            gl.glDepthFunc(GL.GL_GREATER);
            gl.glColor4f(1f, 0f, 1f, 0.4f);
            dc.getGLU().gluCylinder(quadric, this.cylinderRadius, this.cylinderRadius, this.cylinderHeight, 30, 30);

            dc.getGLU().gluDeleteQuadric(quadric);
        }
        finally
        {
            ogsh.pop(gl);
        }
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (!(o instanceof Cylinder))
            return false;

        Cylinder cylinder = (Cylinder) o;

        if (Double.compare(cylinder.cylinderHeight, cylinderHeight) != 0)
            return false;
        if (Double.compare(cylinder.cylinderRadius, cylinderRadius) != 0)
            return false;
        if (axisUnitDirection != null ? !axisUnitDirection.equals(cylinder.axisUnitDirection)
            : cylinder.axisUnitDirection != null)
            return false;
        if (bottomCenter != null ? !bottomCenter.equals(cylinder.bottomCenter) : cylinder.bottomCenter != null)
            return false;
        //noinspection RedundantIfStatement
        if (topCenter != null ? !topCenter.equals(cylinder.topCenter) : cylinder.topCenter != null)
            return false;

        return true;
    }

    @Override
    public int hashCode()
    {
        int result;
        long temp;
        result = bottomCenter != null ? bottomCenter.hashCode() : 0;
        result = 31 * result + (topCenter != null ? topCenter.hashCode() : 0);
        result = 31 * result + (axisUnitDirection != null ? axisUnitDirection.hashCode() : 0);
        temp = cylinderRadius != +0.0d ? Double.doubleToLongBits(cylinderRadius) : 0L;
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        temp = cylinderHeight != +0.0d ? Double.doubleToLongBits(cylinderHeight) : 0L;
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    public String toString()
    {
        return this.cylinderRadius + ", " + this.bottomCenter.toString() + ", " + this.topCenter.toString() + ", "
            + this.axisUnitDirection.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy