2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
7 package gov
.nasa
.worldwind
.render
;
9 import com
.sun
.opengl
.util
.BufferUtil
;
10 import gov
.nasa
.worldwind
.Movable
;
11 import gov
.nasa
.worldwind
.globes
.Globe
;
12 import gov
.nasa
.worldwind
.geom
.*;
13 import gov
.nasa
.worldwind
.util
.Logging
;
15 import javax
.media
.opengl
.GL
;
17 import java
.nio
.DoubleBuffer
;
18 import java
.util
.ArrayList
;
22 * @version $Id: Polyline.java 3608 2007-11-22 16:44:28Z tgaskins $
24 public class Polyline
implements Renderable
, Movable
26 public final static int GREAT_CIRCLE
= 0;
27 public final static int LINEAR
= 1;
28 // public final static int RHUMB_LINE = 1;
29 // public final static int LOXODROME = RHUMB_LINE;
31 public final static int ANTIALIAS_DONT_CARE
= GL
.GL_DONT_CARE
;
32 public final static int ANTIALIAS_FASTEST
= GL
.GL_FASTEST
;
33 public final static int ANTIALIAS_NICEST
= GL
.GL_NICEST
;
35 private ArrayList
<Position
> positions
;
36 private Vec4 referenceCenterPoint
;
37 private Position referenceCenterPosition
= Position
.ZERO
;
38 private int antiAliasHint
= GL
.GL_FASTEST
;
39 private Color color
= Color
.WHITE
;
40 private double lineWidth
= 1;
41 private boolean filled
= false; // makes it a polygon
42 private boolean followTerrain
= false;
43 private double offset
= 0;
44 private double terrainConformance
= 10;
45 private int pathType
= GREAT_CIRCLE
;
46 private ArrayList
<ArrayList
<Vec4
>> currentSpans
;
47 private double length
;
48 private short stipplePattern
= (short) 0xAAAA;
49 private int stippleFactor
= 0;
51 private int numSubsegments
= 10;
55 this.setPositions(null);
58 public Polyline(Iterable
<Position
> positions
)
60 this.setPositions(positions
);
63 public Polyline(Iterable
<LatLon
> positions
, double elevation
)
65 this.setPositions(positions
, elevation
);
70 if (this.currentSpans
!= null)
71 this.currentSpans
.clear();
72 this.currentSpans
= null;
75 public Color
getColor()
80 public void setColor(Color color
)
84 String msg
= Logging
.getMessage("nullValue.ColorIsNull");
85 Logging
.logger().severe(msg
);
86 throw new IllegalArgumentException(msg
);
92 public int getAntiAliasHint()
97 public void setAntiAliasHint(int hint
)
99 if (!(hint
== ANTIALIAS_DONT_CARE
|| hint
== ANTIALIAS_FASTEST
|| hint
== ANTIALIAS_NICEST
))
101 String msg
= Logging
.getMessage("generic.InvalidHint");
102 Logging
.logger().severe(msg
);
103 throw new IllegalArgumentException(msg
);
106 this.antiAliasHint
= hint
;
109 public boolean isFilled()
114 public void setFilled(boolean filled
)
116 this.filled
= filled
;
119 public int getPathType()
125 * Sets the type of path to draw, one of {@link #GREAT_CIRCLE}, which draws each segment of the path as a great
126 * circle, or {@link #LINEAR}, which determines the intermediate positions between segments by interpolating the
129 * @param pathType the type of path to draw.
131 public void setPathType(int pathType
)
134 this.pathType
= pathType
;
137 public boolean isFollowTerrain()
139 return followTerrain
;
143 * Indicates whether the path should follow the terrain's surface. If the value is <code>true</code>, the elevation
144 * values in this path's positions are ignored and the path is drawn on the terrain surface. Otherwise the path is
145 * drawn according to the elevations given in the path's positions. If following the terrain, the path may also have
146 * an offset. See {@link #setOffset(double)};
148 * @param followTerrain <code>true</code> to follow the terrain, otherwise <code>false</code>.
150 public void setFollowTerrain(boolean followTerrain
)
153 this.followTerrain
= followTerrain
;
156 public double getOffset()
162 * Specifies an offset, in meters, to add to the path points when the path's follow-terrain attribute is true. See
163 * {@link #setFollowTerrain(boolean)}.
165 * @param offset the path pffset in meters.
167 public void setOffset(double offset
)
170 this.offset
= offset
;
173 public double getTerrainConformance()
175 return terrainConformance
;
179 * Specifies the precision to which the path follows the terrain when the follow-terrain attribute it true. The
180 * conformance value indicates the approximate length of each sub-segment of the path as it's drawn, in pixels.
181 * Lower values specify higher precision, but at the cost of performance.
183 * @param terrainConformance the path conformance in pixels.
185 public void setTerrainConformance(double terrainConformance
)
187 this.terrainConformance
= terrainConformance
;
190 public double getLineWidth()
195 public void setLineWidth(double lineWidth
)
197 this.lineWidth
= lineWidth
;
201 * Returns the length of the line as drawn. If the path follows the terrain, the length returned is the distance one
202 * would travel if on the surface. If the path does not follow the terrain, the length returned is the distance
203 * along the full length of the path at the path's elevations and current path type.
205 * @return the path's length in meters.
207 public double getLength()
212 public short getStipplePattern()
214 return stipplePattern
;
218 * Sets the stipple pattern for specifying line types other than solid. See the OpenGL specification or programming
219 * guides for a description of this parameter. Stipple is also affected by the path's stipple factor, {@link
220 * #setStippleFactor(int)}.
222 * @param stipplePattern the stipple pattern.
224 public void setStipplePattern(short stipplePattern
)
226 this.stipplePattern
= stipplePattern
;
229 public int getStippleFactor()
231 return stippleFactor
;
235 * Sets the stipple factor for specifying line types other than solid. See the OpenGL specification or programming
236 * guides for a description of this parameter. Stipple is also affected by the path's stipple pattern, {@link
237 * #setStipplePattern(short)}.
239 * @param stippleFactor the stipple factor.
241 public void setStippleFactor(int stippleFactor
)
243 this.stippleFactor
= stippleFactor
;
246 public int getNumSubsegments()
248 return numSubsegments
;
252 * Specifies the number of intermediate segments to draw for each segment between positions. The end points of the
253 * intermediate segments are calculated according to the current path type and follow-terrain setting.
255 * @param numSubsegments
257 public void setNumSubsegments(int numSubsegments
)
260 this.numSubsegments
= numSubsegments
;
264 * Specifies the path's positions.
266 * @param inPositions the path positions.
268 public void setPositions(Iterable
<Position
> inPositions
)
271 this.positions
= new ArrayList
<Position
>();
272 if (inPositions
!= null)
274 for (Position position
: inPositions
)
276 this.positions
.add(position
);
280 if ((this.filled
&& this.positions
.size() < 3))
282 String msg
= Logging
.getMessage("generic.InsufficientPositions");
283 Logging
.logger().severe(msg
);
284 throw new IllegalArgumentException(msg
);
289 * Sets the paths positions as latitude and longitude values at a constant altitude.
291 * @param inPositions the latitudes and longitudes of the positions.
292 * @param elevation the elevation to assign each position.
294 public void setPositions(Iterable
<LatLon
> inPositions
, double elevation
)
297 this.positions
= new ArrayList
<Position
>();
298 if (inPositions
!= null)
300 for (LatLon position
: inPositions
)
302 this.positions
.add(new Position(position
, elevation
));
306 if (this.filled
&& this.positions
.size() < 3)
308 String msg
= Logging
.getMessage("generic.InsufficientPositions");
309 Logging
.logger().severe(msg
);
310 throw new IllegalArgumentException(msg
);
314 public Iterable
<Position
> getPositions()
316 return this.positions
;
319 public void render(DrawContext dc
)
323 String message
= Logging
.getMessage("nullValue.DrawContextIsNull");
324 Logging
.logger().severe(message
);
325 throw new IllegalStateException(message
);
328 this.globe
= dc
.getGlobe();
330 if (this.positions
.size() < 2)
333 if (this.currentSpans
== null || this.followTerrain
) // vertices computed every frame to follow terrain changes
335 // Reference center must be computed prior to computing vertices.
336 this.computeReferenceCenter(dc
);
337 this.makeVertices(dc
);
340 if (this.currentSpans
== null || this.currentSpans
.size() < 1)
345 int attrBits
= GL
.GL_HINT_BIT
| GL
.GL_CURRENT_BIT
| GL
.GL_LINE_BIT
;
346 if (!dc
.isPickingMode())
348 if (this.color
.getAlpha() != 255)
349 attrBits
|= GL
.GL_COLOR_BUFFER_BIT
;
352 gl
.glPushAttrib(attrBits
);
353 gl
.glPushClientAttrib(GL
.GL_CLIENT_VERTEX_ARRAY_BIT
);
354 dc
.getView().pushReferenceCenter(dc
, this.referenceCenterPoint
);
358 if (!dc
.isPickingMode())
360 if (this.color
.getAlpha() != 255)
362 gl
.glEnable(GL
.GL_BLEND
);
363 gl
.glBlendFunc(GL
.GL_SRC_ALPHA
, GL
.GL_ONE_MINUS_SRC_ALPHA
);
365 dc
.getGL().glColor4ub((byte) this.color
.getRed(), (byte) this.color
.getGreen(),
366 (byte) this.color
.getBlue(), (byte) this.color
.getAlpha());
369 if (this.stippleFactor
> 0)
371 gl
.glEnable(GL
.GL_LINE_STIPPLE
);
372 gl
.glLineStipple(this.stippleFactor
, this.stipplePattern
);
376 gl
.glDisable(GL
.GL_LINE_STIPPLE
);
379 int hintAttr
= GL
.GL_LINE_SMOOTH_HINT
;
381 hintAttr
= GL
.GL_POLYGON_SMOOTH_HINT
;
382 gl
.glHint(hintAttr
, this.antiAliasHint
);
384 int primType
= GL
.GL_LINE_STRIP
;
386 primType
= GL
.GL_POLYGON
;
388 gl
.glLineWidth((float) this.lineWidth
);
390 gl
.glEnableClientState(GL
.GL_VERTEX_ARRAY
);
391 if (this.followTerrain
)
393 for (ArrayList
<Vec4
> span
: this.currentSpans
)
398 DoubleBuffer vertBuffer
= this.bufferVertices(span
);
400 gl
.glVertexPointer(3, GL
.GL_DOUBLE
, 0, vertBuffer
.rewind());
401 gl
.glDrawArrays(primType
, 0, vertBuffer
.capacity() / 3);
403 if (this.followTerrain
)
408 gl
.glPopClientAttrib();
410 dc
.getView().popReferenceCenter(dc
);
414 private void pushOffest(DrawContext dc
)
416 // Modify the projection transform to shift the depth values slightly toward the camera in order to
417 // ensure the lines are selected during depth buffering.
420 float[] pm
= new float[16];
421 gl
.glGetFloatv(GL
.GL_PROJECTION_MATRIX
, pm
, 0);
422 pm
[10] *= 0.99; // TODO: See Lengyel 2 ed. Section 9.1.2 to compute optimal/minimal offset
424 gl
.glPushAttrib(GL
.GL_TRANSFORM_BIT
);
425 gl
.glMatrixMode(GL
.GL_PROJECTION
);
427 gl
.glLoadMatrixf(pm
, 0);
430 private void popOffest(DrawContext dc
)
433 gl
.glMatrixMode(GL
.GL_PROJECTION
);
438 private DoubleBuffer
bufferVertices(ArrayList
<Vec4
> vertArray
)
440 if (vertArray
== null)
443 DoubleBuffer db
= BufferUtil
.newDoubleBuffer(3 * vertArray
.size());
444 for (Vec4 v
: vertArray
)
445 db
.put(v
.x
).put(v
.y
).put(v
.z
);
450 protected void makeVertices(DrawContext dc
)
452 if (this.currentSpans
== null)
453 this.currentSpans
= new ArrayList
<ArrayList
<Vec4
>>();
455 this.currentSpans
.clear();
459 if (this.positions
.size() < 1)
462 Position posA
= this.positions
.get(0);
463 for (int i
= 1; i
< this.positions
.size(); i
++)
465 Position posB
= this.positions
.get(i
);
467 if (this.followTerrain
&& !this.isSegmentVisible(dc
, posA
, posB
))
473 ArrayList
<Vec4
> span
;
474 span
= this.makeSegment(dc
, posA
, posB
);
483 private void addSpan(ArrayList
<Vec4
> span
)
485 if (span
== null || span
.size() < 1)
488 if (this.currentSpans
.size() < 1)
490 this.currentSpans
.add(span
);
494 this.currentSpans
.add(span
);
497 private boolean isSegmentVisible(DrawContext dc
, Position posA
, Position posB
)
499 Frustum f
= dc
.getView().getFrustumInModelCoordinates();
501 Vec4 ptA
= this.computePoint(dc
, posA
, true);
505 Vec4 ptB
= this.computePoint(dc
, posB
, true);
512 Position posC
= Position
.interpolate(0.5, posA
, posB
);
513 Vec4 ptC
= this.computePoint(dc
, posC
, true);
517 double r
= Line
.distanceToSegment(ptA
, ptB
, ptC
);
518 Cylinder cyl
= new Cylinder(ptA
, ptB
, r
== 0 ?
1 : r
);
520 return cyl
.intersects(dc
.getView().getFrustumInModelCoordinates());
523 private Vec4
computePoint(DrawContext dc
, Position pos
, boolean applyOffset
)
525 if (this.followTerrain
)
527 double height
= !applyOffset ?
0 : this.offset
;
528 return this.computeTerrainPoint(dc
, pos
.getLatitude(), pos
.getLongitude(), height
);
532 double height
= pos
.getElevation() + (applyOffset ?
this.offset
: 0);
533 return dc
.getGlobe().computePointFromPosition(pos
.getLatitude(), pos
.getLongitude(), height
);
537 private double computeSegmentLength(DrawContext dc
, Position posA
, Position posB
)
539 LatLon llA
= new LatLon(posA
.getLatitude(), posA
.getLongitude());
540 LatLon llB
= new LatLon(posB
.getLatitude(), posB
.getLongitude());
542 Angle ang
= LatLon
.sphericalDistance(llA
, llB
);
544 if (this.followTerrain
)
546 return ang
.radians
* (dc
.getGlobe().getRadius() + this.offset
);
550 double height
= this.offset
+ 0.5 * (posA
.getElevation() + posB
.getElevation());
551 return ang
.radians
* (dc
.getGlobe().getRadius() + height
);
555 private ArrayList
<Vec4
> makeSegment(DrawContext dc
, Position posA
, Position posB
)
557 ArrayList
<Vec4
> span
= null;
559 Vec4 ptA
= this.computePoint(dc
, posA
, false); // point w/o offset applied
562 double arcLength
= this.computeSegmentLength(dc
, posA
, posB
);
563 if (arcLength
<= 0) // points differing only in altitude
565 ptA
= this.computePoint(dc
, posA
, true); // points w/o offset applied
566 ptB
= this.computePoint(dc
, posB
, true);
567 span
= this.addPointToSpan(ptA
, span
);
568 if (!ptA
.equals(ptB
))
569 span
= this.addPointToSpan(ptB
, span
);
575 Quaternion qA
= null;
576 Quaternion qB
= null;
578 for (double s
= 0, p
= 0; s
< 1;)
583 ptA
= this.computePoint(dc
, posA
, true);
586 if (this.followTerrain
)
587 p
+= this.terrainConformance
* dc
.getView().computePixelSizeAtDistance(
588 ptA
.distanceTo3(dc
.getView().getEyePoint()));
590 p
+= arcLength
/ this.numSubsegments
;
599 else if (this.pathType
== LINEAR
)
601 pos
= Position
.interpolate(s
, posA
, posB
);
607 ptB
= this.computePoint(dc
, posB
, false);
608 axis
= origPtA
.cross3(ptB
).normalize3();
609 Angle ang
= origPtA
.angleBetween3(ptB
);
610 qA
= Quaternion
.fromAxisAngle(Angle
.ZERO
, axis
);
611 qB
= Quaternion
.fromAxisAngle(ang
, axis
);
613 Quaternion q
= Quaternion
.slerp(s
, qA
, qB
);
614 Vec4 pp
= origPtA
.transformBy3(q
);
615 pos
= dc
.getGlobe().computePositionFromPoint(pp
);
618 ptB
= this.computePoint(dc
, pos
, true);
619 span
= this.clipAndAdd(dc
, ptA
, ptB
, span
);
620 this.length
+= ptA
.distanceTo3(ptB
);
628 private ArrayList
<Vec4
> clipAndAdd(DrawContext dc
, Vec4 ptA
, Vec4 ptB
, ArrayList
<Vec4
> span
)
630 // Line clipping appears to be useful only for long lines with few segments. It's costly otherwise.
631 // TODO: Investigate trade-off of line clipping.
632 // if (Line.clipToFrustum(ptA, ptB, dc.getView().getFrustumInModelCoordinates()) == null)
636 // this.addSpan(span);
643 span
= this.addPointToSpan(ptA
, span
);
645 return this.addPointToSpan(ptB
, span
);
648 private ArrayList
<Vec4
> addPointToSpan(Vec4 p
, ArrayList
<Vec4
> span
)
651 span
= new ArrayList
<Vec4
>();
653 span
.add(p
.subtract3(this.referenceCenterPoint
));
658 private void computeReferenceCenter(DrawContext dc
)
660 if (this.positions
.size() < 1)
663 if (this.positions
.size() < 3)
664 this.referenceCenterPosition
= this.positions
.get(0);
666 this.referenceCenterPosition
= this.positions
.get(this.positions
.size() / 2);
668 this.referenceCenterPoint
= this.computeTerrainPoint(dc
,
669 this.referenceCenterPosition
.getLatitude(), this.referenceCenterPosition
.getLongitude(), this.offset
);
672 public Position
getReferencePosition()
674 return this.referenceCenterPosition
;
677 private Vec4
computeTerrainPoint(DrawContext dc
, Angle lat
, Angle lon
, double offset
)
679 Vec4 p
= dc
.getSurfaceGeometry().getSurfacePoint(lat
, lon
, offset
);
683 p
= dc
.getGlobe().computePointFromPosition(lat
, lon
,
684 offset
+ dc
.getGlobe().getElevation(lat
, lon
) * dc
.getVerticalExaggeration());
690 public void move(Position delta
)
694 String msg
= Logging
.getMessage("nullValue.PositionIsNull");
695 Logging
.logger().severe(msg
);
696 throw new IllegalArgumentException(msg
);
699 this.moveTo(this.getReferencePosition().add(delta
));
702 public void moveTo(Position position
)
704 if (position
== null)
706 String msg
= Logging
.getMessage("nullValue.PositionIsNull");
707 Logging
.logger().severe(msg
);
708 throw new IllegalArgumentException(msg
);
713 if (this.positions
.size() < 1)
716 Vec4 origRef
= this.referenceCenterPoint
;
717 Vec4 newRef
= this.globe
.computePointFromPosition(position
);
719 LatLon
.sphericalDistance(this.referenceCenterPosition
.getLatLon(), position
.getLatLon());
720 Vec4 axis
= origRef
.cross3(newRef
).normalize3();
721 Quaternion q
= Quaternion
.fromAxisAngle(distance
, axis
);
723 for (int i
= 0; i
< this.positions
.size(); i
++)
725 Position pos
= this.positions
.get(i
);
726 Vec4 p
= this.globe
.computePointFromPosition(pos
);
727 p
= p
.transformBy3(q
);
728 pos
= this.globe
.computePositionFromPoint(p
);
729 this.positions
.set(i
, pos
);