Update to Worldwind release 0.4.1
[worldwind-tracker.git] / gov / nasa / worldwind / render / Polyline.java
blob7976d31d1b603d8adc594cb933c3be75328d4112
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
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;
16 import java.awt.*;
17 import java.nio.DoubleBuffer;
18 import java.util.ArrayList;
20 /**
21 * @author tag
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;
50 private Globe globe;
51 private int numSubsegments = 10;
53 public Polyline()
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);
68 private void reset()
70 if (this.currentSpans != null)
71 this.currentSpans.clear();
72 this.currentSpans = null;
75 public Color getColor()
77 return color;
80 public void setColor(Color color)
82 if (color == null)
84 String msg = Logging.getMessage("nullValue.ColorIsNull");
85 Logging.logger().severe(msg);
86 throw new IllegalArgumentException(msg);
89 this.color = color;
92 public int getAntiAliasHint()
94 return antiAliasHint;
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()
111 return filled;
114 public void setFilled(boolean filled)
116 this.filled = filled;
119 public int getPathType()
121 return pathType;
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
127 * segment endpoints.
129 * @param pathType the type of path to draw.
131 public void setPathType(int pathType)
133 this.reset();
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)
152 this.reset();
153 this.followTerrain = followTerrain;
156 public double getOffset()
158 return offset;
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)
169 this.reset();
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()
192 return lineWidth;
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()
209 return length;
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)
259 this.reset();
260 this.numSubsegments = numSubsegments;
264 * Specifies the path's positions.
266 * @param inPositions the path positions.
268 public void setPositions(Iterable<Position> inPositions)
270 this.reset();
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)
296 this.reset();
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)
321 if (dc == null)
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)
331 return;
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)
341 return;
343 GL gl = dc.getGL();
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);
374 else
376 gl.glDisable(GL.GL_LINE_STIPPLE);
379 int hintAttr = GL.GL_LINE_SMOOTH_HINT;
380 if (this.filled)
381 hintAttr = GL.GL_POLYGON_SMOOTH_HINT;
382 gl.glHint(hintAttr, this.antiAliasHint);
384 int primType = GL.GL_LINE_STRIP;
385 if (this.filled)
386 primType = GL.GL_POLYGON;
388 gl.glLineWidth((float) this.lineWidth);
390 gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
391 if (this.followTerrain)
392 this.pushOffest(dc);
393 for (ArrayList<Vec4> span : this.currentSpans)
395 if (span == null)
396 continue;
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)
404 this.popOffest(dc);
406 finally
408 gl.glPopClientAttrib();
409 gl.glPopAttrib();
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.
418 GL gl = dc.getGL();
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);
426 gl.glPushMatrix();
427 gl.glLoadMatrixf(pm, 0);
430 private void popOffest(DrawContext dc)
432 GL gl = dc.getGL();
433 gl.glMatrixMode(GL.GL_PROJECTION);
434 gl.glPopMatrix();
435 gl.glPopAttrib();
438 private DoubleBuffer bufferVertices(ArrayList<Vec4> vertArray)
440 if (vertArray == null)
441 return 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);
447 return db;
450 protected void makeVertices(DrawContext dc)
452 if (this.currentSpans == null)
453 this.currentSpans = new ArrayList<ArrayList<Vec4>>();
454 else
455 this.currentSpans.clear();
457 this.length = 0;
459 if (this.positions.size() < 1)
460 return;
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))
469 posA = posB;
470 continue;
473 ArrayList<Vec4> span;
474 span = this.makeSegment(dc, posA, posB);
476 if (span != null)
477 this.addSpan(span);
479 posA = posB;
483 private void addSpan(ArrayList<Vec4> span)
485 if (span == null || span.size() < 1)
486 return;
488 if (this.currentSpans.size() < 1)
490 this.currentSpans.add(span);
491 return;
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);
502 if (f.contains(ptA))
503 return true;
505 Vec4 ptB = this.computePoint(dc, posB, true);
506 if (f.contains(ptB))
507 return true;
509 if (ptA.equals(ptB))
510 return false;
512 Position posC = Position.interpolate(0.5, posA, posB);
513 Vec4 ptC = this.computePoint(dc, posC, true);
514 if (f.contains(ptC))
515 return 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);
530 else
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);
548 else
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
560 Vec4 ptB;
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);
570 return span;
573 Vec4 origPtA = null;
574 Vec4 axis = null;
575 Quaternion qA = null;
576 Quaternion qB = null;
578 for (double s = 0, p = 0; s < 1;)
580 if (origPtA == null)
582 origPtA = ptA;
583 ptA = this.computePoint(dc, posA, true);
586 if (this.followTerrain)
587 p += this.terrainConformance * dc.getView().computePixelSizeAtDistance(
588 ptA.distanceTo3(dc.getView().getEyePoint()));
589 else
590 p += arcLength / this.numSubsegments;
592 s = p / arcLength;
594 Position pos;
595 if (s >= 1)
597 pos = posB;
599 else if (this.pathType == LINEAR)
601 pos = Position.interpolate(s, posA, posB);
603 else
605 if (axis == null)
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);
622 ptA = ptB;
625 return span;
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)
633 // {
634 // if (span != null)
635 // {
636 // this.addSpan(span);
637 // span = null;
638 // }
639 // return span;
640 // }
642 if (span == null)
643 span = this.addPointToSpan(ptA, span);
645 return this.addPointToSpan(ptB, span);
648 private ArrayList<Vec4> addPointToSpan(Vec4 p, ArrayList<Vec4> span)
650 if (span == null)
651 span = new ArrayList<Vec4>();
653 span.add(p.subtract3(this.referenceCenterPoint));
655 return span;
658 private void computeReferenceCenter(DrawContext dc)
660 if (this.positions.size() < 1)
661 return;
663 if (this.positions.size() < 3)
664 this.referenceCenterPosition = this.positions.get(0);
665 else
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);
681 if (p == null)
683 p = dc.getGlobe().computePointFromPosition(lat, lon,
684 offset + dc.getGlobe().getElevation(lat, lon) * dc.getVerticalExaggeration());
687 return p;
690 public void move(Position delta)
692 if (delta == null)
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);
711 this.reset();
713 if (this.positions.size() < 1)
714 return;
716 Vec4 origRef = this.referenceCenterPoint;
717 Vec4 newRef = this.globe.computePointFromPosition(position);
718 Angle distance =
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);