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