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

gov.nasa.worldwind.render.airspaces.Box Maven / Gradle / Ivy

The 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.cache.Cacheable;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.terrain.Terrain;
import gov.nasa.worldwind.util.*;

import com.jogamp.opengl.*;
import java.nio.*;
import java.util.*;

/**
 * @author lado
 * @version $Id: Box.java 2563 2014-12-12 19:29:38Z dcollins $
 */
public class Box extends AbstractAirspace
{
    protected static final int DEFAULT_PILLARS = 8;
    protected static final int DEFAULT_STACKS = 2;

    protected static final int DEFAULT_CENTER_LINE_STIPPLE_FACTOR = 2;
    protected static final short DEFAULT_CENTER_LINE_STIPPLE_PATTERN = (short) 0xEEEE;
    protected static final double DEFAULT_CENTER_LINE_OFFSET = 0.999;

    private LatLon beginLocation = LatLon.ZERO;
    private LatLon endLocation = LatLon.ZERO;
    private double leftWidth = 1.0;
    private double rightWidth = 1.0;
    private Angle beginLeftAzimuth = null;
    private Angle beginRightAzimuth = null;
    private Angle endLeftAzimuth = null;
    private Angle endRightAzimuth = null;
    private boolean enableStartCap = true;
    private boolean enableEndCap = true;
    private boolean enableCenterLine;

    private boolean forceCullFace = false;
    private int pillars = DEFAULT_PILLARS;
    private int stacks = DEFAULT_STACKS;

    private Object geometryCacheKey = new Object();

    public Box(LatLon beginLocation, LatLon endLocation, double leftWidth, double rightWidth)
    {
        if (beginLocation == null || endLocation == null)
        {
            String message = Logging.getMessage("nullValue.LocationIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (leftWidth < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "leftWidth < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (rightWidth < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "rightWidth < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.beginLocation = beginLocation;
        this.endLocation = endLocation;
        this.leftWidth = leftWidth;
        this.rightWidth = rightWidth;
        this.makeDefaultDetailLevels();
    }

    public Box(Box source)
    {
        super(source);

        this.beginLocation = source.beginLocation;
        this.endLocation = source.endLocation;
        this.leftWidth = source.leftWidth;
        this.rightWidth = source.rightWidth;
        this.beginLeftAzimuth = source.beginLeftAzimuth;
        this.beginRightAzimuth = source.beginRightAzimuth;
        this.endLeftAzimuth = source.endLeftAzimuth;
        this.endRightAzimuth = source.endRightAzimuth;
        this.enableStartCap = source.enableStartCap;
        this.enableEndCap = source.enableEndCap;
        this.enableCenterLine = source.enableCenterLine;
        this.forceCullFace = source.forceCullFace;
        this.pillars = source.pillars;
        this.stacks = source.stacks;

        this.makeDefaultDetailLevels();
    }

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

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

    private void makeDefaultDetailLevels()
    {
        List levels = new ArrayList();
        double[] ramp = ScreenSizeDetailLevel.computeDefaultScreenSizeRamp(5);

        DetailLevel level;
        level = new ScreenSizeDetailLevel(ramp[0], "Detail-Level-0");
        level.setValue(PILLARS, 8);
        level.setValue(STACKS, 2);
        level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[1], "Detail-Level-1");
        level.setValue(PILLARS, 6);
        level.setValue(STACKS, 2);
        level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

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

        level = new ScreenSizeDetailLevel(ramp[3], "Detail-Level-3");
        level.setValue(PILLARS, 2);
        level.setValue(STACKS, 1);
        level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

        level = new ScreenSizeDetailLevel(ramp[4], "Detail-Level-4");
        level.setValue(PILLARS, 1);
        level.setValue(STACKS, 1);
        level.setValue(DISABLE_TERRAIN_CONFORMANCE, true);
        levels.add(level);

        this.setDetailLevels(levels);
    }

    public LatLon[] getLocations()
    {
        return new LatLon[] {this.beginLocation, this.endLocation};
    }

    /**
     * Sets the leg's locations, in geographic coordinates.
     *
     * @param beginLocation geographic coordinates(latitude and longitude) specifying the center of the begining edge.
     * @param endLocation geographic coordinates(latitude and longitude) specifying the center of the ending edge.
     *
     * @throws IllegalArgumentException if location1 or location2 is null
     */
    public void setLocations(LatLon beginLocation, LatLon endLocation)
    {
        if (beginLocation == null || endLocation == null)
        {
            String message = Logging.getMessage("nullValue.LocationIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.beginLocation = beginLocation;
        this.endLocation = endLocation;
        this.invalidateGeometry();
    }

    public double[] getWidths()
    {
        double[] array = new double[2];
        array[0] = this.leftWidth;
        array[1] = this.rightWidth;
        return array;
    }

    public void setWidths(double leftWidth, double rightWidth)
    {
        if (leftWidth < 0.0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "leftWidth=" + leftWidth);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (rightWidth < 0.0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "rightWidth=" + rightWidth);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.leftWidth = leftWidth;
        this.rightWidth = rightWidth;
        this.invalidateGeometry();
    }

    /**
     * Indicates the azimuth angles for this box's four corners, relative to geographic north. Angles are organized in
     * the returned array as follows: begin left, begin right, end left, end right. Null elements in any index indicate
     * that the default angle is used.
     *
     * @return an array of length four indicating this box's corner azimuths.
     */
    public Angle[] getCornerAzimuths()
    {
        return new Angle[] {this.beginLeftAzimuth, this.beginRightAzimuth, this.endLeftAzimuth, this.endRightAzimuth};
    }

    /**
     * Specifies the azimuth angles for this box's four corners, relative to geographic north. Specifying a null
     * argument indicates that the default angle should be used.
     */
    public void setCornerAzimuths(Angle beginLeftAzimuth, Angle beginRightAzimuth, Angle endLeftAzimuth,
        Angle endRightAzimuth)
    {
        this.beginLeftAzimuth = beginLeftAzimuth;
        this.beginRightAzimuth = beginRightAzimuth;
        this.endLeftAzimuth = endLeftAzimuth;
        this.endRightAzimuth = endRightAzimuth;
        this.invalidateGeometry();
    }

    public boolean[] isEnableCaps()
    {
        boolean[] array = new boolean[2];
        array[0] = this.enableStartCap;
        array[1] = this.enableEndCap;
        return array;
    }

    public void setEnableCaps(boolean enableStartCap, boolean enableEndCap)
    {
        this.enableStartCap = enableStartCap;
        this.enableEndCap = enableEndCap;
        this.invalidateGeometry();
    }

    public void setEnableCaps(boolean enable)
    {
        this.setEnableCaps(enable, enable);
    }

    public void setEnableStartCap(boolean enable)
    {
        this.setEnableCaps(enable, this.enableEndCap);
    }

    public void setEnableEndCap(boolean enable)
    {
        this.setEnableCaps(this.enableStartCap, enable);
    }

    public boolean isEnableCenterLine()
    {
        return this.enableCenterLine;
    }

    public void setEnableCenterLine(boolean enable)
    {
        this.enableCenterLine = enable;
        this.invalidateGeometry();
    }

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

    protected void invalidateGeometry()
    {
        this.invalidateAirspaceData();
        this.geometryCacheKey = new Object();
    }

    protected gov.nasa.worldwind.geom.Box computeExtent(Globe globe, double verticalExaggeration)
    {
        List points = this.computeMinimalGeometry(globe, verticalExaggeration);
        if (points == null || points.isEmpty())
            return null;

        return gov.nasa.worldwind.geom.Box.computeBoundingBox(points);
    }

    @Override
    protected List computeMinimalGeometry(Globe globe, double verticalExaggeration)
    {
        List locations = this.makeCapLocations(globe, DEFAULT_PILLARS, DEFAULT_STACKS);
        List points = new ArrayList();
        this.makeExtremePoints(globe, verticalExaggeration, locations, points);

        return points;
    }

    @Override
    protected SurfaceShape createSurfaceShape()
    {
        return new SurfaceBox();
    }

    @Override
    protected void regenerateSurfaceShape(DrawContext dc, SurfaceShape shape)
    {
        int lengthSegments = this.getPillars();
        int widthSegments = this.getStacks();
        List locations = this.makeSideLocations(dc.getGlobe(), lengthSegments, widthSegments);

        ((SurfaceBox) shape).setLocations(locations);
        ((SurfaceBox) shape).setLengthSegments(lengthSegments);
        ((SurfaceBox) shape).setWidthSegments(widthSegments);
        ((SurfaceBox) shape).setEnableCaps(this.isEnableCaps()[0], this.isEnableCaps()[1]);
        ((SurfaceBox) shape).setEnableCenterLine(this.isEnableCenterLine());
    }

    protected void doMoveTo(Globe globe, 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);
        }

        List locations = new ArrayList(2);
        locations.add(this.getLocations()[0]);
        locations.add(this.getLocations()[1]);

        List newLocations = LatLon.computeShiftedLocations(globe, oldRef, newRef, locations);
        this.setLocations(newLocations.get(0), newLocations.get(1));

        super.doMoveTo(oldRef, newRef);
    }

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

        LatLon[] locations = this.getLocations();
        int count = locations.length;
        for (int i = 0; i < count; i++)
        {
            double distance = LatLon.greatCircleDistance(oldRef, locations[i]).radians;
            double azimuth = LatLon.greatCircleAzimuth(oldRef, locations[i]).radians;
            locations[i] = LatLon.greatCircleEndPosition(newRef, azimuth, distance);
        }
        this.setLocations(locations[0], locations[1]);
    }

    protected boolean isForceCullFace()
    {
        return this.forceCullFace;
    }

    protected void setForceCullFace(boolean forceCullFace)
    {
        this.forceCullFace = forceCullFace;
    }

    protected int getPillars()
    {
        return this.pillars;
    }

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

        this.pillars = pillars;
    }

    protected int getStacks()
    {
        return this.stacks;
    }

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

        this.stacks = stacks;
    }

    protected int getHeightStacks()
    {
        return 1;
    }

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

    protected Vec4 computeReferenceCenter(DrawContext dc)
    {
        Extent extent = this.getExtent(dc);
        return extent != null ? extent.getCenter() : null;
    }

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

        double[] altitudes = this.getAltitudes(dc.getVerticalExaggeration());
        boolean[] terrainConformant = this.isTerrainConforming();
        int lengthSegments = this.getPillars();
        int widthSegments = this.getStacks();

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

            Object o = level.getValue(PILLARS);
            if (o != null && o instanceof Integer)
                lengthSegments = (Integer) o;

            o = level.getValue(STACKS);
            if (o != null && o instanceof Integer)
                widthSegments = (Integer) o;

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

        this.setExpiryTime(this.nextExpiryTime(dc, this.isTerrainConforming()));
        this.clearElevationMap();

        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        OGLStackHandler ogsh = new OGLStackHandler();
        try
        {
            if (this.forceCullFace || !this.enableStartCap || !this.enableEndCap)
            {
                ogsh.pushAttrib(gl, GL2.GL_POLYGON_BIT);
                gl.glEnable(GL.GL_CULL_FACE);
                gl.glFrontFace(GL.GL_CCW);
            }

            if (Airspace.DRAW_STYLE_FILL.equals(drawStyle))
            {
                this.drawBox(dc, altitudes, terrainConformant, lengthSegments, widthSegments);
            }
            else if (Airspace.DRAW_STYLE_OUTLINE.equals(drawStyle))
            {
                this.drawBoxOutline(dc, altitudes, terrainConformant, lengthSegments, widthSegments);

                if (this.enableCenterLine)
                {
                    this.drawBoxCenterLine(dc, altitudes, terrainConformant, lengthSegments, widthSegments);
                }
            }
        }
        finally
        {
            ogsh.pop(gl);
        }
    }

    protected void applyCenterLineState(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        AirspaceAttributes attrs = this.getActiveAttributes();

        if (attrs.getOutlineStippleFactor() <= 0) // override stipple in attributes
        {
            gl.glEnable(GL2.GL_LINE_STIPPLE);
            gl.glLineStipple(Box.DEFAULT_CENTER_LINE_STIPPLE_FACTOR, Box.DEFAULT_CENTER_LINE_STIPPLE_PATTERN);
        }
    }

    //**************************************************************//
    //********************  Box  ***********************************//
    //**************************************************************//

    private static class BoxGeometry implements Cacheable
    {
        public Geometry sideGeometry = new Geometry();
        public Geometry capGeometry = new Geometry();
        public Geometry outlineIndices = new Geometry();
        public Geometry centerLineIndices = new Geometry();
        public Vec4 referencePoint;

        @Override
        public long getSizeInBytes()
        {
            return this.sideGeometry.getSizeInBytes()
                + this.capGeometry.getSizeInBytes()
                + this.outlineIndices.getSizeInBytes()
                + this.centerLineIndices.getSizeInBytes();
        }
    }

    private static class BoxCorners
    {
        public LatLon beginLeft;
        public LatLon beginRight;
        public LatLon endLeft;
        public LatLon endRight;

        public LatLon beginLeftProj;
        public LatLon beginRightProj;
        public LatLon endLeftProj;
        public LatLon endRightProj;

        public double leftArcLength;
        public double rightArcLength;
    }

    private void drawBox(DrawContext dc, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments)
    {
        BoxGeometry geom = this.getBoxGeometry(dc, altitudes, terrainConformant, lengthSegments, widthSegments);
        try
        {
            dc.getView().pushReferenceCenter(dc, geom.referencePoint);
            this.drawGeometry(dc, geom.sideGeometry, geom.sideGeometry);
            this.drawGeometry(dc, geom.capGeometry, geom.capGeometry);
        }
        finally
        {
            dc.getView().popReferenceCenter(dc);
        }
    }

    private void drawBoxOutline(DrawContext dc, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments)
    {
        BoxGeometry geom = this.getBoxGeometry(dc, altitudes, terrainConformant, lengthSegments, widthSegments);
        try
        {
            dc.getView().pushReferenceCenter(dc, geom.referencePoint);
            this.drawGeometry(dc, geom.outlineIndices, geom.sideGeometry);
        }
        finally
        {
            dc.getView().popReferenceCenter(dc);
        }
    }

    private void drawBoxCenterLine(DrawContext dc, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments)
    {
        BoxGeometry geom = this.getBoxGeometry(dc, altitudes, terrainConformant, lengthSegments, widthSegments);
        try
        {
            dc.getView().pushReferenceCenter(dc, geom.referencePoint);
            dc.pushProjectionOffest(DEFAULT_CENTER_LINE_OFFSET); // move center line depth slightly in front of fill
            this.applyCenterLineState(dc);
            this.drawGeometry(dc, geom.centerLineIndices, geom.capGeometry);
        }
        finally
        {
            dc.popProjectionOffest();
            dc.getView().popReferenceCenter(dc);
        }
    }

    private BoxGeometry getBoxGeometry(DrawContext dc, double[] altitudes, boolean[] terrainConformant,
        int lengthSegments, int widthSegments)
    {
        Object cacheKey = new Geometry.CacheKey(dc.getGlobe(), this.getClass(), "Box.Geometry", this.geometryCacheKey,
            altitudes, terrainConformant, lengthSegments, widthSegments);
        BoxGeometry geom = (BoxGeometry) this.getGeometryCache().getObject(cacheKey);

        if (geom != null && !this.isExpired(dc, geom.sideGeometry))
            return geom;

        if (geom == null)
            geom = new BoxGeometry();

        this.makeBoxGeometry(dc, altitudes, terrainConformant, lengthSegments, widthSegments, geom);
        this.updateExpiryCriteria(dc, geom.sideGeometry);
        this.getGeometryCache().add(cacheKey, geom);

        return geom;
    }

    private void makeBoxGeometry(DrawContext dc, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments, BoxGeometry geom)
    {
        geom.referencePoint = this.computeReferenceCenter(dc);
        this.makeSideGeometry(dc.getTerrain(), altitudes, terrainConformant, lengthSegments, widthSegments, geom);
        this.makeCapGeometry(dc.getTerrain(), altitudes, terrainConformant, lengthSegments, widthSegments, geom);
    }

    private void makeSideGeometry(Terrain terrain, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments, BoxGeometry geom)
    {
        List locations = this.makeSideLocations(terrain.getGlobe(), lengthSegments, widthSegments);

        // Compute model coordinate vertex points.
        int vertexCount = 2 * locations.size(); // upper altitude and lower altitude vertices
        float[] pointArray = new float[3 * vertexCount];
        FloatBuffer pointBuffer = FloatBuffer.wrap(pointArray);

        for (LatLon ll : locations)
        {
            for (int i = 1; i >= 0; i--) // upper altitude then lower altitude
            {
                Vec4 p = terrainConformant[i] ?
                    terrain.getSurfacePoint(ll.latitude, ll.longitude, altitudes[i]) :
                    terrain.getGlobe().computePointFromPosition(ll.latitude, ll.longitude, altitudes[i]);
                pointBuffer.put((float) (p.x - geom.referencePoint.x));
                pointBuffer.put((float) (p.y - geom.referencePoint.y));
                pointBuffer.put((float) (p.z - geom.referencePoint.z));
            }
        }

        // Compute triangle indices and line segment indices.
        int[] sideSegments = {2 * widthSegments, lengthSegments, 2 * widthSegments, lengthSegments};
        boolean[] sideFlag = {this.enableStartCap, true, this.enableEndCap, true};
        int indexCount = 0;
        int outlineCount = 8;

        // Count the number of triangle and line segment indices, depending on whether each end cap is enabled.
        for (int i = 0; i < 4; i++)
        {
            if (sideFlag[i])
            {
                indexCount += 6 * sideSegments[i];
                outlineCount += 4 * sideSegments[i];
            }
        }

        int index = 0;
        int[] indexArray = new int[indexCount];
        int[] outlineArray = new int[outlineCount];
        IntBuffer indexBuffer = IntBuffer.wrap(indexArray);
        IntBuffer outlineBuffer = IntBuffer.wrap(outlineArray);

        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < sideSegments[i]; j++)
            {
                if (sideFlag[i])
                {
                    indexBuffer.put(index).put(index + 1).put(index + 2); // upper left triangle
                    indexBuffer.put(index + 2).put(index + 1).put(index + 3); // lower right triangle
                    outlineBuffer.put(index).put(index + 2); // upper altitude segment
                    outlineBuffer.put(index + 1).put(index + 3); // lower altitude segment
                }
                index += 2;
            }

            outlineBuffer.put(index).put(index + 1); // vertical segment
            index += 2; // advance to the beginning of the next segment
        }

        // Compute model coordinate vertex normals.
        float[] normalArray = new float[3 * vertexCount];
        GeometryBuilder gb = new GeometryBuilder();
        gb.makeIndexedTriangleArrayNormals(0, indexCount, indexArray, 0, vertexCount, pointArray, normalArray);

        geom.sideGeometry.setVertexData(vertexCount, pointArray);
        geom.sideGeometry.setNormalData(vertexCount, normalArray);
        geom.sideGeometry.setElementData(GL.GL_TRIANGLES, indexCount, indexArray);
        geom.outlineIndices.setElementData(GL.GL_LINES, outlineCount, outlineArray);
    }

    private void makeCapGeometry(Terrain terrain, double[] altitudes, boolean[] terrainConformant, int lengthSegments,
        int widthSegments, BoxGeometry geom)
    {
        List locations = this.makeCapLocations(terrain.getGlobe(), lengthSegments, widthSegments);

        // Compute model coordinate vertex points.
        int vertexCount = 2 * locations.size(); // upper altitude and lower altitude vertices
        float[] pointArray = new float[3 * vertexCount];
        FloatBuffer pointBuffer = FloatBuffer.wrap(pointArray);

        for (LatLon ll : locations)
        {
            for (int i = 1; i >= 0; i--) // upper altitude then lower altitude
            {
                Vec4 p = terrainConformant[i] ?
                    terrain.getSurfacePoint(ll.latitude, ll.longitude, altitudes[i]) :
                    terrain.getGlobe().computePointFromPosition(ll.latitude, ll.longitude, altitudes[i]);
                pointBuffer.put((float) (p.x - geom.referencePoint.x));
                pointBuffer.put((float) (p.y - geom.referencePoint.y));
                pointBuffer.put((float) (p.z - geom.referencePoint.z));
            }
        }

        // Compute triangle indices.
        int cellCount = 4 * lengthSegments * widthSegments; // top/bottom cells for left and right sides
        int indexCount = 6 * cellCount; // six indices per cell
        int[] indexArray = new int[indexCount];
        IntBuffer indexBuffer = IntBuffer.wrap(indexArray);

        int index = 0;
        int rowStride = 4 * widthSegments + 2;

        // left top and bottom
        for (int i = 0; i < lengthSegments; i++)
        {
            for (int j = 0; j < 2 * widthSegments; j++)
            {
                // upper altitude triangles
                indexBuffer.put(index).put(index + 2).put(index + rowStride);
                indexBuffer.put(index + rowStride).put(index + 2).put(index + rowStride + 2);
                index++;
                // lower altitude triangles
                indexBuffer.put(index).put(index + rowStride).put(index + 2);
                indexBuffer.put(index + 2).put(index + rowStride).put(index + rowStride + 2);
                index++;
            }

            index += 2; // skip the last row of side vertices
        }

        // Compute center line indices.
        int segmentCount = 2 * lengthSegments + (this.enableStartCap ? 1 : 0) + (this.enableEndCap ? 1 : 0);
        int centerLineCount = 2 * segmentCount; // two indices per segment
        int[] centerLineArray = new int[centerLineCount];
        IntBuffer centerLineBuffer = IntBuffer.wrap(centerLineArray);

        index = 2 * widthSegments; // start at the first center vertex

        if (this.enableStartCap)
        {
            centerLineBuffer.put(index).put(index + 1); // begin vertical segment
        }

        for (int i = 0; i < lengthSegments; i++)
        {
            centerLineBuffer.put(index).put(index + rowStride); // upper altitude segment
            centerLineBuffer.put(index + 1).put(index + rowStride + 1); // lower altitude segment
            index += rowStride;
        }

        if (this.enableEndCap)
        {
            centerLineBuffer.put(index).put(index + 1); // end vertical segment
        }

        // Compute model coordinate vertex normals.
        float[] normalArray = new float[3 * vertexCount];
        GeometryBuilder gb = new GeometryBuilder();
        gb.makeIndexedTriangleArrayNormals(0, indexCount, indexArray, 0, vertexCount, pointArray, normalArray);

        geom.capGeometry.setVertexData(vertexCount, pointArray);
        geom.capGeometry.setNormalData(vertexCount, normalArray);
        geom.capGeometry.setElementData(GL.GL_TRIANGLES, indexCount, indexArray);
        geom.centerLineIndices.setElementData(GL.GL_LINES, centerLineCount, centerLineArray);
    }

    private List makeSideLocations(Globe globe, int lengthSegments, int widthSegments)
    {
        ArrayList locations = new ArrayList();
        BoxCorners corners = this.computeBoxCorners(globe);

        // begin side
        this.appendLocations(corners.beginLeft, this.beginLocation, corners.beginRight, widthSegments, locations);

        // right side
        locations.add(corners.beginRight);
        for (int i = 1; i < lengthSegments; i++)
        {
            double amount = (double) i / (double) lengthSegments;
            LatLon rightProj = LatLon.interpolateGreatCircle(amount, corners.beginRightProj, corners.endRightProj);
            double rightAzimuth = LatLon.greatCircleAzimuth(rightProj, corners.endRightProj).radians + (Math.PI / 2);
            LatLon right = LatLon.greatCircleEndPosition(rightProj, rightAzimuth, corners.rightArcLength);
            locations.add(right);
        }
        locations.add(corners.endRight);

        // end side
        this.appendLocations(corners.endRight, this.endLocation, corners.endLeft, widthSegments, locations);

        // left side
        locations.add(corners.endLeft);
        for (int i = 1; i < lengthSegments; i++)
        {
            double amount = (double) i / (double) lengthSegments;
            LatLon leftProj = LatLon.interpolateGreatCircle(amount, corners.endLeftProj, corners.beginLeftProj);
            double leftAzimuth = LatLon.greatCircleAzimuth(leftProj, corners.endLeftProj).radians - (Math.PI / 2);
            LatLon left = LatLon.greatCircleEndPosition(leftProj, leftAzimuth, corners.leftArcLength);
            locations.add(left);
        }
        locations.add(corners.beginLeft);

        return locations;
    }

    private List makeCapLocations(Globe globe, int lengthSegments, int widthSegments)
    {
        ArrayList locations = new ArrayList();
        BoxCorners corners = this.computeBoxCorners(globe);

        // begin row
        this.appendLocations(corners.beginLeft, this.beginLocation, corners.beginRight, widthSegments, locations);

        // interior rows
        for (int i = 1; i < lengthSegments; i++)
        {
            double amount = (double) i / (double) lengthSegments;
            LatLon center = LatLon.interpolateGreatCircle(amount, this.beginLocation, this.endLocation);
            LatLon leftProj = LatLon.interpolateGreatCircle(amount, corners.beginLeftProj, corners.endLeftProj);
            LatLon rightProj = LatLon.interpolateGreatCircle(amount, corners.beginRightProj, corners.endRightProj);
            double leftAzimuth = LatLon.greatCircleAzimuth(leftProj, corners.endLeftProj).radians - (Math.PI / 2);
            double rightAzimuth = LatLon.greatCircleAzimuth(rightProj, corners.endRightProj).radians + (Math.PI / 2);
            LatLon left = LatLon.greatCircleEndPosition(leftProj, leftAzimuth, corners.leftArcLength);
            LatLon right = LatLon.greatCircleEndPosition(rightProj, rightAzimuth, corners.rightArcLength);

            this.appendLocations(left, center, right, widthSegments, locations);
        }

        // end row
        this.appendLocations(corners.endLeft, this.endLocation, corners.endRight, widthSegments, locations);

        return locations;
    }

    private BoxCorners computeBoxCorners(Globe globe)
    {
        BoxCorners corners = new BoxCorners();
        double beginAzimuth = LatLon.greatCircleAzimuth(this.beginLocation, this.endLocation).radians;
        double endAzimuth = LatLon.greatCircleAzimuth(this.endLocation, this.beginLocation).radians;
        double centerArcLength = LatLon.greatCircleDistance(this.beginLocation, this.endLocation).radians;
        corners.leftArcLength = this.leftWidth / globe.getRadius();
        corners.rightArcLength = this.rightWidth / globe.getRadius();

        corners.beginLeft = LatLon.greatCircleEndPosition(this.beginLocation, beginAzimuth - (Math.PI / 2),
            corners.leftArcLength);
        corners.beginLeftProj = this.beginLocation;
        if (this.beginLeftAzimuth != null)
        {
            double arcAngle = beginAzimuth - this.beginLeftAzimuth.radians;
            double arcLength = Math.asin(Math.cos(arcAngle) * Math.sin(corners.leftArcLength) / Math.sin(arcAngle));
            double sideLength = Math.asin(Math.sin(corners.leftArcLength) / Math.sin(arcAngle));
            if (arcLength < centerArcLength)
            {
                corners.beginLeft = LatLon.greatCircleEndPosition(this.beginLocation, this.beginLeftAzimuth.radians,
                    sideLength);
                corners.beginLeftProj = LatLon.greatCircleEndPosition(this.beginLocation, beginAzimuth, arcLength);
            }
        }

        corners.beginRight = LatLon.greatCircleEndPosition(this.beginLocation, beginAzimuth + (Math.PI / 2),
            corners.rightArcLength);
        corners.beginRightProj = this.beginLocation;
        if (this.beginRightAzimuth != null)
        {
            double arcAngle = this.beginRightAzimuth.radians - beginAzimuth;
            double arcLength = Math.asin(Math.cos(arcAngle) * Math.sin(corners.rightArcLength) / Math.sin(arcAngle));
            double sideLength = Math.asin(Math.sin(corners.rightArcLength) / Math.sin(arcAngle));
            if (arcLength < centerArcLength)
            {
                corners.beginRight = LatLon.greatCircleEndPosition(this.beginLocation, this.beginRightAzimuth.radians,
                    sideLength);
                corners.beginRightProj = LatLon.greatCircleEndPosition(this.beginLocation, beginAzimuth, arcLength);
            }
        }

        corners.endLeft = LatLon.greatCircleEndPosition(this.endLocation, endAzimuth + (Math.PI / 2),
            corners.leftArcLength);
        corners.endLeftProj = this.endLocation;
        if (this.endLeftAzimuth != null)
        {
            double arcAngle = this.endLeftAzimuth.radians - endAzimuth;
            double arcLength = Math.asin(Math.cos(arcAngle) * Math.sin(corners.leftArcLength) / Math.sin(arcAngle));
            double sideLength = Math.asin(Math.sin(corners.leftArcLength) / Math.sin(arcAngle));
            if (arcLength < centerArcLength)
            {
                corners.endLeft = LatLon.greatCircleEndPosition(this.endLocation, this.endLeftAzimuth.radians,
                    sideLength);
                corners.endLeftProj = LatLon.greatCircleEndPosition(this.endLocation, endAzimuth, arcLength);
            }
        }

        corners.endRight = LatLon.greatCircleEndPosition(this.endLocation, endAzimuth - (Math.PI / 2),
            corners.rightArcLength);
        corners.endRightProj = this.endLocation;
        if (this.endRightAzimuth != null)
        {
            double arcAngle = endAzimuth - this.endRightAzimuth.radians;
            double arcLength = Math.asin(Math.cos(arcAngle) * Math.sin(corners.rightArcLength) / Math.sin(arcAngle));
            double sideLength = Math.asin(Math.sin(corners.rightArcLength) / Math.sin(arcAngle));
            if (arcLength < centerArcLength)
            {
                corners.endRight = LatLon.greatCircleEndPosition(this.endLocation, this.endRightAzimuth.radians,
                    sideLength);
                corners.endRightProj = LatLon.greatCircleEndPosition(this.endLocation, endAzimuth, arcLength);
            }
        }

        return corners;
    }

    private void appendLocations(LatLon begin, LatLon middle, LatLon end, int numSegments, List result)
    {
        for (int i = 0; i <= numSegments; i++)
        {
            double amount = (double) i / (double) numSegments;
            result.add(LatLon.interpolateGreatCircle(amount, begin, middle));
        }

        for (int i = 1; i <= numSegments; i++) // skip the first segment, it's already covered in the above loop
        {
            double amount = (double) i / (double) numSegments;
            result.add(LatLon.interpolateGreatCircle(amount, middle, end));
        }
    }

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

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

        rs.addStateValueAsLatLon(context, "location1", this.beginLocation);
        rs.addStateValueAsLatLon(context, "location2", this.endLocation);
        rs.addStateValueAsDouble(context, "leftWidth", this.leftWidth);
        rs.addStateValueAsDouble(context, "rightWidth", this.rightWidth);
        rs.addStateValueAsBoolean(context, "enableStartCap", this.enableStartCap);
        rs.addStateValueAsBoolean(context, "enableEndCap", this.enableEndCap);
    }

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

        LatLon loc1 = rs.getStateValueAsLatLon(context, "location1");
        if (loc1 == null)
            loc1 = this.getLocations()[0];

        LatLon loc2 = rs.getStateValueAsLatLon(context, "location2");
        if (loc2 == null)
            loc2 = this.getLocations()[1];

        this.setLocations(loc1, loc2);

        Double lw = rs.getStateValueAsDouble(context, "leftWidth");
        if (lw == null)
            lw = this.getWidths()[0];

        Double rw = rs.getStateValueAsDouble(context, "rightWidth");
        if (rw == null)
            rw = this.getWidths()[1];

        this.setWidths(lw, rw);

        Boolean enableStart = rs.getStateValueAsBoolean(context, "enableStartCap");
        if (enableStart == null)
            enableStart = this.isEnableCaps()[0];

        Boolean enableEnd = rs.getStateValueAsBoolean(context, "enableEndCap");
        if (enableEnd == null)
            enableEnd = this.isEnableCaps()[1];

        this.setEnableCaps(enableStart, enableEnd);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy