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

gov.nasa.worldwind.formats.shapefile.ShapefilePolylines Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.formats.shapefile;

import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.GpuResourceCache;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

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

/**
 * @author dcollins
 * @version $Id: ShapefilePolylines.java 2303 2014-09-14 22:33:36Z dcollins $
 */
public class ShapefilePolylines extends ShapefileRenderable implements OrderedRenderable, PreRenderable
{
    public static class Record extends ShapefileRenderable.Record
    {
        // Data structures supporting drawing.
        protected Tile tile;
        protected IntBuffer outlineIndices;

        public Record(ShapefileRenderable shapefileRenderable, ShapefileRecord shapefileRecord)
        {
            super(shapefileRenderable, shapefileRecord);
        }
    }

    protected static class RecordGroup
    {
        // Record group properties.
        public final ShapeAttributes attributes;
        public ArrayList records = new ArrayList();
        // Data structures supporting drawing.
        public IntBuffer indices;
        public Range outlineIndexRange = new Range(0, 0);
        public Object vboKey = new Object();

        public RecordGroup(ShapeAttributes attributes)
        {
            this.attributes = attributes;
        }
    }

    protected static class Tile implements OrderedRenderable, SurfaceRenderable
    {
        // Tile properties.
        public ShapefileRenderable shapefileRenderable;
        public final Sector sector;
        public final int level;
        // Tile records, attribute groups and child tiles.
        public ArrayList records = new ArrayList();
        public ArrayList attributeGroups = new ArrayList();
        public long attributeStateID;
        public Tile[] children;
        // Tile shape data.
        public FloatBuffer vertices;
        public int vertexStride;
        public Vec4 referencePoint;
        public Matrix transformMatrix;
        public Object vboKey = new Object();

        public Tile(ShapefileRenderable shapefileRenderable, Sector sector, int level)
        {
            this.shapefileRenderable = shapefileRenderable;
            this.sector = sector;
            this.level = level;
        }

        @Override
        public double getDistanceFromEye()
        {
            return 0; // distance from eye is irrelevant for ordered surface renderables
        }

        @Override
        public List getSectors(DrawContext dc)
        {
            return Arrays.asList(this.sector);
        }

        @Override
        public Object getStateKey(DrawContext dc)
        {
            return new TileStateKey(this);
        }

        @Override
        public void pick(DrawContext dc, Point pickPoint)
        {
        }

        @Override
        public void render(DrawContext dc)
        {
            ((ShapefilePolylines) this.shapefileRenderable).renderTile(dc, this);
        }
    }

    protected static class TileStateKey
    {
        protected Tile tile;
        protected long attributeStateID;
        protected ShapeAttributes[] attributeGroups;

        public TileStateKey(Tile tile)
        {
            this.tile = tile;
            this.attributeStateID = tile.attributeStateID;
            this.attributeGroups = new ShapeAttributes[tile.attributeGroups.size()];

            for (int i = 0; i < this.attributeGroups.length; i++)
            {
                this.attributeGroups[i] = tile.attributeGroups.get(i).attributes.copy();
            }
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            TileStateKey that = (TileStateKey) o;
            return this.tile.equals(that.tile)
                && this.attributeStateID == that.attributeStateID
                && Arrays.equals(this.attributeGroups, that.attributeGroups);
        }

        @Override
        public int hashCode()
        {
            int result = this.tile.hashCode();
            result = 31 * result + (int) (this.attributeStateID ^ (this.attributeStateID >>> 32));
            result = 31 * result + Arrays.hashCode(this.attributeGroups);
            return result;
        }
    }

    /** The default outline pick width. */
    protected static final int DEFAULT_OUTLINE_PICK_WIDTH = 10;

    // Tile quadtree structures.
    protected Tile rootTile;
    protected int tileMaxLevel = 3;
    protected int tileMaxCapacity = 10000;
    // Data structures supporting polygon tessellation and drawing.
    protected ArrayList currentTiles = new ArrayList();
    protected PolylineTessellator tess = new PolylineTessellator();
    protected byte[] colorByteArray = new byte[3];
    protected float[] colorFloatArray = new float[4];
    protected double[] matrixArray = new double[16];
    // Data structures supporting picking.
    protected int outlinePickWidth = DEFAULT_OUTLINE_PICK_WIDTH;
    protected Layer pickLayer;
    protected PickSupport pickSupport = new PickSupport();
    protected SurfaceObjectTileBuilder pickTileBuilder = new SurfaceObjectTileBuilder(new Dimension(512, 512),
        GL2.GL_RGBA8, false, false);
    protected ByteBuffer pickColors;
    protected Object pickColorsVboKey = new Object();

    /**
     * Creates a new ShapefilePolylines with the specified shapefile. The normal attributes and the highlight attributes
     * for each ShapefileRenderable.Record are assigned default values. In order to modify ShapefileRenderable.Record
     * shape attributes or key-value attributes during construction, use {@link #ShapefilePolylines(gov.nasa.worldwind.formats.shapefile.Shapefile,
     * gov.nasa.worldwind.render.ShapeAttributes, gov.nasa.worldwind.render.ShapeAttributes,
     * gov.nasa.worldwind.formats.shapefile.ShapefileRenderable.AttributeDelegate)}.
     *
     * @param shapefile The shapefile to display.
     *
     * @throws IllegalArgumentException if the shapefile is null.
     */
    public ShapefilePolylines(Shapefile shapefile)
    {
        if (shapefile == null)
        {
            String msg = Logging.getMessage("nullValue.ShapefileIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.init(shapefile, null, null, null);
    }

    /**
     * Creates a new ShapefilePolylines with the specified shapefile. The normal attributes, the highlight attributes
     * and the attribute delegate are optional. Specifying a non-null value for normalAttrs or highlightAttrs causes
     * each ShapefileRenderable.Record to adopt those attributes. Specifying a non-null value for the attribute delegate
     * enables callbacks during creation of each ShapefileRenderable.Record. See {@link
     * gov.nasa.worldwind.formats.shapefile.ShapefileRenderable.AttributeDelegate} for more information.
     *
     * @param shapefile         The shapefile to display.
     * @param normalAttrs       The normal attributes for each ShapefileRenderable.Record. May be null to use the
     *                          default attributes.
     * @param highlightAttrs    The highlight attributes for each ShapefileRenderable.Record. May be null to use the
     *                          default highlight attributes.
     * @param attributeDelegate Optional callback for configuring each ShapefileRenderable.Record's shape attributes and
     *                          key-value attributes. May be null.
     *
     * @throws IllegalArgumentException if the shapefile is null.
     */
    public ShapefilePolylines(Shapefile shapefile, ShapeAttributes normalAttrs, ShapeAttributes highlightAttrs,
        AttributeDelegate attributeDelegate)
    {
        if (shapefile == null)
        {
            String msg = Logging.getMessage("nullValue.ShapefileIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.init(shapefile, normalAttrs, highlightAttrs, attributeDelegate);
    }

    /**
     * Indicates the outline line width to use during picking. A larger width than normal typically makes the outline
     * easier to pick.
     *
     * @return the outline line width used during picking.
     */
    public int getOutlinePickWidth()
    {
        return this.outlinePickWidth;
    }

    /**
     * Specifies the outline line width to use during picking. A larger width than normal typically makes the outline
     * easier to pick.
     * 

* Note that the size of the pick aperture also affects the precision necessary to pick. * * @param outlinePickWidth the outline pick width. The default is 10. * * @throws IllegalArgumentException if the width is less than 0. */ public void setOutlinePickWidth(int outlinePickWidth) { if (outlinePickWidth < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", "width < 0"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.outlinePickWidth = outlinePickWidth; } @Override public double getDistanceFromEye() { return 0; } @Override public void preRender(DrawContext dc) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (!this.visible) return; if (this.getRecordCount() == 0) // Shapefile is empty or contains only null records. return; // Assemble the tiles used for rendering, then cause those tiles to be drawn into the scene controller's // composite surface object tiles. this.assembleTiles(dc); for (Tile tile : this.currentTiles) { dc.addOrderedSurfaceRenderable(tile); } // Assemble the tiles used for picking, then build a set of surface object tiles containing unique colors for // each record. if (dc.getCurrentLayer().isPickEnabled()) { try { dc.enablePickingMode(); this.assembleTiles(dc); this.pickSupport.clearPickList(); this.pickTileBuilder.setForceTileUpdates(true); // force pick tiles to update with new pick colors this.pickTileBuilder.buildTiles(dc, this.currentTiles); // draw tiles and add candidates to pickSupport } finally { dc.disablePickingMode(); } } } @Override public void pick(DrawContext dc, Point pickPoint) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (!this.visible) return; int recordCount = this.getRecordCount(); if (recordCount == 0) // Shapefile is empty or contains only null records. return; GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. try { // pick list cleared in preRender this.pickSupport.beginPicking(dc); gl.glEnable(GL.GL_CULL_FACE); dc.getGeographicSurfaceTileRenderer().setUseImageTilePickColors(true); dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.pickTileBuilder.getTiles(dc)); } finally { dc.getGeographicSurfaceTileRenderer().setUseImageTilePickColors(false); gl.glDisable(GL.GL_CULL_FACE); this.pickSupport.endPicking(dc); this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer); this.pickTileBuilder.clearTiles(dc); } } @Override public void render(DrawContext dc) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (!this.visible) return; if (this.getRecordCount() == 0) // Shapefile is empty or contains only null records. return; if (dc.isPickingMode() && this.pickTileBuilder.getTileCount(dc) > 0) { this.pickLayer = dc.getCurrentLayer(); dc.addOrderedSurfaceRenderable(this); // perform the pick during ordered surface rendering } } @Override protected void assembleRecords(Shapefile shapefile) { this.rootTile = new Tile(this, this.sector, 0); super.assembleRecords(shapefile); if (this.mustSplitTile(this.rootTile)) { this.splitTile(this.rootTile); } this.rootTile.records.trimToSize(); // Reduce memory overhead from unused ArrayList capacity. } @Override protected boolean mustAssembleRecord(ShapefileRecord shapefileRecord) { return super.mustAssembleRecord(shapefileRecord) && (shapefileRecord.isPolylineRecord() || shapefileRecord.isPolygonRecord()); // accept both polyline and polygon records } @Override protected void assembleRecord(ShapefileRecord shapefileRecord) { Record record = this.createRecord(shapefileRecord); this.addRecord(shapefileRecord, record); this.rootTile.records.add(record); record.tile = this.rootTile; } protected ShapefilePolylines.Record createRecord(ShapefileRecord shapefileRecord) { return new ShapefilePolylines.Record(this, shapefileRecord); } @Override protected void recordDidChange(ShapefileRenderable.Record record) { Tile tile = ((ShapefilePolylines.Record) record).tile; if (tile != null) // tile is null when attributes are specified during construction { this.invalidateTileAttributeGroups(tile); } } protected boolean mustSplitTile(Tile tile) { return tile.level < this.tileMaxLevel && tile.records.size() > this.tileMaxCapacity; } protected void splitTile(Tile tile) { // Create four child tiles by subdividing the tile's sector in latitude and longitude. Sector[] childSectors = tile.sector.subdivide(); tile.children = new Tile[4]; tile.children[0] = new Tile(this, childSectors[0], tile.level + 1); tile.children[1] = new Tile(this, childSectors[1], tile.level + 1); tile.children[2] = new Tile(this, childSectors[2], tile.level + 1); tile.children[3] = new Tile(this, childSectors[3], tile.level + 1); // Move any records completely contained in a child tile's sector into the child's list of records. This may // include records that are marked as not visible, as recomputing the tile tree for record visibility changes // would be expensive. Iterator iterator = tile.records.iterator(); while (iterator.hasNext()) { Record record = iterator.next(); for (int i = 0; i < 4; i++) { if (tile.children[i].sector.contains(record.sector)) { tile.children[i].records.add(record); // add it to the child record.tile = tile.children[i]; // assign the record's tile iterator.remove(); // remove it from the parent break; // skip to the next record } } } // Recursively split child tiles as necessary, moving their records into each child's descendants. The recursive // split stops when a child tile reaches a maximum level, or when the number of records contained within the // tile is small enough. for (int i = 0; i < 4; i++) { if (this.mustSplitTile(tile.children[i])) { this.splitTile(tile.children[i]); } tile.children[i].records.trimToSize(); // Reduce memory overhead from unused ArrayList capacity. } } protected void assembleTiles(DrawContext dc) { this.currentTiles.clear(); this.addTileOrDescendants(dc, this.rootTile); } protected void addTileOrDescendants(DrawContext dc, Tile tile) { // Determine whether or not the tile is visible. If the tile is not visible, then neither are the tile's records // or the tile's children. Note that a tile with no records may have children, so we can't use the tile's record // count as a determination of whether or not to test its children. if (!this.isTileVisible(dc, tile)) { return; } // Add the tile to the list of tiles to draw, regenerating the tile's geometry and the tile's attribute groups // as necessary. if (tile.records.size() > 0) { if (this.mustRegenerateTileGeometry(tile)) { this.regenerateTileGeometry(tile); } if (this.mustAssembleTileAttributeGroups(tile)) { this.assembleTileAttributeGroups(tile); } this.currentTiles.add(tile); } // Process the tile's children, if any. if (tile.children != null) { for (Tile childTile : tile.children) { this.addTileOrDescendants(dc, childTile); } } } protected boolean isTileVisible(DrawContext dc, Tile tile) { Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), tile.sector); if (dc.isSmall(extent, 1)) { return false; } if (dc.isPickingMode()) { return dc.getPickFrustums().intersectsAny(extent); } return dc.getView().getFrustumInModelCoordinates().intersects(extent); } protected boolean mustRegenerateTileGeometry(Tile tile) { return tile.vertices == null; } protected void regenerateTileGeometry(Tile tile) { this.tessellateTile(tile); } protected void tessellateTile(Tile tile) { int numPoints = 0; for (Record record : tile.records) { numPoints += record.numberOfPoints; } // Allocate the geographic coordinate vertices to hold the coordinates for all records in the tile. The records // in the tile never changes, so the number of vertices in the tile never changes. int vertexStride = 2; FloatBuffer vertices = Buffers.newDirectFloatBuffer(vertexStride * numPoints); double[] location = new double[2]; float[] vertex = new float[2]; Vec4 rp = null; // Generate the geographic coordinate vertices and indices for all records in the tile. This may include records // that are marked as not visible, as recomputing the vertices and indices for record visibility changes would // be expensive. The tessellated indices are generated only once, since each record's indices never change. for (Record record : tile.records) { this.tess.reset(); for (int i = 0; i < record.getBoundaryCount(); i++) { this.tess.beginPolyline(); VecBuffer points = record.getBoundaryPoints(i); for (int j = 0; j < points.getSize(); j++) { points.get(j, location); double x = location[0]; // map longitude to x double y = location[1]; // map latitude to y int index = vertices.position() / vertexStride; this.tess.addVertex(x, y, 0, index); if (rp == null) // first vertex in the tile { rp = new Vec4(x, y, 0); } vertex[0] = (float) (x - rp.x); vertex[1] = (float) (y - rp.y); vertices.put(vertex); } this.tess.endPolyline(); } this.assembleRecordIndices(this.tess, record); } tile.vertices = (FloatBuffer) vertices.rewind(); tile.vertexStride = vertexStride; tile.referencePoint = rp; tile.transformMatrix = Matrix.fromTranslation(rp.x, rp.y, rp.z); } protected void assembleRecordIndices(PolylineTessellator tessellator, Record record) { // Get the tessellated boundary indices representing a line segment tessellation of the record parts. // Flip each buffer in order to limit the buffer range we use to values added during tessellation. IntBuffer tessBoundary = (IntBuffer) tessellator.getIndices().flip(); // Allocate and fill the record's outline indices. IntBuffer outlineIndices = IntBuffer.allocate(tessBoundary.remaining()); outlineIndices.put(tessBoundary); record.outlineIndices = (IntBuffer) outlineIndices.rewind(); } protected void invalidateTileAttributeGroups(Tile tile) { tile.attributeGroups.clear(); } protected boolean mustAssembleTileAttributeGroups(Tile tile) { return tile.attributeGroups.isEmpty(); } protected void assembleTileAttributeGroups(Tile tile) { tile.attributeGroups.clear(); tile.attributeStateID++; // Assemble the tile's records into groups with common attributes. Attributes are compared using the instance's // address, so subsequent changes to an Attribute instance will be reflected in the record group automatically. // We take care to avoid assembling groups based on any Attribute property, as those properties may change // without re-assembling these groups. However, changes to a record's visibility state, highlight state, normal // attributes reference and highlight attributes reference invalidate this grouping. HashMap attrMap = new HashMap(); for (Record record : tile.records) { if (!record.isVisible()) // ignore records marked as not visible continue; ShapeAttributes attrs = this.determineActiveAttributes(record); RecordGroup group = attrMap.get(attrs); if (group == null) // create a new group if one doesn't already exist { group = new RecordGroup(attrs); attrMap.put(attrs, group); // add it to the map to prevent duplicates tile.attributeGroups.add(group); // add it to the tile's attribute group list } group.records.add(record); group.outlineIndexRange.length += record.outlineIndices.remaining(); } // Make the indices for each record group. We take care to make indices for both the interior and the outline, // regardless of the current state of Attributes.isDrawInterior and Attributes.isDrawOutline. This enable these // properties change state without needing to re-assemble these groups. for (RecordGroup group : tile.attributeGroups) { int indexCount = group.outlineIndexRange.length; IntBuffer indices = Buffers.newDirectIntBuffer(indexCount); group.outlineIndexRange.location = indices.position(); for (Record record : group.records) // assemble the group's line indices in a single contiguous range { indices.put(record.outlineIndices); record.outlineIndices.rewind(); } group.indices = (IntBuffer) indices.rewind(); group.records.clear(); group.records.trimToSize(); // Reduce memory overhead from unused ArrayList capacity. } } protected void renderTile(DrawContext dc, Tile tile) { this.beginDrawing(dc); try { if (dc.isPickingMode()) { this.drawTileInUniqueColors(dc, tile); } else { this.drawTile(dc, tile); } } finally { this.endDrawing(dc); } } protected void beginDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glDisable(GL.GL_DEPTH_TEST); gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); // all drawing uses vertex arrays gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPushMatrix(); if (!dc.isPickingMode()) { gl.glEnable(GL.GL_BLEND); gl.glEnable(GL.GL_LINE_SMOOTH); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } } protected void endDrawing(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glEnable(GL.GL_DEPTH_TEST); gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glColor4f(1, 1, 1, 1); gl.glLineWidth(1); gl.glPopMatrix(); if (!dc.isPickingMode()) { gl.glDisable(GL.GL_BLEND); gl.glDisable(GL.GL_LINE_SMOOTH); gl.glBlendFunc(GL.GL_ONE, GL.GL_ZERO); } if (dc.getGLRuntimeCapabilities().isUseVertexBufferObject()) { gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0); } } protected void drawTile(DrawContext dc, Tile tile) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. int[] vboId = null; boolean useVbo = dc.getGLRuntimeCapabilities().isUseVertexBufferObject(); if (useVbo && (vboId = (int[]) dc.getGpuResourceCache().get(tile.vboKey)) == null) { long vboSize = 4 * tile.vertices.remaining(); // 4 bytes for each float vertex component vboId = new int[1]; gl.glGenBuffers(1, vboId, 0); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vboId[0]); gl.glBufferData(GL.GL_ARRAY_BUFFER, vboSize, tile.vertices, GL.GL_STATIC_DRAW); gl.glVertexPointer(tile.vertexStride, GL.GL_FLOAT, 0, 0); dc.getGpuResourceCache().put(tile.vboKey, vboId, GpuResourceCache.VBO_BUFFERS, vboSize); } else if (useVbo) { gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vboId[0]); gl.glVertexPointer(tile.vertexStride, GL.GL_FLOAT, 0, 0); } else { gl.glVertexPointer(tile.vertexStride, GL.GL_FLOAT, 0, tile.vertices); } SurfaceTileDrawContext sdc = (SurfaceTileDrawContext) dc.getValue(AVKey.SURFACE_TILE_DRAW_CONTEXT); Matrix modelview = sdc.getModelviewMatrix().multiply(tile.transformMatrix); modelview.toArray(this.matrixArray, 0, false); gl.glLoadMatrixd(this.matrixArray, 0); for (RecordGroup attrGroup : tile.attributeGroups) { this.drawTileAttributeGroup(dc, attrGroup); } } protected void drawTileAttributeGroup(DrawContext dc, RecordGroup attributeGroup) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. ShapeAttributes attrs = attributeGroup.attributes; if (!attrs.isDrawOutline()) return; int[] vboId = null; boolean useVbo = dc.getGLRuntimeCapabilities().isUseVertexBufferObject(); if (useVbo && (vboId = (int[]) dc.getGpuResourceCache().get(attributeGroup.vboKey)) == null) { long vboSize = 4 * attributeGroup.indices.remaining(); // 4 bytes for each unsigned int index vboId = new int[1]; gl.glGenBuffers(1, vboId, 0); gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, vboId[0]); gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, vboSize, attributeGroup.indices, GL.GL_STATIC_DRAW); dc.getGpuResourceCache().put(attributeGroup.vboKey, vboId, GpuResourceCache.VBO_BUFFERS, vboSize); } else if (useVbo) { gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, vboId[0]); } if (!dc.isPickingMode()) { float[] color = this.colorFloatArray; attrs.getOutlineMaterial().getDiffuse().getRGBComponents(color); gl.glColor4f(color[0], color[1], color[2], color[3]); } if (dc.isPickingMode() && attrs.getOutlineWidth() < this.getOutlinePickWidth()) gl.glLineWidth(this.getOutlinePickWidth()); else gl.glLineWidth((float) attrs.getOutlineWidth()); if (useVbo) { gl.glDrawElements(GL.GL_LINES, attributeGroup.indices.remaining(), GL.GL_UNSIGNED_INT, 0); } else { gl.glDrawElements(GL.GL_LINES, attributeGroup.indices.remaining(), GL.GL_UNSIGNED_INT, attributeGroup.indices); } } protected void drawTileInUniqueColors(DrawContext dc, Tile tile) { GL2 gl = dc.getGL().getGL2(); int pickColorsSize = 3 * (tile.vertices.remaining() / tile.vertexStride); // 1 RGB color for each XY vertex if (this.pickColors == null || this.pickColors.capacity() < pickColorsSize) { this.pickColors = Buffers.newDirectByteBuffer(pickColorsSize); dc.getGpuResourceCache().remove(this.pickColorsVboKey); // remove any associated VBO from GPU memory } this.pickColors.clear(); ByteBuffer colors; int[] vboId = null; boolean useVbo = dc.getGLRuntimeCapabilities().isUseVertexBufferObject(); if (useVbo && (vboId = (int[]) dc.getGpuResourceCache().get(this.pickColorsVboKey)) == null) { vboId = new int[1]; gl.glGenBuffers(1, vboId, 0); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vboId[0]); gl.glBufferData(GL.GL_ARRAY_BUFFER, this.pickColors.remaining(), this.pickColors, GL2.GL_DYNAMIC_DRAW); dc.getGpuResourceCache().put(this.pickColorsVboKey, vboId, GpuResourceCache.VBO_BUFFERS, this.pickColors.remaining()); colors = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY); } else if (useVbo) { gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vboId[0]); colors = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY); } else { colors = pickColors; } byte[] vertexColors = this.colorByteArray; for (Record record : tile.records) { // Assign each record a unique RGB color. Generate vertex colors for every record - regardless of its // visibility - since the tile's color array must match the tile's vertex array. Color color = dc.getUniquePickColor(); this.pickSupport.addPickableObject(color.getRGB(), record); vertexColors[0] = (byte) color.getRed(); vertexColors[1] = (byte) color.getGreen(); vertexColors[2] = (byte) color.getBlue(); // Add the unique color each vertex of the record. for (int i = 0; i < record.numberOfPoints; i++) { colors.put(vertexColors, 0, 3); } } colors.flip(); try { this.pickSupport.beginPicking(dc); gl.glEnableClientState(GL2.GL_COLOR_ARRAY); if (useVbo) { gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER); gl.glColorPointer(3, GL.GL_UNSIGNED_BYTE, 0, 0); } else { gl.glColorPointer(3, GL.GL_UNSIGNED_BYTE, 0, colors); } this.drawTile(dc, tile); } finally { gl.glDisableClientState(GL2.GL_COLOR_ARRAY); this.pickSupport.endPicking(dc); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy