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

gov.nasa.worldwind.render.airspaces.Polygon 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.avlist.AVKey;
import gov.nasa.worldwind.cache.Cacheable;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.Box;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

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

/**
 * @author tag
 * @version $Id: Polygon.java 2309 2014-09-17 00:04:08Z tgaskins $
 */
public class Polygon extends AbstractAirspace
{
    protected static final int DEFAULT_SUBDIVISIONS = 3;
    protected static final int MINIMAL_GEOMETRY_SUBDIVISIONS = 2;

    private List locations = new ArrayList();
    private boolean enableCaps = true;
    private int subdivisions = DEFAULT_SUBDIVISIONS;

    public Polygon(Polygon source)
    {
        super(source);

        this.enableCaps = source.enableCaps;
        this.subdivisions = source.subdivisions;

        this.addLocations(source.locations);
        this.makeDefaultDetailLevels();
    }

    public Polygon(Iterable locations)
    {
        this.addLocations(locations);
        this.makeDefaultDetailLevels();
    }

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

    public Polygon()
    {
        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(SUBDIVISIONS, 4);
        level.setValue(DISABLE_TERRAIN_CONFORMANCE, false);
        levels.add(level);

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

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

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

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

        this.setDetailLevels(levels);
    }

    public List getLocations()
    {
        return Collections.unmodifiableList(this.locations);
    }

    public void setLocations(Iterable locations)
    {
        this.locations.clear();
        this.addLocations(locations);
    }

    protected List getLocationList()
    {
        return this.locations;
    }

    protected void addLocations(Iterable newLocations)
    {
        if (newLocations != null)
        {
            for (LatLon ll : newLocations)
            {
                if (ll != null)
                    this.locations.add(ll);
            }
        }

        this.invalidateAirspaceData();
    }

    public boolean isEnableCaps()
    {
        return this.enableCaps;
    }

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

    public Position getReferencePosition()
    {
        return this.computeReferencePosition(this.locations, this.getAltitudes());
    }

    protected Extent computeExtent(Globe globe, double verticalExaggeration)
    {
        List points = this.computeMinimalGeometry(globe, verticalExaggeration);
        if (points == null || points.isEmpty())
            return null;

        // Add a point at the center of this polygon to the points used to compute its extent. The center point captures
        // the curvature of the globe when the polygon's minimal geometry only contain any points near the polygon's
        // edges.
        Vec4 centerPoint = Vec4.computeAveragePoint(points);
        LatLon centerLocation = globe.computePositionFromPoint(centerPoint);
        this.makeExtremePoints(globe, verticalExaggeration, Arrays.asList(centerLocation), points);

        return Box.computeBoundingBox(points);
    }

    @Override
    protected List computeMinimalGeometry(Globe globe, double verticalExaggeration)
    {
        List locations = this.getLocations();
        if (locations == null || locations.isEmpty())
            return null;

        ArrayList copyOfLocations = new ArrayList(locations);
        ArrayList tessellatedLocations = new ArrayList();
        this.makeTessellatedLocations(globe, MINIMAL_GEOMETRY_SUBDIVISIONS, copyOfLocations, tessellatedLocations);

        ArrayList points = new ArrayList();
        this.makeExtremePoints(globe, verticalExaggeration, tessellatedLocations, points);

        return points;
    }

    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 newLocations = LatLon.computeShiftedLocations(globe, oldRef, newRef, this.getLocations());
        this.setLocations(newLocations);

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

        int count = this.locations.size();
        LatLon[] newLocations = new LatLon[count];
        for (int i = 0; i < count; i++)
        {
            LatLon ll = this.locations.get(i);
            double distance = LatLon.greatCircleDistance(oldRef, ll).radians;
            double azimuth = LatLon.greatCircleAzimuth(oldRef, ll).radians;
            newLocations[i] = LatLon.greatCircleEndPosition(newRef, azimuth, distance);
        }
        this.setLocations(Arrays.asList(newLocations));
    }

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

    @Override
    protected void updateSurfaceShape(DrawContext dc, SurfaceShape shape)
    {
        super.updateSurfaceShape(dc, shape);

        boolean mustDrawInterior = this.getActiveAttributes().isDrawInterior() && this.isEnableCaps();
        shape.getAttributes().setDrawInterior(mustDrawInterior); // suppress the shape interior when caps are disabled
    }

    @Override
    protected void regenerateSurfaceShape(DrawContext dc, SurfaceShape shape)
    {
        ((SurfacePolygon) shape).setOuterBoundary(this.locations);
    }

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

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

        this.subdivisions = subdivisions;
    }

    //**************************************************************//
    //********************  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)
    {
        this.doRenderGeometry(dc, drawStyle, this.locations, null);
    }

    protected void doRenderGeometry(DrawContext dc, String drawStyle, List locations, List edgeFlags)
    {
        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);
        }
        if (locations == null)
        {
            String message = "nullValue.LocationsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (locations.size() == 0)
            return;

        double[] altitudes = this.getAltitudes(dc.getVerticalExaggeration());
        boolean[] terrainConformant = this.isTerrainConforming();
        boolean enableCaps = this.isEnableCaps();
        int subdivisions = this.subdivisions;

        if (this.getAltitudeDatum()[0].equals(AVKey.ABOVE_GROUND_REFERENCE)
            || this.getAltitudeDatum()[1].equals(AVKey.ABOVE_GROUND_REFERENCE))
        {
            this.adjustForGroundReference(dc, terrainConformant, altitudes);
        }

        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 referenceCenter = this.computeReferenceCenter(dc);
        this.setExpiryTime(this.nextExpiryTime(dc, terrainConformant));
        this.clearElevationMap();

        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        OGLStackHandler ogsh = new OGLStackHandler();
        try
        {
            dc.getView().pushReferenceCenter(dc, referenceCenter);

            if (Airspace.DRAW_STYLE_FILL.equals(drawStyle))
            {
                if (enableCaps && !this.isAirspaceCollapsed())
                {
                    ogsh.pushAttrib(gl, GL2.GL_POLYGON_BIT);
                    gl.glEnable(GL.GL_CULL_FACE);
                    gl.glFrontFace(GL.GL_CCW);
                }

                this.drawPolygonFill(dc, locations, edgeFlags, altitudes, terrainConformant, enableCaps, subdivisions,
                    referenceCenter);
            }
            else if (Airspace.DRAW_STYLE_OUTLINE.equals(drawStyle))
            {
                this.drawPolygonOutline(dc, locations, edgeFlags, altitudes, terrainConformant, enableCaps,
                    subdivisions, referenceCenter);
            }
        }
        finally
        {
            dc.getView().popReferenceCenter(dc);
            ogsh.pop(gl);
        }
    }

    protected void adjustForGroundReference(DrawContext dc, boolean[] terrainConformant, double[] altitudes)
    {
        LatLon groundRef = this.getGroundReference();

        if (groundRef == null && this.getLocationList().size() > 0)
            groundRef = this.getLocationList().get(0);

        this.adjustForGroundReference(dc, terrainConformant, altitudes, groundRef); // no-op if groudRef is null
    }

    protected int computeEllipsoidalPolygon(Globe globe, List locations, List edgeFlags,
        Vec4[] points, Boolean[] edgeFlagArray, Matrix[] transform)
    {
        if (globe == null)
        {
            String message = Logging.getMessage("nullValue.GlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (locations == null)
        {
            String message = "nullValue.LocationsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (points == null)
        {
            String message = "nullValue.LocationsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (points.length < (1 + locations.size()))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength",
                "points.length < " + (1 + locations.size()));
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (transform == null)
        {
            String message = "nullValue.TransformIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (transform.length < 1)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength",
                "transform.length < 1");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Allocate space to hold the list of locations and location vertices.
        int locationCount = locations.size();

        // Compute the cartesian points for each location.
        for (int i = 0; i < locationCount; i++)
        {
            LatLon ll = locations.get(i);
            points[i] = globe.computeEllipsoidalPointFromPosition(ll.getLatitude(), ll.getLongitude(), 0.0);

            if (edgeFlagArray != null)
                edgeFlagArray[i] = (edgeFlags != null) ? edgeFlags.get(i) : true;
        }

        // Compute the average of the cartesian points.
        Vec4 centerPoint = Vec4.computeAveragePoint(Arrays.asList(points));

        // Test whether the polygon is closed. If it is not closed, repeat the first vertex.
        if (!points[0].equals(points[locationCount - 1]))
        {
            points[locationCount] = points[0];
            if (edgeFlagArray != null)
                edgeFlagArray[locationCount] = edgeFlagArray[0];

            locationCount++;
        }

        // Compute a transform that will map the cartesian points to a local coordinate system centered at the average
        // of the points and oriented with the globe surface.
        Position centerPos = globe.computePositionFromEllipsoidalPoint(centerPoint);
        Matrix tx = globe.computeEllipsoidalOrientationAtPosition(centerPos.latitude, centerPos.longitude,
            centerPos.elevation);
        Matrix txInv = tx.getInverse();
        // Map the cartesian points to a local coordinate space.
        for (int i = 0; i < locationCount; i++)
        {
            points[i] = points[i].transformBy4(txInv);
        }

        transform[0] = tx;

        return locationCount;
    }

    private void makePolygonVertices(int count, Vec4[] points, float[] vertices)
    {
        for (int i = 0; i < count; i++)
        {
            int index = 3 * i;
            vertices[index] = (float) points[i].x;
            vertices[index + 1] = (float) points[i].y;
            vertices[index + 2] = (float) points[i].z;
        }
    }

    //**************************************************************//
    //********************  Polygon  ******************//
    //**************************************************************//

    protected static class PolygonGeometry implements Cacheable
    {
        private Geometry fillIndexGeometry;
        private Geometry outlineIndexGeometry;
        private Geometry vertexGeometry;

        public PolygonGeometry()
        {
            this.fillIndexGeometry = new Geometry();
            this.outlineIndexGeometry = new Geometry();
            this.vertexGeometry = new Geometry();
        }

        public Geometry getFillIndexGeometry()
        {
            return this.fillIndexGeometry;
        }

        public Geometry getOutlineIndexGeometry()
        {
            return this.outlineIndexGeometry;
        }

        public Geometry getVertexGeometry()
        {
            return this.vertexGeometry;
        }

        public long getSizeInBytes()
        {
            long sizeInBytes = 0L;
            sizeInBytes += (this.fillIndexGeometry != null) ? this.fillIndexGeometry.getSizeInBytes() : 0L;
            sizeInBytes += (this.outlineIndexGeometry != null) ? this.outlineIndexGeometry.getSizeInBytes() : 0L;
            sizeInBytes += (this.vertexGeometry != null) ? this.vertexGeometry.getSizeInBytes() : 0L;

            return sizeInBytes;
        }
    }

    private PolygonGeometry getPolygonGeometry(DrawContext dc, List locations, List edgeFlags,
        double[] altitudes, boolean[] terrainConformant,
        boolean enableCaps, int subdivisions,
        Vec4 referenceCenter)
    {
        Object cacheKey = new Geometry.CacheKey(dc.getGlobe(), this.getClass(), "Polygon",
            locations, edgeFlags, altitudes[0], altitudes[1], terrainConformant[0], terrainConformant[1],
            enableCaps, subdivisions, referenceCenter);

        // Wrap geometry creation in a try/catch block. We do this to catch and handle OutOfMemoryErrors caused during
        // tessellation of the polygon vertices. If the polygon cannot be tessellated, we replace the polygon's
        // locations with an empty list to prevent subsequent tessellation attempts, and to avoid rendering a misleading
        // representation by omitting any part of the geometry.
        try
        {
            PolygonGeometry geom = (PolygonGeometry) this.getGeometryCache().getObject(cacheKey);
            if (geom == null || this.isExpired(dc, geom.getVertexGeometry()))
            {
                if (geom == null)
                    geom = new PolygonGeometry();
                this.makePolygon(dc, locations, edgeFlags, altitudes, terrainConformant, enableCaps, subdivisions,
                    referenceCenter, geom);
                this.updateExpiryCriteria(dc, geom.getVertexGeometry());
                this.getGeometryCache().add(cacheKey, geom);
            }

            return geom;
        }
        catch (OutOfMemoryError e)
        {
            String message = Logging.getMessage("generic.ExceptionWhileTessellating", this);
            Logging.logger().log(java.util.logging.Level.SEVERE, message, e);

            //noinspection ThrowableInstanceNeverThrown
            dc.addRenderingException(new WWRuntimeException(message, e));

            this.handleUnsuccessfulGeometryCreation();
            return null;
        }
    }

    protected void handleUnsuccessfulGeometryCreation()
    {
        // If creating the polygon geometry was unsuccessful, we modify the polygon to avoid any additional creation
        // attempts, and free any resources that the polygon won't use. This is done to gracefully handle
        // OutOfMemoryErrors throws while tessellating the polygon geometry.

        // Replace the polygon's locations with an empty list. This ensures that any rendering code won't attempt to
        // re-create the polygon's geometry.
        this.locations = Collections.emptyList();
        // Reinitialize the polygon, since we've replaced its locations with an empty list.
        this.invalidateAirspaceData();
    }

    private void drawPolygonFill(DrawContext dc, List locations, List edgeFlags,
        double[] altitudes, boolean[] terrainConformant,
        boolean enableCaps, int subdivisions,
        Vec4 referenceCenter)
    {
        PolygonGeometry geom = this.getPolygonGeometry(dc, locations, edgeFlags, altitudes, terrainConformant,
            enableCaps, subdivisions, referenceCenter);
        if (geom != null)
            this.drawGeometry(dc, geom.getFillIndexGeometry(), geom.getVertexGeometry());
    }

    private void drawPolygonOutline(DrawContext dc, List locations, List edgeFlags,
        double[] altitudes, boolean[] terrainConformant,
        boolean enableCaps, int subdivisions,
        Vec4 referenceCenter)
    {
        PolygonGeometry geom = this.getPolygonGeometry(dc, locations, edgeFlags, altitudes, terrainConformant,
            enableCaps, subdivisions, referenceCenter);
        if (geom != null)
            this.drawGeometry(dc, geom.getOutlineIndexGeometry(), geom.getVertexGeometry());
    }

    private void makePolygon(DrawContext dc, List locations, List edgeFlags,
        double[] altitudes, boolean[] terrainConformant,
        boolean enableCaps, int subdivisions,
        Vec4 referenceCenter,
        PolygonGeometry dest)
    {
        if (locations.size() == 0)
            return;

        GeometryBuilder gb = this.getGeometryBuilder();

        Vec4[] polyPoints = new Vec4[locations.size() + 1];
        Boolean[] polyEdgeFlags = new Boolean[locations.size() + 1];
        Matrix[] polyTransform = new Matrix[1];
        int polyCount = this.computeEllipsoidalPolygon(dc.getGlobe(), locations, edgeFlags, polyPoints, polyEdgeFlags,
            polyTransform);

        // Compute the winding order of the planar cartesian points. If the order is not counter-clockwise, then
        // reverse the locations and points ordering.
        int winding = gb.computePolygonWindingOrder2(0, polyCount, polyPoints);
        if (winding != GeometryBuilder.COUNTER_CLOCKWISE)
        {
            gb.reversePoints(0, polyCount, polyPoints);
            gb.reversePoints(0, polyCount, polyEdgeFlags);
        }

        float[] polyVertices = new float[3 * polyCount];
        this.makePolygonVertices(polyCount, polyPoints, polyVertices);

        int fillDrawMode = GL.GL_TRIANGLES;
        int outlineDrawMode = GL.GL_LINES;

        int fillIndexCount = 0;
        int outlineIndexCount = 0;
        int vertexCount = 0;

        GeometryBuilder.IndexedTriangleArray ita = null;

        fillIndexCount += this.getEdgeFillIndexCount(polyCount, subdivisions);
        outlineIndexCount += this.getEdgeOutlineIndexCount(polyCount, subdivisions, polyEdgeFlags);
        vertexCount += this.getEdgeVertexCount(polyCount, subdivisions);

        if (enableCaps)
        {
            ita = gb.tessellatePolygon2(0, polyCount, polyVertices);
            for (int i = 0; i < subdivisions; i++)
            {
                gb.subdivideIndexedTriangleArray(ita);
            }

            fillIndexCount += ita.getIndexCount();
            vertexCount += ita.getVertexCount();
            // Bottom cap isn't drawn if airspace is collapsed.
            if (!this.isAirspaceCollapsed())
            {
                fillIndexCount += ita.getIndexCount();
                vertexCount += ita.getVertexCount();
            }
        }

        int[] fillIndices = new int[fillIndexCount];
        int[] outlineIndices = new int[outlineIndexCount];
        float[] vertices = new float[3 * vertexCount];
        float[] normals = new float[3 * vertexCount];

        int fillIndexPos = 0;
        int outlineIndexPos = 0;
        int vertexPos = 0;

        this.makeEdge(dc, polyCount, polyVertices, polyEdgeFlags, altitudes, terrainConformant, subdivisions,
            GeometryBuilder.OUTSIDE, polyTransform[0], referenceCenter,
            fillIndexPos, fillIndices, outlineIndexPos, outlineIndices, vertexPos, vertices, normals);
        fillIndexPos += this.getEdgeFillIndexCount(polyCount, subdivisions);
        outlineIndexPos += this.getEdgeOutlineIndexCount(polyCount, subdivisions, polyEdgeFlags);
        vertexPos += this.getEdgeVertexCount(polyCount, subdivisions);

        if (enableCaps)
        {
            this.makeCap(dc, ita, altitudes[1], terrainConformant[1], GeometryBuilder.OUTSIDE, polyTransform[0],
                referenceCenter, fillIndexPos, fillIndices, vertexPos, vertices, normals);
            fillIndexPos += ita.getIndexCount();
            vertexPos += ita.getVertexCount();
            // Bottom cap isn't drawn if airspace is collapsed.
            if (!this.isAirspaceCollapsed())
            {
                this.makeCap(dc, ita, altitudes[0], terrainConformant[0], GeometryBuilder.INSIDE, polyTransform[0],
                    referenceCenter, fillIndexPos, fillIndices, vertexPos, vertices, normals);
                fillIndexPos += ita.getIndexCount();
                vertexPos += ita.getVertexCount();
            }
        }

        dest.getFillIndexGeometry().setElementData(fillDrawMode, fillIndexCount, fillIndices);
        dest.getOutlineIndexGeometry().setElementData(outlineDrawMode, outlineIndexCount, outlineIndices);
        dest.getVertexGeometry().setVertexData(vertexCount, vertices);
        dest.getVertexGeometry().setNormalData(vertexCount, normals);
    }

    protected void makeTessellatedLocations(Globe globe, int subdivisions, List locations,
        List tessellatedLocations)
    {
        ArrayList points = new ArrayList();
        for (LatLon ll : locations)
        {
            points.add(globe.computeEllipsoidalPointFromPosition(ll.latitude, ll.longitude, 0));
        }

        //noinspection StringEquality
        if (WWMath.computeWindingOrderOfLocations(locations) != AVKey.COUNTER_CLOCKWISE)
            Collections.reverse(locations);

        Vec4 centerPoint = Vec4.computeAveragePoint(points);
        Position centerPos = globe.computePositionFromEllipsoidalPoint(centerPoint);
        Vec4 surfaceNormal = globe.computeEllipsoidalNormalAtLocation(centerPos.latitude, centerPos.longitude);

        int numPoints = points.size();
        float[] coords = new float[3 * numPoints];
        for (int i = 0; i < numPoints; i++)
        {
            points.get(i).toFloatArray(coords, 3 * i, 3);
        }

        GeometryBuilder gb = new GeometryBuilder();
        GeometryBuilder.IndexedTriangleArray tessellatedPoints = gb.tessellatePolygon(0, numPoints, coords,
            surfaceNormal);

        for (int i = 0; i < subdivisions; i++)
        {
            gb.subdivideIndexedTriangleArray(tessellatedPoints);
        }

        for (int i = 0; i < tessellatedPoints.getVertexCount(); i++)
        {
            Vec4 v = Vec4.fromFloatArray(tessellatedPoints.getVertices(), 3 * i, 3);
            tessellatedLocations.add(globe.computePositionFromEllipsoidalPoint(v));
        }
    }

    //**************************************************************//
    //********************  Polygon Edge        ********************//
    //**************************************************************//

    private int getEdgeFillIndexCount(int count, int subdivisions)
    {
        return (count - 1) * this.getSectionFillIndexCount(subdivisions);
    }

    private int getEdgeOutlineIndexCount(int count, int subdivisions, Boolean[] edgeFlags)
    {
        int sum = 0;
        for (int i = 0; i < count - 1; i++)
        {
            sum += this.getSectionOutlineIndexCount(subdivisions, edgeFlags[i], edgeFlags[i + 1]);
        }

        return sum;
    }

    private int getEdgeVertexCount(int count, int subdivisions)
    {
        return (count - 1) * this.getSectionVertexCount(subdivisions);
    }

    private int getSectionFillIndexCount(int subdivisions)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        return 6 * (gb.getSubdivisionPointsVertexCount(subdivisions) - 1);
    }

    private int getSectionOutlineIndexCount(int subdivisions, boolean beginEdgeFlag, boolean endEdgeFlag)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        int count = 4 * (gb.getSubdivisionPointsVertexCount(subdivisions) - 1);
        if (beginEdgeFlag)
            count += 2;
        if (endEdgeFlag)
            count += 2;

        return count;
    }

    private int getSectionVertexCount(int subdivisions)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        return 2 * gb.getSubdivisionPointsVertexCount(subdivisions);
    }

    private void makeEdge(DrawContext dc, int count, float[] locations, Boolean[] edgeFlags,
        double[] altitudes, boolean[] terrainConformant,
        int subdivisions, int orientation,
        Matrix locationTransform,
        Vec4 referenceCenter,
        int fillIndexPos, int[] fillIndices,
        int outlineIndexPos, int[] outlineIndices,
        int vertexPos, float[] vertices, float[] normals)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        gb.setOrientation(orientation);

        int sectionFillIndexCount = this.getSectionFillIndexCount(subdivisions);
        int sectionVertexCount = this.getSectionVertexCount(subdivisions);

        for (int i = 0; i < count - 1; i++)
        {
            boolean beginEdgeFlag = edgeFlags[i];
            boolean endEdgeFlag = edgeFlags[i + 1];

            this.makeSectionFillIndices(subdivisions, vertexPos, fillIndexPos, fillIndices);
            this.makeSectionOutlineIndices(subdivisions, vertexPos, outlineIndexPos, outlineIndices,
                beginEdgeFlag, endEdgeFlag);
            this.makeSectionVertices(dc, i, locations, altitudes, terrainConformant, subdivisions,
                locationTransform, referenceCenter, vertexPos, vertices);
            gb.makeIndexedTriangleArrayNormals(fillIndexPos, sectionFillIndexCount, fillIndices,
                vertexPos, sectionVertexCount, vertices, normals);

            fillIndexPos += sectionFillIndexCount;
            outlineIndexPos += this.getSectionOutlineIndexCount(subdivisions, beginEdgeFlag, endEdgeFlag);
            vertexPos += sectionVertexCount;
        }
    }

    private void makeSectionFillIndices(int subdivisions, int vertexPos, int indexPos, int[] indices)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        int count = gb.getSubdivisionPointsVertexCount(subdivisions);

        int index = indexPos;
        int pos, nextPos;
        for (int i = 0; i < count - 1; i++)
        {
            pos = vertexPos + 2 * i;
            nextPos = vertexPos + 2 * (i + 1);
            indices[index++] = pos + 1;
            indices[index++] = pos;
            indices[index++] = nextPos + 1;
            indices[index++] = nextPos + 1;
            indices[index++] = pos;
            indices[index++] = nextPos;
        }
    }

    private void makeSectionOutlineIndices(int subdivisions, int vertexPos, int indexPos, int[] indices,
        boolean beginEdgeFlag, boolean endEdgeFlag)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        int count = gb.getSubdivisionPointsVertexCount(subdivisions);

        int index = indexPos;
        int pos, nextPos;

        if (beginEdgeFlag)
        {
            pos = vertexPos;
            indices[index++] = pos;
            indices[index++] = pos + 1;
        }

        for (int i = 0; i < count - 1; i++)
        {
            pos = vertexPos + 2 * i;
            nextPos = vertexPos + 2 * (i + 1);
            indices[index++] = pos;
            indices[index++] = nextPos;
            indices[index++] = pos + 1;
            indices[index++] = nextPos + 1;
        }

        if (endEdgeFlag)
        {
            pos = vertexPos + 2 * (count - 1);
            indices[index++] = pos;
            indices[index] = pos + 1;
        }
    }

    private void makeSectionVertices(DrawContext dc, int locationPos, float[] locations,
        double[] altitude, boolean[] terrainConformant,
        int subdivisions,
        Matrix locationTransform,
        Vec4 referenceCenter,
        int vertexPos, float[] vertices)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        int numPoints = gb.getSubdivisionPointsVertexCount(subdivisions);

        Globe globe = dc.getGlobe();
        int index1 = 3 * locationPos;
        int index2 = 3 * (locationPos + 1);

        float[] locationVerts = new float[3 * numPoints];
        gb.makeSubdivisionPoints(
            locations[index1], locations[index1 + 1], locations[index1 + 2],
            locations[index2], locations[index2 + 1], locations[index2 + 2],
            subdivisions, locationVerts);

        for (int i = 0; i < numPoints; i++)
        {
            int index = 3 * i;
            Vec4 vec = new Vec4(locationVerts[index], locationVerts[index + 1], locationVerts[index + 2]);
            vec = vec.transformBy4(locationTransform);
            Position pos = globe.computePositionFromEllipsoidalPoint(vec); // ellipsoidal-coordinate point and transform

            for (int j = 0; j < 2; j++)
            {
                vec = this.computePointFromPosition(dc, pos.getLatitude(), pos.getLongitude(), altitude[j],
                    terrainConformant[j]); // final model-coordinate point

                index = 2 * i + j;
                index = 3 * (vertexPos + index);
                vertices[index] = (float) (vec.x - referenceCenter.x);
                vertices[index + 1] = (float) (vec.y - referenceCenter.y);
                vertices[index + 2] = (float) (vec.z - referenceCenter.z);
            }
        }
    }

    //**************************************************************//
    //********************  Polygon Cap         ********************//
    //**************************************************************//

    private void makeCap(DrawContext dc, GeometryBuilder.IndexedTriangleArray ita,
        double altitude, boolean terrainConformant,
        int orientation,
        Matrix locationTransform,
        Vec4 referenceCenter,
        int indexPos, int[] indices,
        int vertexPos, float[] vertices, float[] normals)
    {
        GeometryBuilder gb = this.getGeometryBuilder();
        Globe globe = dc.getGlobe();

        int indexCount = ita.getIndexCount();
        int vertexCount = ita.getVertexCount();
        int[] locationIndices = ita.getIndices();
        float[] locationVerts = ita.getVertices();

        this.copyIndexArray(indexCount, (orientation == GeometryBuilder.INSIDE), locationIndices,
            vertexPos, indexPos, indices);

        for (int i = 0; i < vertexCount; i++)
        {
            int index = 3 * i;
            Vec4 vec = new Vec4(locationVerts[index], locationVerts[index + 1], locationVerts[index + 2]);
            vec = vec.transformBy4(locationTransform);

            Position pos = globe.computePositionFromEllipsoidalPoint(vec); // ellipsoidal-coordinate point and transform
            vec = this.computePointFromPosition(dc, pos.getLatitude(), pos.getLongitude(), altitude,
                terrainConformant); // final model-coordinate point

            index = 3 * (vertexPos + i);
            vertices[index] = (float) (vec.x - referenceCenter.x);
            vertices[index + 1] = (float) (vec.y - referenceCenter.y);
            vertices[index + 2] = (float) (vec.z - referenceCenter.z);
        }

        gb.makeIndexedTriangleArrayNormals(indexPos, indexCount, indices, vertexPos, vertexCount, vertices,
            normals);
    }

    private void copyIndexArray(int indexCount, boolean reverseWinding, int[] indices,
        int destVertexPos, int destIndexPos, int[] dest)
    {
        for (int i = 0; i < indexCount; i += 3)
        {
            if (reverseWinding)
            {
                dest[destIndexPos + i] = destVertexPos + indices[i + 2];
                dest[destIndexPos + i + 1] = destVertexPos + indices[i + 1];
                dest[destIndexPos + i + 2] = destVertexPos + indices[i];
            }
            else
            {
                dest[destIndexPos + i] = destVertexPos + indices[i];
                dest[destIndexPos + i + 1] = destVertexPos + indices[i + 1];
                dest[destIndexPos + i + 2] = destVertexPos + indices[i + 2];
            }
        }
    }

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

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

        rs.addStateValueAsBoolean(context, "enableCaps", this.enableCaps);

        if (this.locations != null)
            rs.addStateValueAsLatLonList(context, "locations", this.locations);
    }

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

        Boolean booleanState = rs.getStateValueAsBoolean(context, "enableCaps");
        if (booleanState != null)
            this.setEnableCaps(booleanState);

        List locations = rs.getStateValueAsLatLonList(context, "locations");
        if (locations != null)
            this.setLocations(locations);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy