Statistics
| Revision:

gvsig-3d / 2.1 / branches / org.gvsig.view3d_vector_and_extrusion_2.3 / org.gvsig.view3d / org.gvsig.view3d / org.gvsig.view3d.lib / org.gvsig.view3d.lib.impl / src / main / java / org / gvsig / view3d / lib / impl / layers / vector / ExtrudedPolygon.java @ 708

History | View | Annotate | Download (95.6 KB)

1
/*
2
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
3
 * National Aeronautics and Space Administration.
4
 * All Rights Reserved.
5
 */
6

    
7
package org.gvsig.view3d.lib.impl.layers.vector;
8

    
9
import java.io.File;
10
import java.io.IOException;
11
import java.nio.FloatBuffer;
12
import java.nio.IntBuffer;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.Collections;
16
import java.util.HashMap;
17
import java.util.Iterator;
18
import java.util.List;
19
import java.util.Locale;
20

    
21
import javax.media.opengl.GL;
22
import javax.media.opengl.GL2;
23
import javax.media.opengl.glu.GLU;
24
import javax.xml.stream.XMLStreamException;
25
import javax.xml.stream.XMLStreamWriter;
26

    
27
import com.jogamp.common.nio.Buffers;
28

    
29
import gov.nasa.worldwind.Configuration;
30
import gov.nasa.worldwind.WorldWind;
31
import gov.nasa.worldwind.WorldWindow;
32
import gov.nasa.worldwind.avlist.AVKey;
33
import gov.nasa.worldwind.cache.ShapeDataCache;
34
import gov.nasa.worldwind.exception.WWRuntimeException;
35
import gov.nasa.worldwind.geom.Box;
36
import gov.nasa.worldwind.geom.Extent;
37
import gov.nasa.worldwind.geom.Intersection;
38
import gov.nasa.worldwind.geom.LatLon;
39
import gov.nasa.worldwind.geom.Line;
40
import gov.nasa.worldwind.geom.Position;
41
import gov.nasa.worldwind.geom.Sector;
42
import gov.nasa.worldwind.geom.Triangle;
43
import gov.nasa.worldwind.geom.Vec4;
44
import gov.nasa.worldwind.globes.Globe;
45
import gov.nasa.worldwind.ogc.kml.impl.KMLExportUtil;
46
import gov.nasa.worldwind.render.AbstractShape;
47
import gov.nasa.worldwind.render.BasicShapeAttributes;
48
import gov.nasa.worldwind.render.DrawContext;
49
import gov.nasa.worldwind.render.Material;
50
import gov.nasa.worldwind.render.ShapeAttributes;
51
import gov.nasa.worldwind.render.SurfacePolygon;
52
import gov.nasa.worldwind.render.SurfaceShape;
53
import gov.nasa.worldwind.render.WWTexture;
54
import gov.nasa.worldwind.terrain.Terrain;
55
import gov.nasa.worldwind.util.GLUTessellatorSupport;
56
import gov.nasa.worldwind.util.Logging;
57
import gov.nasa.worldwind.util.OGLStackHandler;
58
import gov.nasa.worldwind.util.WWBufferUtil;
59
import gov.nasa.worldwind.util.WWMath;
60

    
61
/**
62
 * A multi-sided 3D shell formed by a base polygon in latitude and longitude extruded from the terrain to either a
63
 * specified height or an independent height per location. The base polygon may be complex with multiple internal but
64
 * not intersecting contours.
65
 * <p/>
66
 * Extruded polygon boundaries may be specified using either {@link LatLon} locations or {@link Position} positions, but
67
 * all the shape's boundary vertices must be the same type.
68
 * <p/>
69
 * Extruded polygons may optionally be textured. Textures may be applied to both the faces of the outer and inner
70
 * boundaries or just the outer boundaries. Texture can also be applied independently to the cap. Standard lighting is
71
 * optionally applied. Texture source images are lazily retrieved and loaded. This can cause a brief period in which the
72
 * texture is not displayed while it is retrieved from disk or network.
73
 * <p/>
74
 * <code>ExtrudedPolygon</code> side faces and cap have independent attributes for both normal and highlighted drawing.
75
 * <p/>
76
 * When specifying a single height (altitude mode {@link WorldWind#CONSTANT}, the height is relative to a reference
77
 * location designated by one of the specified polygon locations in the outer boundary. The default reference location
78
 * is the first one in the polygon's outer boundary. An alternative location may be specified by calling {@link
79
 * #setReferenceLocation(LatLon)}. The extruded polygon is capped with a plane at the specified height and tangent to
80
 * the ellipsoid at the reference location. Since locations other than the reference location may resolve to points at
81
 * elevations other than that at the reference location, the distances from those points to the cap are adjusted so that
82
 * the adjacent sides precisely meet the cap. When specifying polygons using a single height, only the latitudes and
83
 * longitudes of polygon boundary positions must be specified.
84
 * <p/>
85
 * Independent per-location heights may be specified via the <i>altitude</i> field of {@link Position}s defining the
86
 * polygon's inner and outer boundaries. Depending on the specified altitude mode, the position altitudes may be
87
 * interpreted as altitudes relative to mean sea level or altitudes above the ground at the associated latitude and
88
 * longitude locations.
89
 * <p/>
90
 * Boundaries are required to be closed, their first location must be equal to their last location. Boundaries that are
91
 * not closed are explicitly closed by this shape when they are specified.
92
 * <p/>
93
 * Extruded polygons are safe to share among World Windows. They should not be shared among layers in the same World
94
 * Window.
95
 * <p/>
96
 * In order to support simultaneous use of this shape with multiple globes (windows), this shape maintains a cache of
97
 * data computed relative to each globe. During rendering, the data for the currently active globe, as indicated in the
98
 * draw context, is made current. Subsequently called methods rely on the existence of this current data cache entry.
99
 * <p/>
100
 * When drawn on a 2D globe, this shape uses a {@link SurfacePolygon} to represent itself. Cap texture is not supported
101
 * in this case.
102
 *
103
 * @author tag
104
 * @version $Id: ExtrudedPolygon.java 2111 2014-06-30 18:09:45Z tgaskins $
105
 */
106
public class ExtrudedPolygon extends AbstractShape
107
{
108
    /** The default interior color for sides. */
109
    protected static final Material DEFAULT_SIDES_INTERIOR_MATERIAL = Material.LIGHT_GRAY;
110
    /** The default altitude mode. */
111
    protected static final int DEFAULT_ALTITUDE_MODE = WorldWind.CONSTANT;
112

    
113
    /** The attributes used if attributes are not specified. */
114
    protected static final ShapeAttributes defaultSideAttributes;
115

    
116
    protected double baseDepth;
117

    
118
    static
119
    {
120
        defaultSideAttributes = new BasicShapeAttributes();
121
        defaultSideAttributes.setInteriorMaterial(DEFAULT_SIDES_INTERIOR_MATERIAL);
122
    }
123

    
124
    /** The <code>ShapeData</code> class holds globe-specific data for this shape. */
125
    protected static class ShapeData extends AbstractShapeData implements Iterable<ExtrudedBoundaryInfo>
126
    {
127
        /** The boundary locations of the associated shape. Copied from that shape during construction. */
128
        protected List<ExtrudedBoundaryInfo> boundaries = new ArrayList<ExtrudedBoundaryInfo>();
129
        /** A buffer holding the Cartesian cap vertices of all the shape's boundaries. */
130
        protected FloatBuffer capVertexBuffer;
131
        /** A buffer holding the cap normals of all the shape's boundaries. */
132
        protected FloatBuffer capNormalBuffer;
133
        /** A buffer holding the Cartesian vertices of all the shape's side vertices. */
134
        protected FloatBuffer sideVertexBuffer;
135
        /** A buffer holding the side normals of all the shape's boundaries. */
136
        protected FloatBuffer sideNormalBuffer;
137
        /** A buffer holding the texture coordinates of all the shape's faces. Non-null only when texture is applied. */
138
        protected FloatBuffer sideTextureCoordsBuffer;
139

    
140
        // Tessellation fields
141
        /** This shape's tessellation. */
142
        protected GLUTessellatorSupport.CollectIndexListsCallback cb;
143
        /**
144
         * The indices identifying the cap vertices in a shape data's vertex buffer. Determined when this shape is
145
         * tessellated, which occurs only once unless the shape's boundaries are re-specified.
146
         */
147
        protected IntBuffer capFillIndices;
148
        /** Slices of <code>capFillIndices</code>, one per boundary. */
149
        protected List<IntBuffer> capFillIndexBuffers;
150
        /** Indicates whether a tessellation error occurred. No more attempts to tessellate will be made if set to true. */
151
        protected boolean tessellationError = false;
152

    
153
        /**
154
         * Constructs an instance using the boundaries of a specified extruded polygon.
155
         *
156
         * @param dc    the current draw context.
157
         * @param shape this shape.
158
         */
159
        public ShapeData(DrawContext dc, ExtrudedPolygon shape)
160
        {
161
            super(dc, shape.minExpiryTime, shape.maxExpiryTime);
162

    
163
            if (shape.boundaries.size() < 1)
164
            {
165
                // add a placeholder for the outer boundary
166
                this.boundaries.add(new ExtrudedBoundaryInfo(new ArrayList<LatLon>()));
167
                return;
168
            }
169

    
170
            // Copy the shape's boundaries.
171
            for (List<? extends LatLon> boundary : shape.boundaries)
172
            {
173
                this.boundaries.add(new ExtrudedBoundaryInfo(boundary));
174
            }
175

    
176
            // Copy the shape's side texture references.
177
            this.copySideTextureReferences(shape);
178
        }
179

    
180
        protected void copySideTextureReferences(ExtrudedPolygon shape)
181
        {
182
            if (shape.sideTextures != null)
183
            {
184
                for (int i = 0; i < this.boundaries.size() && i < shape.sideTextures.size(); i++)
185
                {
186
                    ExtrudedBoundaryInfo ebi = this.boundaries.get(i);
187
                    if (ebi != null)
188
                        this.boundaries.get(i).sideTextures = shape.sideTextures.get(i);
189
                }
190
            }
191
        }
192

    
193
        /**
194
         * Returns the outer boundary information for this shape data.
195
         *
196
         * @return this shape data's outer boundary information.
197
         */
198
        protected ExtrudedBoundaryInfo getOuterBoundaryInfo()
199
        {
200
            return this.boundaries.get(0);
201
        }
202

    
203
        /**
204
         * Iterates over the boundary information of this shape data.
205
         *
206
         * @return an iterator over this shape data's boundary info.
207
         */
208
        public Iterator<ExtrudedBoundaryInfo> iterator()
209
        {
210
            return this.boundaries.iterator();
211
        }
212
    }
213

    
214
    @Override
215
    protected AbstractShapeData createCacheEntry(DrawContext dc)
216
    {
217
        return new ShapeData(dc, this);
218
    }
219

    
220
    /**
221
     * Indicates the currently active shape data.
222
     *
223
     * @return the currently active shape data.
224
     */
225
    protected ShapeData getCurrent()
226
    {
227
        return (ShapeData) this.getCurrentData();
228
    }
229

    
230
    /**
231
     * Holds globe-specific information for each contour of the polygon. This class is meant only to be used as a way to
232
     * group per-boundary information in globe-specific <code>ShapeData</code>.
233
     */
234
    protected static class ExtrudedBoundaryInfo
235
    {
236
        /** The boundary vertices. This is merely a reference to the paren't shape's boundaries. */
237
        protected List<? extends LatLon> locations;
238
        /** The number of faces in the boundary. (The number of positions - 1.) */
239
        protected int faceCount;
240

    
241
        /** The vertices defining the boundary's cap. */
242
        protected Vec4[] capVertices;
243
        /** The vertices defining the boundary's base. These are always on the terrain. */
244
        protected Vec4[] baseVertices;
245

    
246
        /** Indices identifying the cap vertices in the vertex buffer. */
247
        protected IntBuffer capFillIndices;
248
        /** Indices identifying the cap edges in the vertex buffer. */
249
        protected IntBuffer capEdgeIndices;
250
        /** A buffer holding the vertices defining the boundary's cap. */
251
        protected FloatBuffer capVertexBuffer;
252
        /** A buffer holding the boundary cap's vertex normals. Non-null only when lighting is applied. */
253
        protected FloatBuffer capNormalBuffer;
254

    
255
        /** The indices identifying the boundary's side faces in the side-vertex buffer. */
256
        protected IntBuffer sideIndices;
257
        /** The indices identifying the boundary's edge indices in the side-vertex buffer. */
258
        protected IntBuffer sideEdgeIndices;
259
        /** A buffer holding the side vertices. These are passed to OpenGL. */
260
        protected FloatBuffer sideVertexBuffer;
261
        /** A buffer holding per-vertex normals. Non-null only when lighting is applied. */
262
        protected FloatBuffer sideNormalBuffer;
263
        /** The textures to apply to this boundary, one per face. */
264
        protected List<WWTexture> sideTextures;
265
        /**
266
         * The texture coordinates to use when applying side textures, a coordinate pair for each of 4 corners. These
267
         * are globe-specific because they account for the varying positions of the base on the terrain. (The cap
268
         * texture coordinates are not globe-specific.)
269
         */
270
        protected FloatBuffer sideTextureCoords;
271

    
272
        /**
273
         * Constructs a boundary info instance for a specified boundary.
274
         *
275
         * @param locations the boundary locations. Only this reference is kept; the boundaries are not copied.
276
         */
277
        public ExtrudedBoundaryInfo(List<? extends LatLon> locations)
278
        {
279
            this.locations = locations;
280
            this.faceCount = locations.size() - 1;
281
        }
282
    }
283

    
284
    // This static hash map holds the vertex indices that define the shape geometry. Their contents depend only on the
285
    // number of locations in the source polygon, so they can be reused by all shapes with the same location count.
286
    protected static HashMap<Integer, IntBuffer> capEdgeIndexBuffers = new HashMap<Integer, IntBuffer>();
287
    protected static HashMap<Integer, IntBuffer> sideFillIndexBuffers = new HashMap<Integer, IntBuffer>();
288
    protected static HashMap<Integer, IntBuffer> sideEdgeIndexBuffers = new HashMap<Integer, IntBuffer>();
289

    
290
    /** Indicates the number of vertices that must be present in order for VBOs to be used to render this shape. */
291
    protected static final int VBO_THRESHOLD = Configuration.getIntegerValue(AVKey.VBO_THRESHOLD, 30);
292

    
293
    /**
294
     * The location of each vertex in this shape's boundaries. There is one list per boundary. There is always an entry
295
     * for the outer boundary, but its list is empty if an outer boundary has not been specified.
296
     */
297
    protected List<List<? extends LatLon>> boundaries;
298
    /** The total number of locations in all boundaries. */
299
    protected int totalNumLocations;
300
    /** The total number of faces in all this shape's boundaries. */
301
    protected int totalFaceCount;
302

    
303
    /** This shape's height. Default is 1. */
304
    protected double height = 1;
305

    
306
    /** The attributes to use when drawing this shape's sides. */
307
    protected ShapeAttributes sideAttributes;
308
    /** The attributes to use when drawing this shape's sides in highlight mode. */
309
    protected ShapeAttributes sideHighlightAttributes;
310
    /** The currently active side attributes, derived from the specified attributes. Current only during rendering. */
311
    protected ShapeAttributes activeSideAttributes = new BasicShapeAttributes();
312
    /** This shape's side textures. */
313
    protected List<List<WWTexture>> sideTextures;
314
    /** This shape's cap texture. */
315
    protected WWTexture capTexture;
316
    /** This shape's cap texture coordinates. */
317
    protected FloatBuffer capTextureCoords;
318
    /** Indicates whether the cap should be drawn. */
319
    protected boolean enableCap = true;
320
    /** Indicates whether the sides should be drawn. */
321
    protected boolean enableSides = true;
322

    
323
    // Intersection fields
324
    /** The terrain used in the most recent intersection calculations. */
325
    protected Terrain previousIntersectionTerrain;
326
    /** The globe state key for the globe used in the most recent intersection calculation. */
327
    protected Object previousIntersectionGlobeStateKey;
328
    /** The shape data used for the previous intersection calculation. */
329
    protected ShapeData previousIntersectionShapeData;
330

    
331
    /** Constructs an extruded polygon with an empty outer boundary and a default height of 1 meter. */
332
    public ExtrudedPolygon()
333
    {
334
        this.boundaries = new ArrayList<List<? extends LatLon>>();
335
        this.boundaries.add(new ArrayList<LatLon>()); // placeholder for outer boundary
336
    }
337

    
338
    /**
339
     * Constructs an extruded polygon of a specified height and an empty outer boundary.
340
     *
341
     * @param height the shape height, in meters. May be null, in which case a height of 1 is used. The height is used
342
     *               only when the altitude mode is {@link WorldWind#CONSTANT}, which is the default for this shape.
343
     */
344
    public ExtrudedPolygon(Double height)
345
    {
346
        this(); // to initialize the instance
347

    
348
        this.setHeight(height);
349
    }
350

    
351
    /**
352
     * Constructs an extruded polygon for a specified list of outer boundary locations and a height.
353
     *
354
     * @param corners the list of locations defining this extruded polygon's outer boundary.
355
     * @param height  the shape height, in meters. May be null, in which case a height of 1 is used. The height is used
356
     *                only when the altitude mode is {@link WorldWind#CONSTANT}, which is the default for this shape.
357
     *
358
     * @throws IllegalArgumentException if the location list is null or the height is specified but less than or equal
359
     *                                  to zero.
360
     */
361
    public ExtrudedPolygon(Iterable<? extends LatLon> corners, Double height)
362
    {
363
        this(); // to initialize the instance
364

    
365
        if (corners == null)
366
        {
367
            String message = Logging.getMessage("nullValue.IterableIsNull");
368
            Logging.logger().severe(message);
369
            throw new IllegalArgumentException(message);
370
        }
371

    
372
        if (height != null && height <= 0)
373
        {
374
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "height <= 0");
375
            Logging.logger().severe(message);
376
            throw new IllegalArgumentException(message);
377
        }
378

    
379
        this.setOuterBoundary(corners, height);
380
    }
381

    
382
    /**
383
     * Constructs an extruded polygon from an outer boundary, a height, and images for its outer faces.
384
     *
385
     * @param corners      the list of locations defining this polygon's outer boundary.
386
     * @param height       the shape height, in meters. May be null, in which case a height of 1 is used. The height is
387
     *                     used only when the altitude mode is {@link WorldWind#CONSTANT}, which is the default for this
388
     *                     shape.
389
     * @param imageSources images to apply to the polygon's outer faces. One image for each face must be included. May
390
     *                     also be null.
391
     *
392
     * @throws IllegalArgumentException if the location list is null or the height is specified but less than or equal
393
     *                                  to zero.
394
     */
395
    public ExtrudedPolygon(Iterable<? extends LatLon> corners, double height, Iterable<?> imageSources)
396
    {
397
        this(corners, height);
398

    
399
        if (imageSources != null)
400
        {
401
            this.sideTextures = new ArrayList<List<WWTexture>>();
402
            this.sideTextures.add(this.fillImageList(imageSources));
403
        }
404
    }
405

    
406
    /**
407
     * Constructs an extruded polygon from an outer boundary.
408
     *
409
     * @param corners the list of outer boundary positions -- latitude longitude and altitude. The altitude mode
410
     *                determines whether the positions are considered relative to mean sea level (they are "absolute")
411
     *                or the ground elevation at the associated latitude and longitude.
412
     *
413
     * @throws IllegalArgumentException if the position list is null.
414
     */
415
    public ExtrudedPolygon(Iterable<? extends Position> corners)
416
    {
417
        this(corners, 1d); // the height field is ignored when positions are specified, so any value will do
418
    }
419

    
420
    /**
421
     * Constructs an extruded polygon from an outer boundary specified with position heights.
422
     *
423
     * @param corners the list of positions -- latitude longitude and altitude -- defining the polygon's outer boundary.
424
     *                The altitude mode determines whether the positions are considered relative to mean sea level (they
425
     *                are "absolute") or the ground elevation at the associated latitude and longitude.
426
     *
427
     * @throws IllegalArgumentException if the position list is null.
428
     */
429
    public ExtrudedPolygon(Position.PositionList corners)
430
    {
431
        this(corners.list, 1d); // the height field is ignored when positions are specified, so any value will do
432
    }
433

    
434
    /**
435
     * Constructs an extruded polygon from an outer boundary and apply specified textures to its outer faces.
436
     *
437
     * @param corners      the list of positions -- latitude longitude and altitude -- defining the polygon. The
438
     *                     altitude mode determines whether the positions are considered relative to mean sea level
439
     *                     (they are "absolute") or the ground elevation at the associated latitude and longitude.
440
     * @param imageSources textures to apply to the polygon's outer faces. One texture for each face must be included.
441
     *                     May also be null.
442
     *
443
     * @throws IllegalArgumentException if the position list is null.
444
     */
445
    public ExtrudedPolygon(Iterable<? extends Position> corners, Iterable<?> imageSources)
446
    {
447
        this(corners);
448

    
449
        if (imageSources != null)
450
        {
451
            this.sideTextures = new ArrayList<List<WWTexture>>();
452
            this.sideTextures.add(this.fillImageList(imageSources));
453
        }
454
    }
455

    
456
    protected void initialize()
457
    {
458
        // Overridden to specify a default altitude mode unique to extruded polygons.
459
        this.altitudeMode = DEFAULT_ALTITUDE_MODE;
460
    }
461

    
462
    protected void reset()
463
    {
464
        // Assumes that the boundary lists have already been established.
465

    
466
        for (List<? extends LatLon> locations : this.boundaries)
467
        {
468
            if (locations == null || locations.size() < 3)
469
                continue;
470

    
471
            if (!WWMath.computeWindingOrderOfLocations(locations).equals(AVKey.COUNTER_CLOCKWISE))
472
                Collections.reverse(locations);
473
        }
474

    
475
        this.totalNumLocations = this.countLocations();
476

    
477
        this.previousIntersectionShapeData = null;
478
        this.previousIntersectionTerrain = null;
479
        this.previousIntersectionGlobeStateKey = null;
480

    
481
        super.reset(); // removes all shape-data cache entries
482
    }
483

    
484
    /**
485
     * Counts the total number of locations in this polygon's boundaries, not including positions introduced by
486
     * extrusion.
487
     *
488
     * @return the number of locations in the polygon boundaries.
489
     */
490
    protected int countLocations()
491
    {
492
        int count = 0;
493

    
494
        for (List<? extends LatLon> locations : this.boundaries)
495
        {
496
            count += locations.size();
497
        }
498

    
499
        this.totalFaceCount = count - this.boundaries.size();
500

    
501
        return count;
502
    }
503

    
504
    /**
505
     * Returns the list of locations or positions defining this polygon's outer boundary.
506
     *
507
     * @return this polygon's positions.
508
     */
509
    public Iterable<? extends LatLon> getOuterBoundary()
510
    {
511
        return this.outerBoundary();
512
    }
513

    
514
    /**
515
     * Returns a reference to the outer boundary of this polygon.
516
     *
517
     * @return this polygon's outer boundary.
518
     */
519
    protected List<? extends LatLon> outerBoundary()
520
    {
521
        return this.boundaries.get(0);
522
    }
523

    
524
    /**
525
     * Indicates whether this shape's outer boundary exists and has more than two points.
526
     *
527
     * @return true if the outer boundary is valid, otherwise false.s
528
     */
529
    protected boolean isOuterBoundaryValid()
530
    {
531
        return this.boundaries.size() > 0 && this.boundaries.get(0).size() > 2;
532
    }
533

    
534
    /**
535
     * Specifies the latitude and longitude of the locations defining the outer boundary of this polygon. To specify
536
     * altitudes, pass {@link Position}s rather than {@link LatLon}s. The shape's height is not modified.
537
     *
538
     * @param corners the outer boundary locations.
539
     *
540
     * @throws IllegalArgumentException if the location list is null or contains fewer than three locations.
541
     */
542
    public void setOuterBoundary(Iterable<? extends LatLon> corners)
543
    {
544
        this.setOuterBoundary(corners, this.getHeight());
545
    }
546

    
547
    /**
548
     * Specifies the latitudes, longitudes and outer-boundary images for the outer boundary of this polygon. To specify
549
     * altitudes, pass {@link Position}s rather than {@link LatLon}s.
550
     *
551
     * @param corners      the polygon locations.
552
     * @param imageSources images to apply to the outer faces. One image must be specified for each face. May be null.
553
     *
554
     * @throws IllegalArgumentException if the location list is null.
555
     */
556
    public void setOuterBoundary(Iterable<? extends LatLon> corners, Iterable<?> imageSources)
557
    {
558
        this.setOuterBoundary(corners);
559

    
560
        if (imageSources == null && this.sideTextures == null)
561
            return;
562

    
563
        // Must install or replace the side textures
564

    
565
        if (this.sideTextures == null)
566
            this.sideTextures = new ArrayList<List<WWTexture>>();
567

    
568
        // Add or replace the first element, the outer boundary's element, in the list of side textures.
569
        List<WWTexture> textures = this.fillImageList(imageSources);
570
        this.sideTextures.set(0, textures);
571

    
572
        // Update the shape cache
573
        for (ShapeDataCache.ShapeDataCacheEntry entry : this.shapeDataCache)
574
        {
575
            ShapeData sd = (ShapeData) entry;
576
            if (sd.boundaries != null)
577
                sd.copySideTextureReferences(this);
578
        }
579
    }
580

    
581
    /**
582
     * Specifies the latitude and longitude of the outer boundary locations defining this polygon, and optionally the
583
     * extruded polygon's height. To specify altitudes for each boundary location, pass {@link Position}s rather than
584
     * {@link LatLon}s, but those altitudes are used only when the shape's altitude mode is {@link WorldWind#ABSOLUTE}.
585
     *
586
     * @param corners the outer boundary locations.
587
     * @param height  the shape height, in meters.
588
     *
589
     * @throws IllegalArgumentException if the location list is null or contains fewer than three locations.
590
     */
591
    public void setOuterBoundary(Iterable<? extends LatLon> corners, Double height)
592
    {
593
        if (corners == null)
594
        {
595
            String message = Logging.getMessage("nullValue.IterableIsNull");
596
            Logging.logger().severe(message);
597
            throw new IllegalArgumentException(message);
598
        }
599

    
600
        this.getBoundaries().set(0, this.fillBoundary(corners));
601

    
602
        if (height != null)
603
            this.height = height;
604

    
605
        this.reset();
606
    }
607

    
608
    protected List<? extends LatLon> fillBoundary(Iterable<? extends LatLon> corners)
609
    {
610
        ArrayList<LatLon> list = new ArrayList<LatLon>();
611
        for (LatLon corner : corners)
612
        {
613
            if (corner != null)
614
                list.add(corner);
615
        }
616

    
617
        if (list.size() < 3)
618
        {
619
            String message = Logging.getMessage("generic.InsufficientPositions");
620
            Logging.logger().severe(message);
621
            throw new IllegalArgumentException(message);
622
        }
623

    
624
        // Close the list if not already closed.
625
        if (list.size() > 0 && !list.get(0).equals(list.get(list.size() - 1)))
626
            list.add(list.get(0));
627

    
628
        list.trimToSize();
629

    
630
        return list;
631
    }
632

    
633
    /**
634
     * Add an inner boundary to this polygon.
635
     *
636
     * @param corners the boundary's locations.
637
     *
638
     * @throws IllegalArgumentException if the location list is null or contains fewer than three locations.
639
     */
640
    public void addInnerBoundary(Iterable<? extends LatLon> corners)
641
    {
642
        if (corners == null)
643
        {
644
            String message = Logging.getMessage("nullValue.LocationInListIsNull");
645
            Logging.logger().severe(message);
646
            throw new IllegalArgumentException(message);
647
        }
648

    
649
        this.getBoundaries().add(this.fillBoundary(corners));
650

    
651
        this.reset();
652
    }
653

    
654
    /**
655
     * Add an inner boundary to this polygon and specify images to apply to each of the boundary's faces. Specify {@link
656
     * LatLon}s to use the polygon's single height, or {@link Position}s, to include individual altitudes.
657
     *
658
     * @param corners      the boundary's locations.
659
     * @param imageSources images to apply to the boundary's faces. One image must be specified for each face. May be
660
     *                     null.
661
     *
662
     * @throws IllegalArgumentException if the location list is null.
663
     */
664
    public void addInnerBoundary(Iterable<? extends LatLon> corners, Iterable<?> imageSources)
665
    {
666
        if (corners == null)
667
        {
668
            String message = Logging.getMessage("nullValue.LocationInListIsNull");
669
            Logging.logger().severe(message);
670
            throw new IllegalArgumentException(message);
671
        }
672

    
673
        this.getBoundaries().add(this.fillBoundary(corners));
674

    
675
        if (imageSources != null)
676
        {
677
            if (this.sideTextures == null)
678
            {
679
                this.sideTextures = new ArrayList<List<WWTexture>>();
680
                this.sideTextures.add(new ArrayList<WWTexture>()); // placeholder for outer boundary
681
            }
682

    
683
            this.sideTextures.add(this.fillImageList(imageSources));
684
        }
685

    
686
        this.reset();
687
    }
688

    
689
    /**
690
     * Returns this shape's boundaries.
691
     *
692
     * @return this shape's boundaries.
693
     */
694
    protected List<List<? extends LatLon>> getBoundaries()
695
    {
696
        return this.boundaries;
697
    }
698

    
699
    /**
700
     * Creates texture object for a boundary's image sources.
701
     *
702
     * @param imageSources the images to apply for this boundary.
703
     *
704
     * @return the list of texture objects, or null if the <code>imageSources</code> argument is null.
705
     */
706
    protected List<WWTexture> fillImageList(Iterable<?> imageSources)
707
    {
708
        if (imageSources == null)
709
            return null;
710

    
711
        ArrayList<WWTexture> textures = new ArrayList<WWTexture>();
712

    
713
        for (Object source : imageSources)
714
        {
715
            if (source != null)
716
                textures.add(this.makeTexture(source));
717
            else
718
                textures.add(null);
719
        }
720

    
721
        textures.trimToSize();
722

    
723
        return textures;
724
    }
725

    
726
    /**
727
     * Returns this extruded polygon's cap image.
728
     *
729
     * @return the texture image source, or null if no source has been specified.
730
     */
731
    public Object getCapImageSource()
732
    {
733
        return this.capTexture != null ? this.capTexture.getImageSource() : null;
734
    }
735

    
736
    /**
737
     * Specifies the image to apply to this extruded polygon's cap.
738
     *
739
     * @param imageSource   the image source. May be a {@link String} identifying a file path or URL, a {@link File}, or
740
     *                      a {@link java.net.URL}.
741
     * @param texCoords     the (s, t) texture coordinates aligning the image to the polygon. There must be one texture
742
     *                      coordinate pair, (s, t), for each location in the polygon's outer boundary.
743
     * @param texCoordCount the number of texture coordinates, (s, v) pairs, specified.
744
     *
745
     * @throws IllegalArgumentException if the image source is not null and either the texture coordinates are null or
746
     *                                  inconsistent with the specified texture-coordinate count, or there are fewer
747
     *                                  than three texture coordinate pairs.
748
     */
749
    public void setCapImageSource(Object imageSource, float[] texCoords, int texCoordCount)
750
    {
751
        if (imageSource == null)
752
        {
753
            this.capTexture = null;
754
            this.capTextureCoords = null;
755
            return;
756
        }
757

    
758
        if (texCoords == null)
759
        {
760
            String message = Logging.getMessage("generic.ListIsEmpty");
761
            Logging.logger().severe(message);
762
            throw new IllegalArgumentException(message);
763
        }
764

    
765
        if (texCoordCount < 3 || texCoords.length < 2 * texCoordCount)
766
        {
767
            String message = Logging.getMessage("generic.InsufficientPositions");
768
            Logging.logger().severe(message);
769
            throw new IllegalArgumentException(message);
770
        }
771

    
772
        this.capTexture = this.makeTexture(imageSource);
773

    
774
        // Determine whether the tex-coord list needs to be closed.
775
        boolean closeIt = texCoords[0] != texCoords[texCoordCount - 2] || texCoords[1] != texCoords[texCoordCount - 1];
776

    
777
        this.capTextureCoords = Buffers.newDirectFloatBuffer(2 * (texCoordCount + (closeIt ? 1 : 0)));
778
        for (int i = 0; i < 2 * texCoordCount; i++)
779
        {
780
            this.capTextureCoords.put(texCoords[i]);
781
        }
782

    
783
        if (closeIt)
784
        {
785
            this.capTextureCoords.put(this.capTextureCoords.get(0));
786
            this.capTextureCoords.put(this.capTextureCoords.get(1));
787
        }
788
    }
789

    
790
    /**
791
     * Returns the texture coordinates for this polygon's cap.
792
     *
793
     * @return the texture coordinates, or null if no cap texture coordinates have been specified.
794
     */
795
    public float[] getTextureCoords()
796
    {
797
        if (this.capTextureCoords == null)
798
            return null;
799

    
800
        float[] retCoords = new float[this.capTextureCoords.limit()];
801
        this.capTextureCoords.get(retCoords, 0, retCoords.length);
802

    
803
        return retCoords;
804
    }
805

    
806
    /**
807
     * Get the texture applied to this extruded polygon's cap.
808
     *
809
     * @return The texture, or null if there is no texture or the texture is not yet available.
810
     */
811
    protected WWTexture getCapTexture()
812
    {
813
        return this.capTexture;
814
    }
815

    
816
    /**
817
     * Returns the height of this extruded polygon.
818
     *
819
     * @return the height originally specified, in meters.
820
     */
821
    public double getHeight()
822
    {
823
        return height;
824
    }
825

    
826
    /**
827
     * Specifies the height of this extruded polygon.
828
     *
829
     * @param height the height, in meters.
830
     *
831
     * @throws IllegalArgumentException if the height is less than or equal to zero.
832
     */
833
    public void setHeight(double height)
834
    {
835
        if (this.height == height)
836
            return;
837

    
838
        if (height <= 0)
839
        {
840
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "height <= 0");
841
            Logging.logger().severe(message);
842
            throw new IllegalArgumentException(message);
843
        }
844

    
845
        this.height = height;
846
        this.reset();
847
    }
848

    
849
    /**
850
     * Indicates whether the cap of this extruded polygon is drawn.
851
     *
852
     * @return true to draw the cap, otherwise false.
853
     */
854
    public boolean isEnableCap()
855
    {
856
        return enableCap;
857
    }
858

    
859
    /**
860
     * Specifies whether the cap of this extruded polygon is drawn.
861
     *
862
     * @param enableCap true to draw the cap, otherwise false.
863
     */
864
    public void setEnableCap(boolean enableCap)
865
    {
866
        this.enableCap = enableCap;
867
    }
868

    
869
    /**
870
     * Indicates whether the sides of this extruded polygon are drawn.
871
     *
872
     * @return true to draw the sides, otherwise false.
873
     */
874
    public boolean isEnableSides()
875
    {
876
        return enableSides;
877
    }
878

    
879
    /**
880
     * Specifies whether to draw the sides of this extruded polygon.
881
     *
882
     * @param enableSides true to draw the sides, otherwise false.
883
     */
884
    public void setEnableSides(boolean enableSides)
885
    {
886
        this.enableSides = enableSides;
887
    }
888

    
889
    /**
890
     * Returns the attributes applied to this polygon's side faces.
891
     *
892
     * @return this polygon's side attributes.
893
     */
894
    public ShapeAttributes getSideAttributes()
895
    {
896
        return this.sideAttributes;
897
    }
898

    
899
    /**
900
     * Specifies the attributes applied to this polygon's side faces.
901
     *
902
     * @param attributes this polygon's side attributes.
903
     *
904
     * @throws IllegalArgumentException if <code>attributes</code> is null.
905
     */
906
    public void setSideAttributes(ShapeAttributes attributes)
907
    {
908
        if (attributes == null)
909
        {
910
            String message = "nullValue.AttributesIsNull";
911
            Logging.logger().severe(message);
912
            throw new IllegalArgumentException(message);
913
        }
914

    
915
        this.sideAttributes = attributes;
916
    }
917

    
918
    /**
919
     * Returns the attributes applied to this polygon's cap.
920
     *
921
     * @return this polygon's cap attributes.
922
     */
923
    public ShapeAttributes getCapAttributes()
924
    {
925
        return this.getAttributes();
926
    }
927

    
928
    /**
929
     * Specifies the attributes applied to this polygon's cap.
930
     *
931
     * @param attributes this polygon's cap attributes.
932
     *
933
     * @throws IllegalArgumentException if <code>attributes</code> is null.
934
     */
935
    public void setCapAttributes(ShapeAttributes attributes)
936
    {
937
        this.setAttributes(attributes);
938
    }
939

    
940
    /**
941
     * Returns the highlight attributes applied to this polygon's side faces.
942
     *
943
     * @return this polygon's side highlight attributes.
944
     */
945
    public ShapeAttributes getSideHighlightAttributes()
946
    {
947
        return sideHighlightAttributes;
948
    }
949

    
950
    /**
951
     * Specifies the highlight attributes applied to this polygon's side faces.
952
     *
953
     * @param attributes this polygon's side highlight attributes.
954
     *
955
     * @throws IllegalArgumentException if <code>attributes</code> is null.
956
     */
957
    public void setSideHighlightAttributes(ShapeAttributes attributes)
958
    {
959
        if (attributes == null)
960
        {
961
            String message = "nullValue.AttributesIsNull";
962
            Logging.logger().severe(message);
963
            throw new IllegalArgumentException(message);
964
        }
965

    
966
        this.sideHighlightAttributes = attributes;
967
    }
968

    
969
    /**
970
     * Returns the highlight attributes applied to this polygon's cap.
971
     *
972
     * @return this polygon's cap highlight attributes.
973
     */
974
    public ShapeAttributes getCapHighlightAttributes()
975
    {
976
        return this.getHighlightAttributes();
977
    }
978

    
979
    /**
980
     * Specifies the highlight attributes applied to this polygon's cap.
981
     *
982
     * @param attributes this polygon's cap highlight attributes.
983
     *
984
     * @throws IllegalArgumentException if <code>attributes</code> is null.
985
     */
986
    public void setCapHighlightAttributes(ShapeAttributes attributes)
987
    {
988
        this.setHighlightAttributes(attributes);
989
    }
990

    
991
    /**
992
     * Each time this polygon is rendered the appropriate attributes for the current mode are determined. This method
993
     * returns the resolved attributes.
994
     *
995
     * @return the currently active attributes for this polygon's side faces.
996
     */
997
    protected ShapeAttributes getActiveSideAttributes()
998
    {
999
        return this.activeSideAttributes;
1000
    }
1001

    
1002
    /**
1003
     * Each time this polygon is rendered the appropriate attributes for the current mode are determined. This method
1004
     * returns the resolved attributes.
1005
     *
1006
     * @return the currently active attributes for this polygon's cap.
1007
     */
1008
    protected ShapeAttributes getActiveCapAttributes()
1009
    {
1010
        return this.getActiveAttributes();
1011
    }
1012

    
1013
    public Sector getSector()
1014
    {
1015
        if (this.sector == null && this.outerBoundary().size() > 2)
1016
            this.sector = Sector.boundingSector(this.getOuterBoundary());
1017

    
1018
        return this.sector;
1019
    }
1020

    
1021
    /**
1022
     * Indicates the location to use as a reference location for computed geometry.
1023
     *
1024
     * @return the reference location, or null if no reference location has been specified.
1025
     *
1026
     * @see #getReferencePosition()
1027
     */
1028
    public LatLon getReferenceLocation()
1029
    {
1030
        return this.getReferencePosition();
1031
    }
1032

    
1033
    /**
1034
     * Specifies the location to use as a reference location for computed geometry. This value should typically be left
1035
     * to the default, which is the first location in the extruded polygon's outer boundary.
1036
     *
1037
     * @param referenceLocation the reference location. May be null, in which case the first location of the outer
1038
     *                          boundary is the reference location.
1039
     *
1040
     * @see #setReferencePosition(gov.nasa.worldwind.geom.Position)
1041
     */
1042
    public void setReferenceLocation(LatLon referenceLocation)
1043
    {
1044
        if (referenceLocation == null)
1045
        {
1046
            String message = Logging.getMessage("nullValue.LocationIsNull");
1047
            Logging.logger().severe(message);
1048
            throw new IllegalArgumentException(message);
1049
        }
1050

    
1051
        this.referencePosition = new Position(referenceLocation, 0);
1052
    }
1053

    
1054
    /**
1055
     * Indicates the position used as a reference position for this extruded polygon's computed geometry.
1056
     *
1057
     * @return the reference position, or null if no reference position has been specified.
1058
     */
1059
    public Position getReferencePosition()
1060
    {
1061
        if (this.referencePosition != null)
1062
            return this.referencePosition;
1063

    
1064
        if (this.boundaries.size() > 0 && this.outerBoundary().size() > 0)
1065
        {
1066
            if (this.outerBoundary().get(0) instanceof Position)
1067
                this.referencePosition = (Position) this.outerBoundary().get(0);
1068
            else
1069
                this.referencePosition = new Position(this.outerBoundary().get(0), 0);
1070
        }
1071

    
1072
        return this.referencePosition;
1073
    }
1074

    
1075
    /**
1076
     * Returns this object's base depth.
1077
     *
1078
     * @return This object's base depth, in meters.
1079
     *
1080
     * @see #setBaseDepth(double)
1081
     */
1082
    public double getBaseDepth()
1083
    {
1084
        return baseDepth;
1085
    }
1086

    
1087
    /**
1088
     * Specifies a depth below the terrain at which to place this extruded polygon's base vertices. This value does not
1089
     * affect the height of the extruded polygon nor the position of its cap. It positions the base vertices such that
1090
     * they are the specified distance below the terrain. The default value is zero, therefore the base vertices are
1091
     * position on the terrain.
1092
     *
1093
     * @param baseDepth the depth in meters to position the base vertices below the terrain. Specify positive values to
1094
     *                  position the base vertices below the terrain. (Negative values position the base vertices above
1095
     *                  the terrain.)
1096
     */
1097
    public void setBaseDepth(double baseDepth)
1098
    {
1099
        this.baseDepth = baseDepth;
1100
    }
1101

    
1102
    /**
1103
     * Returns this extruded polygon's side images.
1104
     *
1105
     * @return a collection of lists each identifying the image sources for the associated outer or inner polygon
1106
     *         boundary.
1107
     */
1108
    public List<List<Object>> getImageSources()
1109
    {
1110
        if (this.sideTextures == null)
1111
            return null;
1112

    
1113
        boolean hasTextures = false;
1114
        for (List<WWTexture> textures : this.sideTextures)
1115
        {
1116
            if (textures != null && textures.size() > 0)
1117
            {
1118
                hasTextures = true;
1119
                break;
1120
            }
1121
        }
1122

    
1123
        if (!hasTextures)
1124
            return null;
1125

    
1126
        List<List<Object>> imageSources = new ArrayList<List<Object>>(this.getBoundaries().size());
1127

    
1128
        for (List<WWTexture> textures : this.sideTextures)
1129
        {
1130
            if (textures == null)
1131
            {
1132
                imageSources.add(null);
1133
            }
1134
            else
1135
            {
1136
                ArrayList<Object> images = new ArrayList<Object>(textures.size());
1137
                imageSources.add(images);
1138

    
1139
                for (WWTexture image : textures)
1140
                {
1141
                    images.add(image.getImageSource());
1142
                }
1143
            }
1144
        }
1145

    
1146
        return imageSources;
1147
    }
1148

    
1149
    /**
1150
     * Indicates whether side images have been specified for this extruded polygon.
1151
     *
1152
     * @return true if side textures have been specified, otherwise false.
1153
     */
1154
    public boolean hasSideTextures()
1155
    {
1156
        if (this.sideTextures == null)
1157
            return false;
1158

    
1159
        for (List<WWTexture> textures : this.sideTextures)
1160
        {
1161
            if (textures != null && textures.size() > 0)
1162
                return true;
1163
        }
1164

    
1165
        return false;
1166
    }
1167

    
1168
    @Override
1169
    protected boolean mustApplyTexture(DrawContext dc)
1170
    {
1171
        if (this.getCapTexture() != null && this.capTextureCoords != null)
1172
            return true;
1173

    
1174
        return this.mustApplySideTextures();
1175
    }
1176

    
1177
    @Override
1178
    protected boolean isTerrainDependent()
1179
    {
1180
        return true; // this shape is terrain dependent regardless of the altitude mode
1181
    }
1182

    
1183
    protected boolean mustApplySideTextures()
1184
    {
1185
        return this.hasSideTextures();
1186
    }
1187

    
1188
    /**
1189
     * Indicates whether the interior of either the sides or cap must be drawn.
1190
     *
1191
     * @return true if an interior must be drawn, otherwise false.
1192
     */
1193
    protected boolean mustDrawInterior()
1194
    {
1195
        return super.mustDrawInterior() || this.getActiveSideAttributes().isDrawInterior();
1196
    }
1197

    
1198
    /**
1199
     * Indicates whether the polygon's outline should be drawn.
1200
     *
1201
     * @return true if the outline should be drawn, otherwise false.
1202
     */
1203
    protected boolean mustDrawOutline()
1204
    {
1205
        return super.mustDrawOutline() || this.getActiveSideAttributes().isDrawOutline();
1206
    }
1207

    
1208
    protected boolean mustRegenerateGeometry(DrawContext dc)
1209
    {
1210
        ShapeData shapeData = this.getCurrent();
1211

    
1212
        if (shapeData.capVertexBuffer == null || shapeData.sideVertexBuffer == null)
1213
            return true;
1214

    
1215
        if (dc.getVerticalExaggeration() != shapeData.getVerticalExaggeration())
1216
            return true;
1217

    
1218
        if ((this.mustApplyLighting(dc, this.getActiveCapAttributes()) && shapeData.capNormalBuffer == null)
1219
            || (this.mustApplyLighting(dc, this.getActiveSideAttributes()) && shapeData.sideNormalBuffer == null))
1220
            return true;
1221

    
1222
        return super.mustRegenerateGeometry(dc);
1223
    }
1224

    
1225
    public Extent getExtent(Globe globe, double verticalExaggeration)
1226
    {
1227
        // See if we've cached an extent associated with the globe.
1228
        Extent extent = super.getExtent(globe, verticalExaggeration);
1229
        if (extent != null)
1230
            return extent;
1231

    
1232
        return super.computeExtentFromPositions(globe, verticalExaggeration, this.getOuterBoundary());
1233
    }
1234

    
1235
    /**
1236
     * Computes this shapes extent. If a reference point is specified, the extent is translated to that reference
1237
     * point.
1238
     *
1239
     * @param outerBoundary the shape's outer boundary.
1240
     * @param refPoint      a reference point to which the extent is translated. May be null.
1241
     *
1242
     * @return the computed extent, or null if the extent cannot be computed.
1243
     */
1244
    protected Extent computeExtent(ExtrudedBoundaryInfo outerBoundary, Vec4 refPoint)
1245
    {
1246
        if (outerBoundary == null || outerBoundary.capVertices == null || outerBoundary.baseVertices == null)
1247
            return null;
1248

    
1249
        Vec4[] topVertices = outerBoundary.capVertices;
1250
        Vec4[] botVertices = outerBoundary.baseVertices;
1251

    
1252
        ArrayList<Vec4> allVertices = new ArrayList<Vec4>(2 * topVertices.length);
1253
        allVertices.addAll(Arrays.asList(topVertices));
1254
        allVertices.addAll(Arrays.asList(botVertices));
1255

    
1256
        Box boundingBox = Box.computeBoundingBox(allVertices);
1257

    
1258
        // The bounding box is computed relative to the polygon's reference point, so it needs to be translated to
1259
        // model coordinates in order to indicate its model-coordinate extent.
1260
        return boundingBox != null ? boundingBox.translate(refPoint) : null;
1261
    }
1262

    
1263
    protected void determineActiveAttributes()
1264
    {
1265
        super.determineActiveAttributes(); // determine active cap attributes in super class
1266

    
1267
        if (this.isHighlighted())
1268
        {
1269
            if (this.getSideHighlightAttributes() != null)
1270
                this.activeSideAttributes.copy(this.getSideHighlightAttributes());
1271
            else
1272
            {
1273
                // If no highlight attributes have been specified we need to use the normal attributes but adjust them
1274
                // to cause highlighting.
1275
                if (this.getSideAttributes() != null)
1276
                    this.activeSideAttributes.copy(this.getSideAttributes());
1277
                else
1278
                    this.activeSideAttributes.copy(defaultSideAttributes);
1279

    
1280
                this.activeSideAttributes.setOutlineMaterial(DEFAULT_HIGHLIGHT_MATERIAL);
1281
                this.activeSideAttributes.setInteriorMaterial(DEFAULT_HIGHLIGHT_MATERIAL);
1282
            }
1283
        }
1284
        else
1285
        {
1286
            if (this.getSideAttributes() != null)
1287
                this.activeSideAttributes.copy(this.getSideAttributes());
1288
            else
1289
                this.activeSideAttributes.copy(defaultSideAttributes);
1290
        }
1291
    }
1292

    
1293
//    @Override
1294
    protected SurfaceShape createSurfaceShape()
1295
    {
1296
        SurfacePolygon polygon = new SurfacePolygon();
1297
        this.setSurfacePolygonBoundaries(polygon);
1298

    
1299
        return polygon;
1300
    }
1301

    
1302
    protected void setSurfacePolygonBoundaries(SurfaceShape shape)
1303
    {
1304
        SurfacePolygon polygon = (SurfacePolygon) shape;
1305

    
1306
        polygon.setLocations(this.getOuterBoundary());
1307

    
1308
        List<List<? extends LatLon>> bounds = this.getBoundaries();
1309
        for (int i = 1; i < bounds.size(); i++)
1310
        {
1311
            polygon.addInnerBoundary(bounds.get(i));
1312
        }
1313
    }
1314

    
1315
    public void render(DrawContext dc)
1316
    {
1317
        if (!this.isOuterBoundaryValid())
1318
            return;
1319

    
1320
        super.render(dc);
1321
    }
1322

    
1323
    @Override
1324
    protected boolean isOrderedRenderableValid(DrawContext dc)
1325
    {
1326
        return this.getCurrent().capVertexBuffer != null || this.getCurrent().sideVertexBuffer != null;
1327
    }
1328

    
1329
    @Override
1330
    protected boolean doMakeOrderedRenderable(DrawContext dc)
1331
    {
1332
        if (dc.getSurfaceGeometry() == null || !this.isOuterBoundaryValid())
1333
            return false;
1334

    
1335
        this.createMinimalGeometry(dc, this.getCurrent());
1336

    
1337
        // If the shape is less that a pixel in size, don't render it.
1338
        if (this.getExtent() == null || dc.isSmall(this.getExtent(), 1))
1339
            return false;
1340

    
1341
        if (!this.intersectsFrustum(dc))
1342
            return false;
1343

    
1344
        this.createFullGeometry(dc, dc.getTerrain(), this.getCurrent(), true);
1345

    
1346
        return true;
1347
    }
1348

    
1349
    protected OGLStackHandler beginDrawing(DrawContext dc, int attrMask)
1350
    {
1351
        OGLStackHandler ogsh = super.beginDrawing(dc, attrMask);
1352

    
1353
        if (!dc.isPickingMode())
1354
        {
1355
            // Push an identity texture matrix. This prevents drawSides() from leaking GL texture matrix state. The
1356
            // texture matrix stack is popped from OGLStackHandler.pop() within {@link #endRendering}.
1357
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
1358
            ogsh.pushTextureIdentity(gl);
1359
        }
1360

    
1361
        return ogsh;
1362
    }
1363

    
1364
    @Override
1365
    public void drawOutline(DrawContext dc)
1366
    {
1367
        if (this.isEnableSides() && getActiveSideAttributes().isDrawOutline())
1368
            this.drawSideOutline(dc, this.getCurrent());
1369

    
1370
        if (this.isEnableCap() && getActiveCapAttributes().isDrawOutline())
1371
            this.drawCapOutline(dc, this.getCurrent());
1372
    }
1373

    
1374
    @Override
1375
    public void drawInterior(DrawContext dc)
1376
    {
1377
        if (this.isEnableSides() && getActiveSideAttributes().isDrawInterior())
1378
            this.drawSideInteriors(dc, this.getCurrent());
1379

    
1380
        if (this.isEnableCap() && getActiveCapAttributes().isDrawInterior())
1381
            this.drawCapInterior(dc, this.getCurrent());
1382
    }
1383

    
1384
    /**
1385
     * Not used by this class, which overrides {@link #drawOutline(DrawContext)}.
1386
     *
1387
     * @param dc not used.
1388
     */
1389
    @Override
1390
    protected void doDrawOutline(DrawContext dc)
1391
    {
1392
        return; // this class overrides super.drawOutline so doesn't use this "do" method
1393
    }
1394

    
1395
    /**
1396
     * Draws the cap's outline.
1397
     * <p/>
1398
     * This base implementation draws the outline of the basic polygon. Subclasses should override it to draw their
1399
     * outline or an alternate outline of the basic polygon.
1400
     *
1401
     * @param dc        the draw context.
1402
     * @param shapeData the current shape data.
1403
     */
1404
    public void drawCapOutline(DrawContext dc, ShapeData shapeData)
1405
    {
1406
        this.prepareToDrawOutline(dc, this.getActiveCapAttributes(), defaultAttributes);
1407

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

    
1410
        for (ExtrudedBoundaryInfo boundary : shapeData)
1411
        {
1412
            if (!dc.isPickingMode() && this.mustApplyLighting(dc, this.getActiveCapAttributes()))
1413
                gl.glNormalPointer(GL.GL_FLOAT, 0, boundary.capNormalBuffer.rewind());
1414

    
1415
            IntBuffer indices = boundary.capEdgeIndices;
1416
            gl.glVertexPointer(3, GL.GL_FLOAT, 0, boundary.capVertexBuffer.rewind());
1417
            gl.glDrawElements(GL.GL_LINES, indices.limit(), GL.GL_UNSIGNED_INT, indices.rewind());
1418
        }
1419
//
1420
//        // Diagnostic to show the normal vectors.
1421
//        if (this.mustApplyLighting(dc))
1422
//            dc.drawNormals(1000, this.vertexBuffer, this.normalBuffer);
1423
    }
1424

    
1425
    /**
1426
     * Draws this extruded polygon's side outline.
1427
     *
1428
     * @param dc        the draw context.
1429
     * @param shapeData the current shape data.
1430
     */
1431
    protected void drawSideOutline(DrawContext dc, ShapeData shapeData)
1432
    {
1433
        this.prepareToDrawOutline(dc, this.getActiveSideAttributes(), defaultSideAttributes);
1434

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

    
1437
        for (ExtrudedBoundaryInfo boundary : shapeData)
1438
        {
1439
            if (!dc.isPickingMode() && this.mustApplyLighting(dc, this.getActiveSideAttributes()))
1440
                gl.glNormalPointer(GL.GL_FLOAT, 0, boundary.sideNormalBuffer.rewind());
1441

    
1442
            IntBuffer indices = boundary.sideEdgeIndices;
1443
            indices.rewind();
1444

    
1445
            // Don't draw the top outline if the cap will draw it.
1446
            if (this.isEnableCap() && this.getActiveCapAttributes().isDrawOutline())
1447
            {
1448
                indices = indices.slice();
1449
                indices.position(2 * boundary.faceCount);
1450
            }
1451

    
1452
            gl.glVertexPointer(3, GL.GL_FLOAT, 0, boundary.sideVertexBuffer.rewind());
1453
            gl.glDrawElements(GL.GL_LINES, indices.remaining(), GL.GL_UNSIGNED_INT, indices);
1454
        }
1455
    }
1456

    
1457
    /**
1458
     * Not used by this class.
1459
     *
1460
     * @param dc not used.
1461
     */
1462
    @Override
1463
    protected void doDrawInterior(DrawContext dc)
1464
    {
1465
        return;
1466
    }
1467

    
1468
    /**
1469
     * Draws the filled interior of this shape's cap.
1470
     *
1471
     * @param dc        the draw context.
1472
     * @param shapeData this shape's current globe-specific data.
1473
     */
1474
    public void drawCapInterior(DrawContext dc, ShapeData shapeData)
1475
    {
1476
        super.prepareToDrawInterior(dc, this.getActiveCapAttributes(), defaultAttributes);
1477

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

    
1480
        if (!dc.isPickingMode() && this.mustApplyLighting(dc, this.getActiveCapAttributes()))
1481
            gl.glNormalPointer(GL.GL_FLOAT, 0, shapeData.capNormalBuffer.rewind());
1482

    
1483
        WWTexture texture = this.getCapTexture();
1484
        if (!dc.isPickingMode() && texture != null && this.capTextureCoords != null)
1485
        {
1486
            texture.bind(dc);
1487
            texture.applyInternalTransform(dc);
1488

    
1489
            gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, this.capTextureCoords.rewind());
1490
            dc.getGL().glEnable(GL.GL_TEXTURE_2D);
1491
            gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
1492
        }
1493
        else
1494
        {
1495
            dc.getGL().glDisable(GL.GL_TEXTURE_2D);
1496
            gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
1497
        }
1498

    
1499
        gl.glVertexPointer(3, GL.GL_FLOAT, 0, shapeData.capVertexBuffer.rewind());
1500

    
1501
        for (int i = 0; i < shapeData.cb.getPrimTypes().size(); i++)
1502
        {
1503
            IntBuffer ib = shapeData.capFillIndexBuffers.get(i);
1504
            gl.glDrawElements(shapeData.cb.getPrimTypes().get(i), ib.limit(), GL.GL_UNSIGNED_INT, ib.rewind());
1505
        }
1506
    }
1507

    
1508
    /**
1509
     * Draws this shape's sides.
1510
     *
1511
     * @param dc        the draw context.
1512
     * @param shapeData this shape's current globe-specific data.
1513
     */
1514
    protected void drawSideInteriors(DrawContext dc, ShapeData shapeData)
1515
    {
1516
        super.prepareToDrawInterior(dc, this.getActiveSideAttributes(), defaultSideAttributes);
1517

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

    
1520
        for (ExtrudedBoundaryInfo boundary : shapeData)
1521
        {
1522
            if (!dc.isPickingMode() && this.mustApplyLighting(dc, this.getActiveSideAttributes()))
1523
                gl.glNormalPointer(GL.GL_FLOAT, 0, boundary.sideNormalBuffer.rewind());
1524

    
1525
            if (!dc.isPickingMode() && boundary.sideTextureCoords != null)
1526
            {
1527
                gl.glEnable(GL.GL_TEXTURE_2D);
1528
                gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
1529
                gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, boundary.sideTextureCoords.rewind());
1530
            }
1531
            else
1532
            {
1533
                gl.glDisable(GL.GL_TEXTURE_2D);
1534
                gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
1535
            }
1536

    
1537
            gl.glVertexPointer(3, GL.GL_FLOAT, 0, boundary.sideVertexBuffer.rewind());
1538

    
1539
            boundary.sideIndices.rewind();
1540
            for (int j = 0; j < boundary.faceCount; j++)
1541
            {
1542
                if (!dc.isPickingMode() && boundary.sideTextureCoords != null)
1543
                {
1544
                    if (!boundary.sideTextures.get(j).bind(dc))
1545
                        continue;
1546

    
1547
                    boundary.sideTextures.get(j).applyInternalTransform(dc);
1548
                }
1549

    
1550
                boundary.sideIndices.position(4 * j);
1551
                boundary.sideIndices.limit(4 * (j + 1));
1552
                gl.glDrawElements(GL.GL_TRIANGLE_STRIP, 4, GL.GL_UNSIGNED_INT, boundary.sideIndices);
1553
            }
1554
        }
1555
    }
1556

    
1557
    /**
1558
     * Computes the information necessary to determine this extruded polygon's extent.
1559
     *
1560
     * @param dc        the current draw context.
1561
     * @param shapeData this shape's current globe-specific data.
1562
     */
1563
    protected void createMinimalGeometry(DrawContext dc, ShapeData shapeData)
1564
    {
1565
        this.computeReferencePoint(dc.getTerrain(), shapeData);
1566
        if (shapeData.getReferencePoint() == null)
1567
            return;
1568

    
1569
        this.computeBoundaryVertices(dc.getTerrain(), shapeData.getOuterBoundaryInfo(), shapeData.getReferencePoint());
1570

    
1571
        if (this.getExtent() == null || this.getAltitudeMode() != WorldWind.ABSOLUTE)
1572
            shapeData.setExtent(this.computeExtent(shapeData.getOuterBoundaryInfo(), shapeData.getReferencePoint()));
1573

    
1574
        shapeData.setEyeDistance(this.computeEyeDistance(dc, shapeData));
1575
        shapeData.setGlobeStateKey(dc.getGlobe().getGlobeStateKey(dc));
1576
        shapeData.setVerticalExaggeration(dc.getVerticalExaggeration());
1577
    }
1578

    
1579
    /**
1580
     * Computes the minimum distance between this shape and the eye point.
1581
     *
1582
     * @param dc        the draw context.
1583
     * @param shapeData this shape's current globe-specific data.
1584
     *
1585
     * @return the minimum distance from the shape to the eye point.
1586
     */
1587
    protected double computeEyeDistance(DrawContext dc, ShapeData shapeData)
1588
    {
1589
        double minDistance = Double.MAX_VALUE;
1590
        Vec4 eyePoint = dc.getView().getEyePoint();
1591

    
1592
        for (Vec4 point : shapeData.getOuterBoundaryInfo().capVertices)
1593
        {
1594
            double d = point.add3(shapeData.getReferencePoint()).distanceTo3(eyePoint);
1595
            if (d < minDistance)
1596
                minDistance = d;
1597
        }
1598

    
1599
        return minDistance;
1600
    }
1601

    
1602
    /**
1603
     * Computes and sets this shape's reference point, the Cartesian position corresponding to its geographic location.
1604
     *
1605
     * @param terrain   the terrain to use when computing the reference point. The reference point is always on the
1606
     *                  terrain.
1607
     * @param shapeData the current shape data.
1608
     */
1609
    protected void computeReferencePoint(Terrain terrain, ShapeData shapeData)
1610
    {
1611
        LatLon refPos = this.getReferencePosition();
1612
        if (refPos == null)
1613
            return;
1614

    
1615
        shapeData.setReferencePoint(terrain.getSurfacePoint(refPos.getLatitude(), refPos.getLongitude(), 0));
1616
    }
1617

    
1618
    /**
1619
     * Computes a boundary set's full geometry.
1620
     *
1621
     * @param dc                the current draw context.
1622
     * @param terrain           the terrain to use when computing the geometry.
1623
     * @param shapeData         the boundary set to compute the geometry for.
1624
     * @param skipOuterBoundary true if outer boundaries vertices do not need to be calculated, otherwise false.
1625
     */
1626
    protected void createFullGeometry(DrawContext dc, Terrain terrain, ShapeData shapeData,
1627
        boolean skipOuterBoundary)
1628
    {
1629
        for (ExtrudedBoundaryInfo boundary : shapeData)
1630
        {
1631
            boundary.capEdgeIndices = this.getCapEdgeIndices(boundary.locations.size());
1632
            boundary.sideIndices = this.getSideIndices(boundary.locations.size());
1633
            boundary.sideEdgeIndices = this.getSideEdgeIndices(boundary.locations.size());
1634
        }
1635

    
1636
        if (this.isEnableSides() || this.isEnableCap())
1637
            this.createVertices(terrain, shapeData, skipOuterBoundary);
1638

    
1639
        if (this.isEnableSides())
1640
        {
1641
            this.createSideGeometry(shapeData);
1642

    
1643
            if (this.mustApplyLighting(dc, this.getActiveSideAttributes()))
1644
                this.createSideNormals(shapeData);
1645

    
1646
            if (!dc.isPickingMode() && this.mustApplySideTextures())
1647
                this.createSideTextureCoords(shapeData);
1648
        }
1649

    
1650
        if (this.isEnableCap())
1651
        {
1652
            this.createCapGeometry(dc, shapeData);
1653

    
1654
            if (this.mustApplyLighting(dc, this.getActiveCapAttributes()))
1655
                this.createCapNormals(shapeData);
1656
        }
1657
    }
1658

    
1659
    /**
1660
     * Creates this shape's Cartesian vertices.
1661
     *
1662
     * @param terrain           the terrain to use when computing the vertices.
1663
     * @param shapeData         the current shape data.
1664
     * @param skipOuterBoundary if true, do not compute the outer boundary's vertices because they have already been
1665
     *                          computed.
1666
     */
1667
    protected void createVertices(Terrain terrain, ShapeData shapeData, boolean skipOuterBoundary)
1668
    {
1669
        for (ExtrudedBoundaryInfo boundary : shapeData)
1670
        {
1671
            if (boundary != shapeData.getOuterBoundaryInfo() || !skipOuterBoundary)
1672
                this.computeBoundaryVertices(terrain, boundary, shapeData.getReferencePoint());
1673
        }
1674
    }
1675

    
1676
    /**
1677
     * Compute and set the Cartesian vertices for one specified boundary of this shape.
1678
     *
1679
     * @param terrain  the terrain to use when computing the vertices.
1680
     * @param boundary the boundary for which to compute the vertices.
1681
     * @param refPoint the reference point specifying the coordinate origin of the vertices.
1682
     */
1683
    protected void computeBoundaryVertices(Terrain terrain, ExtrudedBoundaryInfo boundary, Vec4 refPoint)
1684
    {
1685
        Vec4[] topVertices = boundary.capVertices;
1686
        if (topVertices == null || topVertices.length < boundary.locations.size())
1687
            topVertices = new Vec4[boundary.locations.size()];
1688

    
1689
        Vec4[] bottomVertices = boundary.baseVertices;
1690
        if (bottomVertices == null || bottomVertices.length < boundary.locations.size())
1691
            bottomVertices = new Vec4[boundary.locations.size()];
1692

    
1693
        // These variables are used to compute the independent length of each cap vertex for CONSTANT altitude mode.
1694
        Vec4 N = null;
1695
        Vec4 vaa = null;
1696
        double vaaLength = 0;
1697
        double vaLength = 0;
1698

    
1699
        boundary.faceCount = boundary.locations.size() - 1;
1700
        for (int i = 0; i < boundary.faceCount; i++)
1701
        {
1702
            // The order for both top and bottom is CCW as one looks down from space onto the base polygon. For a
1703
            // 4-sided polygon (defined by 5 lat/lon locations) the vertex order is 0-1-2-3-4.
1704

    
1705
            LatLon location = boundary.locations.get(i);
1706

    
1707
            // Compute the bottom point, which is on the terrain.
1708
            Vec4 vert = terrain.getSurfacePoint(location.getLatitude(), location.getLongitude(), 0);
1709

    
1710
            if (this.getBaseDepth() == 0)
1711
            {
1712
                // Place the base vertex on the terrain.
1713
                bottomVertices[i] = vert.subtract3(refPoint);
1714
            }
1715
            else
1716
            {
1717
                // Place the base vertex below the terrain (if base depth is positive).
1718
                double length = vert.getLength3();
1719
                bottomVertices[i] = vert.multiply3((length - this.getBaseDepth()) / length).subtract3(refPoint);
1720
            }
1721

    
1722
            // Compute the top/cap point.
1723
            if (this.getAltitudeMode() == WorldWind.CONSTANT || !(location instanceof Position))
1724
            {
1725
                // The shape's base is on the terrain, and all the cap vertices are at a common altitude
1726
                // relative to the reference position. This means that the distance between the cap and base vertices
1727
                // at the boundary locations varies. Further, the vector between the base vertex and cap vertex at each
1728
                // boundary location is parallel to that at the reference position. The calculation below first
1729
                // determines that reference-position vector, then applies scaled versions of it to determine the cap
1730
                // vertex at all the other boundary positions.
1731
                if (vaa == null)
1732
                {
1733
                    Position refPos = this.getReferencePosition();
1734
                    N = terrain.getGlobe().computeSurfaceNormalAtLocation(refPos.getLatitude(), refPos.getLongitude());
1735
                    vaa = N.multiply3(this.getHeight());
1736
                    vaaLength = vaa.getLength3();
1737
                    vaLength = refPoint.dot3(N);
1738
                }
1739

    
1740
                double delta = vert.dot3(N) - vaLength;
1741
                vert = vert.add3(vaa.multiply3(1d - delta / vaaLength));
1742
            }
1743
            else if (this.getAltitudeMode() == WorldWind.RELATIVE_TO_GROUND)
1744
            {
1745
                vert = terrain.getSurfacePoint(location.getLatitude(), location.getLongitude(),
1746
                    ((Position) location).getAltitude());
1747
            }
1748
            else // WorldWind.ABSOLUTE
1749
            {
1750
                vert = terrain.getGlobe().computePointFromPosition(location.getLatitude(), location.getLongitude(),
1751
                    ((Position) location).getAltitude() * terrain.getVerticalExaggeration());
1752
            }
1753

    
1754
            topVertices[i] = vert.subtract3(refPoint);
1755
        }
1756

    
1757
        topVertices[boundary.locations.size() - 1] = topVertices[0];
1758
        bottomVertices[boundary.locations.size() - 1] = bottomVertices[0];
1759

    
1760
        boundary.capVertices = topVertices;
1761
        boundary.baseVertices = bottomVertices;
1762
    }
1763

    
1764
    /**
1765
     * Constructs the Cartesian geometry of this shape's sides and sets it in the specified shape data.
1766
     *
1767
     * @param shapeData the current shape data.
1768
     */
1769
    protected void createSideGeometry(ShapeData shapeData)
1770
    {
1771
        // The side vertex buffer requires 4 vertices of x,y,z for each polygon face.
1772
        int vertexCoordCount = this.totalFaceCount * 4 * 3; // 4 vertices of x,y,z per face
1773

    
1774
        if (shapeData.sideVertexBuffer != null && shapeData.sideVertexBuffer.capacity() >= vertexCoordCount)
1775
            shapeData.sideVertexBuffer.clear();
1776
        else
1777
            shapeData.sideVertexBuffer = Buffers.newDirectFloatBuffer(vertexCoordCount);
1778

    
1779
        // Create individual buffer slices for each boundary.
1780
        for (ExtrudedBoundaryInfo boundary : shapeData)
1781
        {
1782
            boundary.sideVertexBuffer = this.fillSideVertexBuffer(boundary.capVertices, boundary.baseVertices,
1783
                shapeData.sideVertexBuffer.slice());
1784
            shapeData.sideVertexBuffer.position(
1785
                shapeData.sideVertexBuffer.position() + boundary.sideVertexBuffer.limit());
1786
        }
1787
    }
1788

    
1789
    protected void createSideNormals(ShapeData shapeData)
1790
    {
1791
        int vertexCoordCount = this.totalFaceCount * 4 * 3; // 4 vertices of x,y,z per face
1792

    
1793
        if (shapeData.sideNormalBuffer != null && shapeData.sideNormalBuffer.capacity() >= vertexCoordCount)
1794
            shapeData.sideNormalBuffer.clear();
1795
        else
1796
            shapeData.sideNormalBuffer = Buffers.newDirectFloatBuffer(vertexCoordCount);
1797

    
1798
        // Create individual buffer slices for each boundary.
1799
        for (ExtrudedBoundaryInfo boundary : shapeData)
1800
        {
1801
            boundary.sideNormalBuffer = this.fillSideNormalBuffer(boundary.capVertices, boundary.baseVertices,
1802
                shapeData.sideNormalBuffer.slice());
1803
            shapeData.sideNormalBuffer.position(
1804
                shapeData.sideNormalBuffer.position() + boundary.sideNormalBuffer.limit());
1805
        }
1806
    }
1807

    
1808
    /**
1809
     * Creates the texture coordinates for this shape's sides.
1810
     *
1811
     * @param shapeData the current shape data.
1812
     */
1813
    protected void createSideTextureCoords(ShapeData shapeData)
1814
    {
1815
        // Create individual buffer slices for each boundary.
1816
        for (ExtrudedBoundaryInfo boundary : shapeData)
1817
        {
1818
            boolean applyTextureToThisBoundary = this.hasSideTextures()
1819
                && boundary.sideTextures != null && boundary.sideTextures.size() == boundary.faceCount;
1820

    
1821
            if (applyTextureToThisBoundary)
1822
            {
1823
                int texCoordSize = boundary.faceCount * 4 * 2; // n sides of 4 verts w/s,t
1824
                if (boundary.sideTextureCoords != null && boundary.sideTextureCoords.capacity() >= texCoordSize)
1825
                    boundary.sideTextureCoords.clear();
1826
                else
1827
                    boundary.sideTextureCoords = Buffers.newDirectFloatBuffer(texCoordSize);
1828

    
1829
                this.fillSideTexCoordBuffer(boundary.capVertices, boundary.baseVertices,
1830
                    boundary.sideTextureCoords);
1831
            }
1832
        }
1833
    }
1834

    
1835
    /**
1836
     * Compute the cap geometry.
1837
     *
1838
     * @param dc        the current draw context.
1839
     * @param shapeData boundary vertices are calculated during {@link #createMinimalGeometry(DrawContext,
1840
     *                  gov.nasa.worldwind.render.ExtrudedPolygon.ShapeData)}).
1841
     */
1842
    protected void createCapGeometry(DrawContext dc, ShapeData shapeData)
1843
    {
1844
        if (shapeData.capVertexBuffer != null
1845
            && shapeData.capVertexBuffer.capacity() >= this.totalNumLocations * 3)
1846
            shapeData.capVertexBuffer.clear();
1847
        else
1848
            shapeData.capVertexBuffer = Buffers.newDirectFloatBuffer(this.totalNumLocations * 3);
1849

    
1850
        // Fill the vertex buffer. Simultaneously create individual buffer slices for each boundary. These are used to
1851
        // draw the outline.
1852
        for (ExtrudedBoundaryInfo boundary : shapeData)
1853
        {
1854
            boundary.capVertexBuffer = WWBufferUtil.copyArrayToBuffer(boundary.capVertices,
1855
                shapeData.capVertexBuffer.slice());
1856
            shapeData.capVertexBuffer.position(
1857
                shapeData.capVertexBuffer.position() + boundary.capVertexBuffer.limit());
1858
        }
1859

    
1860
        if (shapeData.cb == null) // need to tessellate only once
1861
            this.createTessllationGeometry(dc, shapeData);
1862

    
1863
        this.generateCapInteriorIndices(shapeData);
1864
    }
1865

    
1866
    protected void createCapNormals(ShapeData shapeData)
1867
    {
1868
        if (shapeData.capNormalBuffer != null
1869
            && shapeData.capNormalBuffer.capacity() >= this.totalNumLocations * 3)
1870
            shapeData.capNormalBuffer.clear();
1871
        else
1872
            shapeData.capNormalBuffer = Buffers.newDirectFloatBuffer(shapeData.capVertexBuffer.capacity());
1873

    
1874
        for (ExtrudedBoundaryInfo boundary : shapeData)
1875
        {
1876
            boundary.capNormalBuffer = this.computeCapNormals(boundary, shapeData.capNormalBuffer.slice());
1877
            shapeData.capNormalBuffer.position(
1878
                shapeData.capNormalBuffer.position() + boundary.capNormalBuffer.limit());
1879
        }
1880
    }
1881

    
1882
    /**
1883
     * Compute normal vectors for an extruded polygon's cap vertices.
1884
     *
1885
     * @param boundary the boundary to compute normals for.
1886
     * @param nBuf     the buffer in which to place the computed normals. Must have enough remaining space to hold the
1887
     *                 normals.
1888
     *
1889
     * @return the buffer specified as input, with its limit incremented by the number of vertices copied, and its
1890
     *         position set to 0.
1891
     */
1892
    protected FloatBuffer computeCapNormals(ExtrudedBoundaryInfo boundary, FloatBuffer nBuf)
1893
    {
1894
        int nVerts = boundary.locations.size();
1895
        Vec4[] verts = boundary.capVertices;
1896
        double avgX, avgY, avgZ;
1897

    
1898
        // Compute normal for first point of boundary.
1899
        Vec4 va = verts[1].subtract3(verts[0]);
1900
        Vec4 vb = verts[nVerts - 2].subtract3(verts[0]); // nverts - 2 because last and first are same
1901
        avgX = (va.y * vb.z) - (va.z * vb.y);
1902
        avgY = (va.z * vb.x) - (va.x * vb.z);
1903
        avgZ = (va.x * vb.y) - (va.y * vb.x);
1904

    
1905
        // Compute normals for interior boundary points.
1906
        for (int i = 1; i < nVerts - 1; i++)
1907
        {
1908
            va = verts[i + 1].subtract3(verts[i]);
1909
            vb = verts[i - 1].subtract3(verts[i]);
1910
            avgX += (va.y * vb.z) - (va.z * vb.y);
1911
            avgY += (va.z * vb.x) - (va.x * vb.z);
1912
            avgZ += (va.x * vb.y) - (va.y * vb.x);
1913
        }
1914

    
1915
        avgX /= nVerts - 1;
1916
        avgY /= nVerts - 1;
1917
        avgZ /= nVerts - 1;
1918
        double length = Math.sqrt(avgX * avgX + avgY * avgY + avgZ * avgZ);
1919

    
1920
        for (int i = 0; i < nVerts; i++)
1921
        {
1922
            nBuf.put((float) (avgX / length)).put((float) (avgY / length)).put((float) (avgZ / length));
1923
        }
1924

    
1925
        nBuf.flip();
1926

    
1927
        return nBuf;
1928
    }
1929

    
1930
    protected FloatBuffer fillSideVertexBuffer(Vec4[] topVerts, Vec4[] bottomVerts, FloatBuffer vBuf)
1931
    {
1932
        // Forms the polygon's faces from its vertices, simultaneously copying the Cartesian coordinates from a Vec4
1933
        // array to a FloatBuffer.
1934

    
1935
        int faceCount = topVerts.length - 1;
1936
        int size = faceCount * 4 * 3; // n sides of 4 verts of x,y,z
1937
        vBuf.limit(size);
1938

    
1939
        // Fill the vertex buffer with coordinates for each independent face -- 4 vertices per face. Vertices need to be
1940
        // independent in order to have different texture coordinates and normals per face.
1941
        // For an n-sided polygon the vertex order is b0-b1-t1-t0, b1-b2-t2-t1, ... Note the counter-clockwise ordering.
1942
        for (int i = 0; i < faceCount; i++)
1943
        {
1944
            int v = i;
1945
            vBuf.put((float) bottomVerts[v].x).put((float) bottomVerts[v].y).put((float) bottomVerts[v].z);
1946
            v = i + 1;
1947
            vBuf.put((float) bottomVerts[v].x).put((float) bottomVerts[v].y).put((float) bottomVerts[v].z);
1948
            v = i + 1;
1949
            vBuf.put((float) topVerts[v].x).put((float) topVerts[v].y).put((float) topVerts[v].z);
1950
            v = i;
1951
            vBuf.put((float) topVerts[v].x).put((float) topVerts[v].y).put((float) topVerts[v].z);
1952
        }
1953

    
1954
        vBuf.flip();
1955

    
1956
        return vBuf;
1957
    }
1958

    
1959
    protected FloatBuffer fillSideNormalBuffer(Vec4[] topVerts, Vec4[] bottomVerts, FloatBuffer nBuf)
1960
    {
1961
        // This method parallels fillVertexBuffer. The normals are stored in exactly the same order.
1962

    
1963
        int faceCount = topVerts.length - 1;
1964
        int size = faceCount * 4 * 3; // n sides of 4 verts of x,y,z
1965
        nBuf.limit(size);
1966

    
1967
        for (int i = 0; i < faceCount; i++)
1968
        {
1969
            Vec4 va = topVerts[i + 1].subtract3(bottomVerts[i]);
1970
            Vec4 vb = topVerts[i].subtract3(bottomVerts[i + 1]);
1971
            Vec4 normal = va.cross3(vb).normalize3();
1972

    
1973
            nBuf.put((float) normal.x).put((float) normal.y).put((float) normal.z);
1974
            nBuf.put((float) normal.x).put((float) normal.y).put((float) normal.z);
1975
            nBuf.put((float) normal.x).put((float) normal.y).put((float) normal.z);
1976
            nBuf.put((float) normal.x).put((float) normal.y).put((float) normal.z);
1977
        }
1978

    
1979
        nBuf.flip();
1980

    
1981
        return nBuf;
1982
    }
1983

    
1984
    /**
1985
     * Computes the texture coordinates for a boundary of this shape.
1986
     *
1987
     * @param topVerts    the boundary's top Cartesian coordinates.
1988
     * @param bottomVerts the boundary's bottom Cartesian coordinates.
1989
     * @param tBuf        the buffer in which to place the computed texture coordinates.
1990
     */
1991
    protected void fillSideTexCoordBuffer(Vec4[] topVerts, Vec4[] bottomVerts, FloatBuffer tBuf)
1992
    {
1993
        int faceCount = topVerts.length - 1;
1994
        double lengths[] = new double[faceCount + 1];
1995

    
1996
        // Find the top-to-bottom lengths of the corners in order to determine their relative lengths.
1997
        for (int i = 0; i < faceCount; i++)
1998
        {
1999
            lengths[i] = bottomVerts[i].distanceTo3(topVerts[i]);
2000
        }
2001
        lengths[faceCount] = lengths[0]; // duplicate the first length to ease iteration below
2002

    
2003
        // Fill the vertex buffer with texture coordinates for each independent face in the same order as the vertices
2004
        // in the vertex buffer.
2005
        int b = 0;
2006
        for (int i = 0; i < faceCount; i++)
2007
        {
2008
            // Set the base texture coord to 0 for the longer side and a proportional value for the shorter side.
2009
            if (lengths[i] > lengths[i + 1])
2010
            {
2011
                tBuf.put(b++, 0).put(b++, 0);
2012
                tBuf.put(b++, 1).put(b++, (float) (1d - lengths[i + 1] / lengths[i]));
2013
            }
2014
            else
2015
            {
2016
                tBuf.put(b++, 0).put(b++, (float) (1d - lengths[i] / lengths[i + 1]));
2017
                tBuf.put(b++, 1).put(b++, 0);
2018
            }
2019
            tBuf.put(b++, 1).put(b++, 1);
2020
            tBuf.put(b++, 0).put(b++, 1);
2021
        }
2022
    }
2023

    
2024
    /**
2025
     * Returns the indices defining the cap vertices.
2026
     *
2027
     * @param n the number of positions in the polygon.
2028
     *
2029
     * @return a buffer of indices that can be passed to OpenGL to draw all the shape's edges.
2030
     */
2031
    protected IntBuffer getCapEdgeIndices(int n)
2032
    {
2033
        IntBuffer ib = capEdgeIndexBuffers.get(n);
2034
        if (ib != null)
2035
            return ib;
2036

    
2037
        // The edges are two-point lines connecting vertex pairs.
2038
        ib = Buffers.newDirectIntBuffer(2 * (n - 1) * 3);
2039
        for (int i = 0; i < n - 1; i++)
2040
        {
2041
            ib.put(i).put(i + 1);
2042
        }
2043

    
2044
        capEdgeIndexBuffers.put(n, ib);
2045

    
2046
        return ib;
2047
    }
2048

    
2049
    /**
2050
     * Returns the indices defining the vertices of each face of this extruded polygon.
2051
     *
2052
     * @param n the number of positions in this extruded polygon.
2053
     *
2054
     * @return a buffer of indices that can be passed to OpenGL to draw all face of the shape.
2055
     */
2056
    protected IntBuffer getSideIndices(int n)
2057
    {
2058
        IntBuffer ib = sideFillIndexBuffers.get(n);
2059
        if (ib != null)
2060
            return ib;
2061

    
2062
        // Compute them if not already computed. Each side is two triangles defined by one triangle strip. All edges
2063
        // can't be combined into one tri-strip because each side may have its own texture and therefore different
2064
        // texture coordinates.
2065
        ib = Buffers.newDirectIntBuffer(n * 4);
2066
        for (int i = 0; i < n; i++)
2067
        {
2068
            ib.put(4 * i + 3).put(4 * i).put(4 * i + 2).put(4 * i + 1);
2069
        }
2070

    
2071
        sideFillIndexBuffers.put(n, ib);
2072

    
2073
        return ib;
2074
    }
2075

    
2076
    /**
2077
     * Returns the indices defining the vertices of a boundary's face edges.
2078
     *
2079
     * @param n the number of positions in the boundary.
2080
     *
2081
     * @return a buffer of indices that can be passed to OpenGL to draw all the boundary's edges.
2082
     */
2083
    protected IntBuffer getSideEdgeIndices(int n)
2084
    {
2085
        IntBuffer ib = sideEdgeIndexBuffers.get(n);
2086
        if (ib != null)
2087
            return ib;
2088

    
2089
        int nn = n - 1; // the boundary is closed so don't add an edge for the redundant position.
2090

    
2091
        // The edges are two-point lines connecting vertex pairs.
2092

    
2093
        ib = Buffers.newDirectIntBuffer((2 * nn) * 3); // 2n each for top, bottom and corners
2094

    
2095
        // Top. Keep this first so that the top edge can be turned off independently.
2096
        for (int i = 0; i < nn; i++)
2097
        {
2098
            ib.put(4 * i + 2).put(4 * i + 3);
2099
        }
2100

    
2101
        // Bottom
2102
        for (int i = 0; i < nn; i++)
2103
        {
2104
            ib.put(4 * i).put(4 * i + 1);
2105
        }
2106

    
2107
        // Corners
2108
        for (int i = 0; i < nn; i++)
2109
        {
2110
            ib.put(4 * i).put(4 * i + 3);
2111
        }
2112

    
2113
        sideEdgeIndexBuffers.put(n, ib);
2114

    
2115
        return ib;
2116
    }
2117

    
2118
    @Override
2119
    protected void fillVBO(DrawContext dc)
2120
    {
2121
        // VBOs are not used by this shape type
2122
    }
2123

    
2124
    /**
2125
     * Tessellates this extruded polygon's cap. This method catches {@link OutOfMemoryError} exceptions and if the draw
2126
     * context is not null passes the exception to the rendering exception listener (see {@link
2127
     * WorldWindow#addRenderingExceptionListener(gov.nasa.worldwind.event.RenderingExceptionListener)}).
2128
     *
2129
     * @param dc        the draw context.
2130
     * @param shapeData the boundary set to tessellate
2131
     */
2132
    protected void createTessllationGeometry(DrawContext dc, ShapeData shapeData)
2133
    {
2134
        // Wrap polygon tessellation in a try/catch block. We do this to catch and handle OutOfMemoryErrors caused during
2135
        // tessellation of the polygon vertices.
2136
        try
2137
        {
2138
            Vec4 normal = this.computePolygonNormal(shapeData);
2139

    
2140
            // There's a fallback for non-computable normal in computePolygonNormal, but test here in case the fallback
2141
            // doesn't work either.
2142
            if (normal == null)
2143
            {
2144
                String message = Logging.getMessage("Geom.ShapeNormalVectorNotComputable", this);
2145
                Logging.logger().log(java.util.logging.Level.SEVERE, message);
2146
                shapeData.tessellationError = true;
2147
                return;
2148
            }
2149

    
2150
            this.tessellatePolygon(shapeData, normal.normalize3());
2151
        }
2152
        catch (OutOfMemoryError e)
2153
        {
2154
            String message = Logging.getMessage("generic.ExceptionWhileTessellating", this);
2155
            Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
2156

    
2157
            shapeData.tessellationError = true;
2158

    
2159
            if (dc != null)
2160
            {
2161
                //noinspection ThrowableInstanceNeverThrown
2162
                dc.addRenderingException(new WWRuntimeException(message, e));
2163
            }
2164
        }
2165
    }
2166

    
2167
    protected Vec4 computePolygonNormal(ShapeData shapeData)
2168
    {
2169
        Vec4 normal = WWMath.computeBufferNormal(shapeData.capVertexBuffer, 0);
2170

    
2171
        // The normal vector is null if this is a degenerate polygon representing a line or a single point. We fall
2172
        // back to using the globe's surface normal at the reference point. This allows the tessellator to process
2173
        // the degenerate polygon without generating an exception.
2174
        if (normal == null)
2175
        {
2176
            Globe globe = shapeData.getGlobeStateKey().getGlobe();
2177
            if (globe != null)
2178
                normal = globe.computeSurfaceNormalAtLocation(
2179
                    this.getReferencePosition().getLatitude(), this.getReferencePosition().getLongitude());
2180
        }
2181

    
2182
        return normal;
2183
    }
2184

    
2185
    /**
2186
     * Tessellates the polygon from its vertices.
2187
     *
2188
     * @param shapeData the polygon boundaries.
2189
     * @param normal    a unit normal vector for the plane containing the polygon vertices. Even though the the vertices
2190
     *                  might not be coplanar, only one representative normal is used for tessellation.
2191
     */
2192
    protected void tessellatePolygon(ShapeData shapeData, Vec4 normal)
2193
    {
2194
        GLUTessellatorSupport glts = new GLUTessellatorSupport();
2195
        shapeData.cb = new GLUTessellatorSupport.CollectIndexListsCallback();
2196

    
2197
        glts.beginTessellation(shapeData.cb, normal);
2198
        try
2199
        {
2200
            double[] coords = new double[3];
2201

    
2202
            GLU.gluTessBeginPolygon(glts.getGLUtessellator(), null);
2203

    
2204
            int k = 0;
2205
            for (ExtrudedBoundaryInfo boundary : shapeData)
2206
            {
2207
                GLU.gluTessBeginContour(glts.getGLUtessellator());
2208
                FloatBuffer vBuf = boundary.capVertexBuffer;
2209
                for (int i = 0; i < boundary.locations.size(); i++)
2210
                {
2211
                    coords[0] = vBuf.get(i * 3);
2212
                    coords[1] = vBuf.get(i * 3 + 1);
2213
                    coords[2] = vBuf.get(i * 3 + 2);
2214

    
2215
                    GLU.gluTessVertex(glts.getGLUtessellator(), coords, 0, k++);
2216
                }
2217
                GLU.gluTessEndContour(glts.getGLUtessellator());
2218
            }
2219

    
2220
            GLU.gluTessEndPolygon(glts.getGLUtessellator());
2221
        }
2222
        finally
2223
        {
2224
            // Free any heap memory used for tessellation immediately. If tessellation has consumed all available
2225
            // heap memory, we must free memory used by tessellation immediately or subsequent operations such as
2226
            // message logging will fail.
2227
            glts.endTessellation();
2228
        }
2229
    }
2230

    
2231
    /**
2232
     * Construct the lists of indices that identify the tessellated shape's vertices in the vertex buffer.
2233
     *
2234
     * @param shapeData the current shape data.
2235
     */
2236
    protected void generateCapInteriorIndices(ShapeData shapeData)
2237
    {
2238
        GLUTessellatorSupport.CollectIndexListsCallback cb = shapeData.cb;
2239

    
2240
        if (shapeData.capFillIndices == null || shapeData.capFillIndices.capacity() < cb.getNumIndices())
2241
            shapeData.capFillIndices = Buffers.newDirectIntBuffer(cb.getNumIndices());
2242
        else
2243
            shapeData.capFillIndices.clear();
2244

    
2245
        if (shapeData.capFillIndexBuffers == null || shapeData.capFillIndexBuffers.size() < cb.getPrimTypes().size())
2246
            shapeData.capFillIndexBuffers = new ArrayList<IntBuffer>(cb.getPrimTypes().size());
2247
        else
2248
            shapeData.capFillIndexBuffers.clear();
2249

    
2250
        for (List<Integer> prim : cb.getPrims())
2251
        {
2252
            IntBuffer ib = shapeData.capFillIndices.slice();
2253
            for (Integer i : prim)
2254
            {
2255
                ib.put(i);
2256
            }
2257
            ib.flip();
2258
            shapeData.capFillIndexBuffers.add(ib);
2259
            shapeData.capFillIndices.position(shapeData.capFillIndices.position() + ib.limit());
2260
        }
2261
    }
2262

    
2263
    protected boolean isSameAsPreviousTerrain(Terrain terrain)
2264
    {
2265
        if (terrain == null || terrain != this.previousIntersectionTerrain)
2266
            return false;
2267

    
2268
        //noinspection SimplifiableIfStatement
2269
        if (terrain.getVerticalExaggeration() != this.previousIntersectionTerrain.getVerticalExaggeration())
2270
            return false;
2271

    
2272
        return this.previousIntersectionGlobeStateKey != null &&
2273
            terrain.getGlobe().getGlobeStateKey().equals(this.previousIntersectionGlobeStateKey);
2274
    }
2275

    
2276
    /**
2277
     * Compute the intersections of a specified line with this extruded polygon. If the polygon's altitude mode is other
2278
     * than {@link WorldWind#ABSOLUTE}, the extruded polygon's geometry is created relative to the specified terrain
2279
     * rather than the terrain used during rendering, which may be at lower level of detail than required for accurate
2280
     * intersection determination.
2281
     *
2282
     * @param line    the line to intersect.
2283
     * @param terrain the {@link Terrain} to use when computing the extruded polygon's geometry.
2284
     *
2285
     * @return a list of intersections identifying where the line intersects the extruded polygon, or null if the line
2286
     *         does not intersect the extruded polygon.
2287
     *
2288
     * @throws InterruptedException if the operation is interrupted.
2289
     * @see Terrain
2290
     */
2291
    public List<Intersection> intersect(Line line, Terrain terrain) throws InterruptedException
2292
    {
2293
        if (!this.isEnableSides() && !this.isEnableCap())
2294
            return null;
2295

    
2296
        Position refPos = this.getReferencePosition();
2297
        if (refPos == null)
2298
            return null;
2299

    
2300
        if (!this.isOuterBoundaryValid())
2301
            return null;
2302

    
2303
        // Reuse the previously computed high-res boundary set if the terrain is the same.
2304
        ShapeData highResShapeData = this.isSameAsPreviousTerrain(terrain) ? this.previousIntersectionShapeData
2305
            : null;
2306

    
2307
        if (highResShapeData == null)
2308
        {
2309
            highResShapeData = this.createIntersectionGeometry(terrain);
2310
            if (highResShapeData == null)
2311
                return null;
2312

    
2313
            this.previousIntersectionShapeData = highResShapeData;
2314
            this.previousIntersectionTerrain = terrain;
2315
            this.previousIntersectionGlobeStateKey = terrain.getGlobe().getGlobeStateKey();
2316
        }
2317

    
2318
        if (highResShapeData.getExtent() != null && highResShapeData.getExtent().intersect(line) == null)
2319
            return null;
2320

    
2321
        final Line localLine = new Line(line.getOrigin().subtract3(highResShapeData.getReferencePoint()),
2322
            line.getDirection());
2323
        List<Intersection> intersections = new ArrayList<Intersection>();
2324

    
2325
        for (ExtrudedBoundaryInfo boundary : highResShapeData)
2326
        {
2327
            List<Intersection> boundaryIntersections = this.intersectBoundarySides(localLine, boundary);
2328

    
2329
            if (boundaryIntersections != null && boundaryIntersections.size() > 0)
2330
                intersections.addAll(boundaryIntersections);
2331
        }
2332

    
2333
        if (this.isEnableCap())
2334
            this.intersectCap(localLine, highResShapeData, intersections);
2335

    
2336
        if (intersections.size() == 0)
2337
            return null;
2338

    
2339
        for (Intersection intersection : intersections)
2340
        {
2341
            Vec4 pt = intersection.getIntersectionPoint().add3(highResShapeData.getReferencePoint());
2342
            intersection.setIntersectionPoint(pt);
2343

    
2344
            // Compute intersection position relative to ground.
2345
            Position pos = terrain.getGlobe().computePositionFromPoint(pt);
2346
            Vec4 gp = terrain.getSurfacePoint(pos.getLatitude(), pos.getLongitude(), 0);
2347
            double dist = Math.sqrt(pt.dotSelf3()) - Math.sqrt(gp.dotSelf3());
2348
            intersection.setIntersectionPosition(new Position(pos, dist));
2349

    
2350
            intersection.setObject(this);
2351
        }
2352

    
2353
        return intersections;
2354
    }
2355

    
2356
    protected ShapeData createIntersectionGeometry(Terrain terrain)
2357
    {
2358
        ShapeData shapeData = new ShapeData(null, this);
2359
        shapeData.setGlobeStateKey(terrain.getGlobe().getGlobeStateKey());
2360

    
2361
        this.computeReferencePoint(terrain, shapeData);
2362
        if (shapeData.getReferencePoint() == null)
2363
            return null;
2364

    
2365
        // Compute the boundary vertices first.
2366
        this.createVertices(terrain, shapeData, false);
2367

    
2368
        if (this.isEnableSides())
2369
            this.createSideGeometry(shapeData);
2370

    
2371
        if (this.isEnableCap())
2372
            this.createCapGeometry(null, shapeData);
2373

    
2374
        shapeData.setExtent(this.computeExtent(shapeData.getOuterBoundaryInfo(), shapeData.getReferencePoint()));
2375

    
2376
        return shapeData;
2377
    }
2378

    
2379
    /**
2380
     * Intersects a line with the sides of an individual boundary.
2381
     *
2382
     * @param line     the line to intersect.
2383
     * @param boundary the boundary to intersect.
2384
     *
2385
     * @return the computed intersections, or null if there are no intersections.
2386
     *
2387
     * @throws InterruptedException if the operation is interrupted.
2388
     */
2389
    protected List<Intersection> intersectBoundarySides(Line line, ExtrudedBoundaryInfo boundary)
2390
        throws InterruptedException
2391
    {
2392
        List<Intersection> intersections = new ArrayList<Intersection>();
2393
        Vec4[] topVertices = boundary.capVertices;
2394
        Vec4[] bottomVertices = boundary.baseVertices;
2395

    
2396
        for (int i = 0; i < boundary.baseVertices.length - 1; i++)
2397
        {
2398
            Vec4 va = bottomVertices[i];
2399
            Vec4 vb = topVertices[i + 1];
2400
            Vec4 vc = topVertices[i];
2401

    
2402
            Intersection intersection = Triangle.intersect(line, va, vb, vc);
2403
            if (intersection != null)
2404
                intersections.add(intersection);
2405

    
2406
            vc = bottomVertices[i + 1];
2407

    
2408
            intersection = Triangle.intersect(line, va, vb, vc);
2409
            if (intersection != null)
2410
                intersections.add(intersection);
2411
        }
2412

    
2413
        return intersections.size() > 0 ? intersections : null;
2414
    }
2415

    
2416
    protected void intersectCap(Line line, ShapeData shapeData, List<Intersection> intersections)
2417
        throws InterruptedException
2418
    {
2419
        if (shapeData.cb.getPrimTypes() == null)
2420
            return;
2421

    
2422
        for (int i = 0; i < shapeData.cb.getPrimTypes().size(); i++)
2423
        {
2424
            IntBuffer ib = shapeData.capFillIndexBuffers.get(i);
2425
            ib.rewind();
2426
            List<Intersection> ti = Triangle.intersectTriangleTypes(line, shapeData.capVertexBuffer, ib,
2427
                shapeData.cb.getPrimTypes().get(i));
2428

    
2429
            if (ti != null && ti.size() > 0)
2430
                intersections.addAll(ti);
2431
        }
2432
    }
2433

    
2434
    /**
2435
     * {@inheritDoc}
2436
     * <p/>
2437
     * Note that this method overwrites the boundary locations lists, and therefore no longer refer to the originally
2438
     * specified boundary lists.
2439
     *
2440
     * @param position the new position of the shape's reference position.
2441
     */
2442
    public void moveTo(Position position)
2443
    {
2444
        if (position == null)
2445
        {
2446
            String msg = Logging.getMessage("nullValue.PositionIsNull");
2447
            Logging.logger().severe(msg);
2448
            throw new IllegalArgumentException(msg);
2449
        }
2450

    
2451
        if (!this.isOuterBoundaryValid())
2452
            return;
2453

    
2454
        Position oldPosition = this.getReferencePosition();
2455
        if (oldPosition == null)
2456
            return;
2457

    
2458
        List<List<? extends LatLon>> newLocations = new ArrayList<List<? extends LatLon>>(this.boundaries.size());
2459

    
2460
        for (List<? extends LatLon> boundary : this.boundaries)
2461
        {
2462
            if (boundary == null || boundary.size() == 0)
2463
                continue;
2464

    
2465
            List<LatLon> newList = LatLon.computeShiftedLocations(oldPosition, position, boundary);
2466
            if (newList == null)
2467
                continue;
2468

    
2469
            // Must convert the new locations to positions if the old ones were positions.
2470
            for (int i = 0; i < boundary.size(); i++)
2471
            {
2472
                if (boundary.get(i) instanceof Position)
2473
                    newList.set(i, new Position(newList.get(i), ((Position) boundary.get(i)).getAltitude()));
2474
            }
2475

    
2476
            newLocations.add(newList);
2477
        }
2478

    
2479
        this.boundaries = newLocations;
2480
        this.setReferencePosition(position);
2481
        this.reset();
2482
    }
2483

    
2484
    protected void doExportAsKML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException
2485
    {
2486
        // Write geometry
2487
        xmlWriter.writeStartElement("Polygon");
2488

    
2489
        xmlWriter.writeStartElement("extrude");
2490
        xmlWriter.writeCharacters("1");
2491
        xmlWriter.writeEndElement();
2492

    
2493
        final String altitudeMode = KMLExportUtil.kmlAltitudeMode(getAltitudeMode());
2494
        xmlWriter.writeStartElement("altitudeMode");
2495
        xmlWriter.writeCharacters(altitudeMode);
2496
        xmlWriter.writeEndElement();
2497

    
2498
        this.writeKMLBoundaries(xmlWriter);
2499

    
2500
        xmlWriter.writeEndElement(); // Polygon
2501
    }
2502

    
2503
    protected void writeKMLBoundaries(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException
2504
    {
2505
        // Outer boundary
2506
        Iterable<? extends LatLon> outerBoundary = this.getOuterBoundary();
2507
        if (outerBoundary != null)
2508
        {
2509
            xmlWriter.writeStartElement("outerBoundaryIs");
2510
            if (outerBoundary.iterator().hasNext() && outerBoundary.iterator().next() instanceof Position)
2511
                this.exportBoundaryAsLinearRing(xmlWriter, outerBoundary);
2512
            else
2513
                KMLExportUtil.exportBoundaryAsLinearRing(xmlWriter, outerBoundary, getHeight());
2514
            xmlWriter.writeEndElement(); // outerBoundaryIs
2515
        }
2516

    
2517
        // Inner boundaries
2518
        Iterator<List<? extends LatLon>> boundaryIterator = this.boundaries.iterator();
2519
        if (boundaryIterator.hasNext())
2520
            boundaryIterator.next(); // Skip outer boundary, we already dealt with it above
2521

    
2522
        while (boundaryIterator.hasNext())
2523
        {
2524
            List<? extends LatLon> boundary = boundaryIterator.next();
2525

    
2526
            xmlWriter.writeStartElement("innerBoundaryIs");
2527
            if (boundary.iterator().hasNext() && boundary.iterator().next() instanceof Position)
2528
                this.exportBoundaryAsLinearRing(xmlWriter, outerBoundary);
2529
            else
2530
                KMLExportUtil.exportBoundaryAsLinearRing(xmlWriter, boundary, getHeight());
2531
            xmlWriter.writeEndElement(); // innerBoundaryIs
2532
        }
2533
    }
2534

    
2535
    /**
2536
     * Writes the boundary in KML as either a list of lat, lon, altitude tuples or lat, lon tuples, depending on the
2537
     * type originally specified.
2538
     *
2539
     * @param xmlWriter the XML writer.
2540
     * @param boundary  the boundary to write.
2541
     *
2542
     * @throws XMLStreamException if an error occurs during writing.
2543
     */
2544
    protected void exportBoundaryAsLinearRing(XMLStreamWriter xmlWriter, Iterable<? extends LatLon> boundary)
2545
        throws XMLStreamException
2546
    {
2547
        xmlWriter.writeStartElement("LinearRing");
2548
        xmlWriter.writeStartElement("coordinates");
2549
        for (LatLon location : boundary)
2550
        {
2551
            if (location instanceof Position)
2552
            {
2553
                xmlWriter.writeCharacters(String.format(Locale.US, "%f,%f,%f ",
2554
                    location.getLongitude().getDegrees(),
2555
                    location.getLatitude().getDegrees(),
2556
                    ((Position) location).getAltitude()));
2557
            }
2558
            else
2559
            {
2560
                xmlWriter.writeCharacters(String.format(Locale.US, "%f,%f ",
2561
                    location.getLongitude().getDegrees(),
2562
                    location.getLatitude().getDegrees()));
2563
            }
2564
        }
2565
        xmlWriter.writeEndElement(); // coordinates
2566
        xmlWriter.writeEndElement(); // LinearRing
2567
    }
2568
}