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

src.gov.nasa.worldwind.render.airspaces.SphereAirspace 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.render.airspaces;

import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.*;

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

/**
 * A spherical airspace shape defined by a center location and a radius. The sphere's center altitude and terrain
 * conformance attributes are taken from the lower altitude and lower terrain conformance. When terrain conformance is
 * disabled, the sphere's altitude behaves as a height above mean sea level. When terrain conformance is enabled, the
 * sphere's altitude will behave as a height offset above the terrain. Unlike other airspace shapes, the sphere's
 * geometry will not morph to the terrain beneath it.
 *
 * @author dcollins
 * @version $Id: SphereAirspace.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class SphereAirspace extends AbstractAirspace
{
    protected static final int DEFAULT_SUBDIVISIONS = 3;

    private LatLon location = LatLon.ZERO;
    private double radius = 1.0;
    // Geometry.
    private int subdivisions = DEFAULT_SUBDIVISIONS;

    public SphereAirspace(LatLon location, double radius)
    {
        if (location == null)
        {
            String message = Logging.getMessage("nullValue.LocationIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (radius < 0.0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.location = location;
        this.radius = radius;
        this.makeDefaultDetailLevels();
    }

    public SphereAirspace(AirspaceAttributes attributes)
    {
        super(attributes);
        this.makeDefaultDetailLevels();
    }

    public SphereAirspace()
    {
        this.makeDefaultDetailLevels();
    }

    private void makeDefaultDetailLevels()
    {
        List levels = new ArrayList();
        double[] ramp = ScreenSizeDetailLevel.computeLinearScreenSizeRamp(7, 10.0, 600.0);

        DetailLevel level;
        level = new ScreenSizeDetailLevel(ramp[0], "Detail-Level-0");
        level.setValue(SUBDIVISIONS, 6);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[1], "Detail-Level-1");
        level.setValue(SUBDIVISIONS, 5);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[2], "Detail-Level-2");
        level.setValue(SUBDIVISIONS, 4);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[3], "Detail-Level-3");
        level.setValue(SUBDIVISIONS, 3);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[4], "Detail-Level-4");
        level.setValue(SUBDIVISIONS, 2);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, true);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[5], "Detail-Level-5");
        level.setValue(SUBDIVISIONS, 1);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, true);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[6], "Detail-Level-6");
        level.setValue(SUBDIVISIONS, 0);
        //level.setValue(DISABLE_TERRAIN_CONFORMANCE, true);
        levels.add(level);

        this.setDetailLevels(levels);
    }

    /**
     * Returns the center location of the sphere.
     *
     * @return location of the sphere.
     */
    public LatLon getLocation()
    {
        return this.location;
    }

    /**
     * Sets the center location of the sphere.
     *
     * @param location the location of the sphere.
     *
     * @throws IllegalArgumentException if location is null
     */
    public void setLocation(LatLon location)
    {
        if (location == null)
        {
            String message = Logging.getMessage("nullValue.LocationIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.location = location;
        this.setExtentOutOfDate();
    }

    /**
     * Returns the radius of the sphere in meters.
     *
     * @return radius of the sphere in meters.
     */
    public double getRadius()
    {
        return this.radius;
    }

    /**
     * Sets the radius of the sphere in meters. This will also set the altitude limits to match the new radius and
     * center elevation.
     *
     * @param radius the radius of the sphere.
     *
     * @throws IllegalArgumentException if radius is less than zero
     */
    public void setRadius(double radius)
    {
        if (radius < 0.0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.radius = radius;
        this.setExtentOutOfDate();
    }

    public Position getReferencePosition()
    {
        double[] altitudes = this.getAltitudes();
        return new Position(this.location, altitudes[0]);
    }

    protected Extent computeExtent(Globe globe, double verticalExaggeration)
    {
        double altitude = this.getAltitudes(verticalExaggeration)[0];
        boolean terrainConformant = this.isTerrainConforming()[0];
        double radius = this.getRadius();

        if (terrainConformant)
        {
            double[] extremes = globe.getMinAndMaxElevations(this.location.getLatitude(), this.location.getLongitude());
            double minAltitude = verticalExaggeration * extremes[0] + altitude - radius;
            double maxAltitude = verticalExaggeration * extremes[1] + altitude + radius;
            Vec4 bottomCenter = globe.computePointFromPosition(this.location, minAltitude);
            Vec4 topCenter = globe.computePointFromPosition(this.location, maxAltitude);
            return new Cylinder(bottomCenter, topCenter, radius);
        }
        else
        {
            Vec4 centerPoint = globe.computePointFromPosition(this.location, altitude);
            return new Sphere(centerPoint, radius);
        }
    }

    @Override
    protected List computeMinimalGeometry(Globe globe, double verticalExaggeration)
    {
        return null; // Sphere has no need for a minimal geometry.
    }

    /**
     * Returns this SphereAirspace's {@link gov.nasa.worldwind.geom.Extent} for the specified DrawContext. This
     * overrides {@link gov.nasa.worldwind.render.airspaces.AbstractAirspace#getExtent(gov.nasa.worldwind.render.DrawContext)}
     * in order to bypass the superclass' extent caching. Unlike other Airspace's Extents, SphereAirspace's Extent is a
     * perfect fitting {@link gov.nasa.worldwind.geom.Sphere}, who's center point depends on the current surface
     * geometry. For this reason SphereAirspace's exact bounding volume can be easily computed, and should not be
     * cached.
     *
     * @param dc the current DrawContext.
     *
     * @return this SphereAirspace's Extent in model coordinates.
     *
     * @throws IllegalArgumentException if the DrawContext is null, or if the Globe held by the DrawContext is null.
     */
    public Extent getExtent(DrawContext dc)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.computeExtent(dc);
    }

    protected Sphere computeExtent(DrawContext dc)
    {
        double altitude = this.getAltitudes(dc.getVerticalExaggeration())[0];
        boolean terrainConformant = this.isTerrainConforming()[0];
        double radius = this.getRadius();

        this.clearElevationMap();

        Vec4 centerPoint = this.computePointFromPosition(dc, this.location.getLatitude(), this.location.getLongitude(),
            altitude, terrainConformant);
        return new Sphere(centerPoint, radius);
    }

    protected void doMoveTo(Position oldRef, Position newRef)
    {
        if (oldRef == null)
        {
            String message = "nullValue.OldRefIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (newRef == null)
        {
            String message = "nullValue.NewRefIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        super.doMoveTo(oldRef, newRef);

        this.setLocation(newRef);
    }

    protected double computeEyeDistance(DrawContext dc)
    {
        Sphere sphere = this.computeExtent(dc);
        Vec4 eyePoint = dc.getView().getEyePoint();

        // Return the distance to the sphere's edge. This provides a good distance for rendering order when the the eye
        // point is inside and outside of the sphere.
        double distance = sphere.getCenter().distanceTo3(eyePoint);
        return Math.abs(distance - sphere.getRadius());
    }

    protected int getSubdivisions()
    {
        return this.subdivisions;
    }

    protected void setSubdivisions(int subdivisions)
    {
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.subdivisions = subdivisions;
    }

    //**************************************************************//
    //********************  Geometry Rendering  ********************//
    //**************************************************************//

    protected void doRenderGeometry(DrawContext dc, String drawStyle)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dc.getGL() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGLIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.clearElevationMap();

        if (Airspace.DRAW_STYLE_FILL.equals(drawStyle))
        {
            this.drawSphere(dc);
        }
        else if (Airspace.DRAW_STYLE_OUTLINE.equals(drawStyle))
        {
            // TODO: sphere airspace outline
        }
    }

    protected void drawSphere(DrawContext dc)
    {
        double[] altitudes = this.getAltitudes(dc.getVerticalExaggeration());
        boolean[] terrainConformant = this.isTerrainConforming();
        int subdivisions = this.getSubdivisions();

        if (this.isEnableLevelOfDetail())
        {
            DetailLevel level = this.computeDetailLevel(dc);

            Object o = level.getValue(SUBDIVISIONS);
            if (o != null && o instanceof Integer)
                subdivisions = (Integer) o;

            //o = level.getValue(DISABLE_TERRAIN_CONFORMANCE);
            //if (o != null && o instanceof Boolean && (Boolean) o)
            //    terrainConformant[0] = terrainConformant[1] = false;
        }

        Vec4 centerPoint = this.computePointFromPosition(dc,
            this.location.getLatitude(), this.location.getLongitude(), altitudes[0], terrainConformant[0]);

        Matrix modelview = dc.getView().getModelviewMatrix();
        modelview = modelview.multiply(Matrix.fromTranslation(centerPoint));
        modelview = modelview.multiply(Matrix.fromScale(this.getRadius()));
        double[] matrixArray = new double[16];
        modelview.toArray(matrixArray, 0, false);

        this.setExpiryTime(-1L); // Sphere geometry never expires.

        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        gl.glPushAttrib(GL2.GL_POLYGON_BIT | GL2.GL_TRANSFORM_BIT);
        try
        {
            gl.glEnable(GL.GL_CULL_FACE);
            gl.glFrontFace(GL.GL_CCW);

            // Were applying a scale transform on the modelview matrix, so the normal vectors must be re-normalized
            // before lighting is computed. In this case we're scaling by a constant factor, so GL_RESCALE_NORMAL
            // is sufficient and potentially less expensive than GL_NORMALIZE (or computing unique normal vectors
            // for each value of radius). GL_RESCALE_NORMAL was introduced in OpenGL version 1.2.
            gl.glEnable(GL2.GL_RESCALE_NORMAL);

            gl.glMatrixMode(GL2.GL_MODELVIEW);
            gl.glPushMatrix();
            try
            {
                gl.glLoadMatrixd(matrixArray, 0);
                this.drawUnitSphere(dc, subdivisions);
            }
            finally
            {
                gl.glPopMatrix();
            }
        }
        finally
        {
            gl.glPopAttrib();
        }
    }

    protected void drawUnitSphere(DrawContext dc, int subdivisions)
    {
        Object cacheKey = new Geometry.CacheKey(dc.getGlobe(), this.getClass(), "Sphere", subdivisions);
        Geometry geom = (Geometry) this.getGeometryCache().getObject(cacheKey);
        if (geom == null || this.isExpired(dc, geom))
        {
            if (geom == null)
                geom = new Geometry();
            this.makeSphere(1.0, subdivisions, geom);
            this.updateExpiryCriteria(dc, geom);
            this.getGeometryCache().add(cacheKey, geom);
        }

        this.getRenderer().drawGeometry(dc, geom);
    }

    protected void makeSphere(double radius, int subdivisions, Geometry dest)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        gb.setOrientation(GeometryBuilder.OUTSIDE);

        GeometryBuilder.IndexedTriangleArray ita = gb.tessellateSphere((float) radius, subdivisions);
        float[] normalArray = new float[3 * ita.getVertexCount()];
        gb.makeIndexedTriangleArrayNormals(ita, normalArray);

        dest.setElementData(GL.GL_TRIANGLES, ita.getIndexCount(), ita.getIndices());
        dest.setVertexData(ita.getVertexCount(), ita.getVertices());
        dest.setNormalData(ita.getVertexCount(), normalArray);
    }

    //**************************************************************//
    //********************  END Geometry Rendering  ****************//
    //**************************************************************//

    protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        super.doGetRestorableState(rs, context);

        rs.addStateValueAsLatLon(context, "location", this.getLocation());
        rs.addStateValueAsDouble(context, "radius", this.getRadius());
        rs.addStateValueAsInteger(context, "subdivisions", this.getSubdivisions());
    }

    protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        super.doRestoreState(rs, context);

        LatLon ll = rs.getStateValueAsLatLon(context, "location");
        if (ll != null)
            this.setLocation(ll);

        Double d = rs.getStateValueAsDouble(context, "radius");
        if (d != null)
            this.setRadius(d);

        Integer i = rs.getStateValueAsInteger(context, "subdivisions");
        if (i != null)
            this.setSubdivisions(i);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy