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
.globes
;
9 import com
.sun
.opengl
.util
.*;
10 import gov
.nasa
.worldwind
.*;
11 import gov
.nasa
.worldwind
.geom
.*;
13 import javax
.media
.opengl
.*;
20 public class EllipsoidIcosahedralTessellator
extends WWObjectImpl
implements Tessellator
22 // TODO: This class works as of 3/15/07 but it is not complete. There is a problem with texture coordinate
23 // generation around +-20 degrees latitude, and picking and meridian/parallel lines are not implemented.
24 // Also needs skirt creation.
25 private static int DEFAULT_DENSITY
= 20;
26 private static final int DEFAULT_MAX_LEVEL
= 14;
28 private static class GlobeInfo
30 private final Globe globe
; // TODO: remove the dependency on this
31 private final double level0EdgeLength
;
32 private final double invAsq
;
33 private final double invCsq
;
35 static final double EDGE_FACTOR
= Math
.sqrt(10d
+ 2d
* Math
.sqrt(5d
)) / 4d
;
37 private GlobeInfo(Globe globe
)
40 double equatorialRadius
= globe
.getEquatorialRadius();
41 double polarRadius
= globe
.getPolarRadius();
43 this.invAsq
= 1 / (equatorialRadius
* equatorialRadius
);
44 this.invCsq
= 1 / (polarRadius
* polarRadius
);
46 this.level0EdgeLength
= equatorialRadius
/ EDGE_FACTOR
;
50 private static class IcosaTile
implements SectorGeometry
52 private static java
.util
.HashMap
<Integer
, double[]> parameterizations
=
53 new java
.util
.HashMap
<Integer
, double[]>();
54 private static java
.util
.HashMap
<Integer
, java
.nio
.IntBuffer
> indexLists
=
55 new java
.util
.HashMap
<Integer
, java
.nio
.IntBuffer
>();
57 protected static double[] getParameterization(int density
)
59 double[] p
= parameterizations
.get(density
);
63 int coordCount
= (density
* density
+ 3 * density
+ 2) / 2;
64 p
= new double[2 * coordCount
];
65 double delta
= 1d
/ density
;
67 for (int j
= 0; j
<= density
; j
++)
70 for (int i
= 0; i
<= density
- j
; i
++)
72 p
[k
++] = i
* delta
; // u
77 parameterizations
.put(density
, p
);
82 protected static java
.nio
.IntBuffer
getIndices(int density
)
84 java
.nio
.IntBuffer buffer
= indexLists
.get(density
);
88 int indexCount
= density
* density
+ 4 * density
- 2;
89 buffer
= com
.sun
.opengl
.util
.BufferUtil
.newIntBuffer(indexCount
);
91 for (int i
= 0; i
< density
; i
++)
96 k
= buffer
.get(buffer
.position() - 3);
101 if (i
% 2 == 0) // even
103 for (int j
= 0; j
< density
- i
; j
++)
113 for (int j
= density
- i
- 1; j
>= 0; j
--)
123 indexLists
.put(density
, buffer
);
128 public static Vec4
getUnitPoint(double u
, double v
, Vec4 p0
, Vec4 p1
, Vec4 p2
)
130 double w
= 1d
- u
- v
;
131 double x
= u
* p1
.x
+ v
* p2
.x
+ w
* p0
.x
;
132 double y
= u
* p1
.y
+ v
* p2
.y
+ w
* p0
.y
;
133 double z
= u
* p1
.z
+ v
* p2
.z
+ w
* p0
.z
;
134 double invLength
= 1d
/ Math
.sqrt(x
* x
+ y
* y
+ z
* z
);
136 return new Vec4(x
* invLength
, y
* invLength
, z
* invLength
);
139 public static Vec4
getMidPoint(Vec4 p0
, Vec4 p1
)
144 (p0
.z
+ p1
.z
) / 2.0);
147 protected final int level
;
148 private final GlobeInfo globeInfo
;
149 private final LatLon g0
, g1
, g2
;
150 private Sector sector
; // lazily evaluated
151 protected final Vec4 unitp0
, unitp1
, unitp2
; // points on unit sphere
152 private final Vec4 p0
;
153 private final Vec4 p1
;
154 private final Vec4 p2
;
155 private final Vec4 pCentroid
;
156 // private final Vector normal; // ellipsoids's normal vector at tile centroid
157 private final Cylinder extent
; // extent of triangle in object coordinates
158 private final double edgeLength
;
159 private int density
= DEFAULT_DENSITY
;
160 private long byteSize
;
162 static final double ROOT3_OVER4
= Math
.sqrt(3) / 4d
;
164 public IcosaTile(GlobeInfo globeInfo
, int level
, Vec4 unitp0
, Vec4 unitp1
, Vec4 unitp2
)
166 // TODO: Validate args
168 this.globeInfo
= globeInfo
;
170 this.unitp0
= unitp0
;
171 this.unitp1
= unitp1
;
172 this.unitp2
= unitp2
;
174 // Compute lat/lon at tile vertices.
175 Angle lat
= Angle
.fromRadians(Math
.asin(this.unitp0
.y
));
176 Angle lon
= Angle
.fromRadians(Math
.atan2(this.unitp0
.x
, this.unitp0
.z
));
177 this.g0
= new LatLon(lat
, lon
);
178 lat
= Angle
.fromRadians(Math
.asin(this.unitp1
.y
));
179 lon
= Angle
.fromRadians(Math
.atan2(this.unitp1
.x
, this.unitp1
.z
));
180 this.g1
= new LatLon(lat
, lon
);
181 lat
= Angle
.fromRadians(Math
.asin(this.unitp2
.y
));
182 lon
= Angle
.fromRadians(Math
.atan2(this.unitp2
.x
, this.unitp2
.z
));
183 this.g2
= new LatLon(lat
, lon
);
185 // Compute the triangle corner points on the ellipsoid at mean, max and min elevations.
186 this.p0
= this.scaleUnitPointToEllipse(this.unitp0
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
187 this.p1
= this.scaleUnitPointToEllipse(this.unitp1
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
188 this.p2
= this.scaleUnitPointToEllipse(this.unitp2
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
191 Vec4 unitCentroid
= getUnitPoint(a
, a
, this.unitp0
, this.unitp1
, this.unitp2
);
192 this.pCentroid
= this.scaleUnitPointToEllipse(unitCentroid
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
194 // // Compute the tile normal, which is the gradient of the ellipse at the centroid.
195 // double nx = 2 * this.pCentroid.x() * this.globeInfo.invAsq;
196 // double ny = 2 * this.pCentroid.y() * this.globeInfo.invCsq;
197 // double nz = 2 * this.pCentroid.z() * this.globeInfo.invAsq;
198 // this.normal = new Vector(nx, ny, nz).normalize();
200 this.extent
= Sector
.computeBoundingCylinder(globeInfo
.globe
, 1d
, this.getSector());
202 this.edgeLength
= this.globeInfo
.level0EdgeLength
/ Math
.pow(2, this.level
);
205 public IcosaTile(GlobeInfo globeInfo
, int level
, LatLon g0
, LatLon g1
, LatLon g2
)
207 // TODO: Validate args
209 this.globeInfo
= globeInfo
;
215 this.unitp0
= PolarPoint
.toCartesian(this.g0
.getLatitude(), this.g0
.getLongitude(), 1);
216 this.unitp1
= PolarPoint
.toCartesian(this.g1
.getLatitude(), this.g1
.getLongitude(), 1);
217 this.unitp2
= PolarPoint
.toCartesian(this.g2
.getLatitude(), this.g2
.getLongitude(), 1);
219 // Compute the triangle corner points on the ellipsoid at mean, max and min elevations.
220 this.p0
= this.scaleUnitPointToEllipse(this.unitp0
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
221 this.p1
= this.scaleUnitPointToEllipse(this.unitp1
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
222 this.p2
= this.scaleUnitPointToEllipse(this.unitp2
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
225 Vec4 unitCentroid
= getUnitPoint(a
, a
, this.unitp0
, this.unitp1
, this.unitp2
);
226 this.pCentroid
= this.scaleUnitPointToEllipse(unitCentroid
, this.globeInfo
.invAsq
, this.globeInfo
.invCsq
);
228 // // Compute the tile normal, which is the gradient of the ellipse at the centroid.
229 // double nx = 2 * this.pCentroid.x() * this.globeInfo.invAsq;
230 // double ny = 2 * this.pCentroid.y() * this.globeInfo.invCsq;
231 // double nz = 2 * this.pCentroid.z() * this.globeInfo.invAsq;
232 // this.normal = new Vector(nx, ny, nz).normalize();
234 this.extent
= Sector
.computeBoundingCylinder(globeInfo
.globe
, 1d
, this.getSector());
236 this.edgeLength
= this.globeInfo
.level0EdgeLength
/ Math
.pow(2, this.level
);
239 public Sector
getSector()
241 if (this.sector
!= null)
246 m
= this.g0
.getLatitude().getRadians();
247 if (this.g1
.getLatitude().getRadians() < m
)
248 m
= this.g1
.getLatitude().getRadians();
249 if (this.g2
.getLatitude().getRadians() < m
)
250 m
= this.g2
.getLatitude().getRadians();
251 Angle minLat
= Angle
.fromRadians(m
);
253 m
= this.g0
.getLatitude().getRadians();
254 if (this.g1
.getLatitude().getRadians() > m
)
255 m
= this.g1
.getLatitude().getRadians();
256 if (this.g2
.getLatitude().getRadians() > m
)
257 m
= this.g2
.getLatitude().getRadians();
258 Angle maxLat
= Angle
.fromRadians(m
);
260 m
= this.g0
.getLongitude().getRadians();
261 if (this.g1
.getLongitude().getRadians() < m
)
262 m
= this.g1
.getLongitude().getRadians();
263 if (this.g2
.getLongitude().getRadians() < m
)
264 m
= this.g2
.getLongitude().getRadians();
265 Angle minLon
= Angle
.fromRadians(m
);
267 m
= this.g0
.getLongitude().getRadians();
268 if (this.g1
.getLongitude().getRadians() > m
)
269 m
= this.g1
.getLongitude().getRadians();
270 if (this.g2
.getLongitude().getRadians() > m
)
271 m
= this.g2
.getLongitude().getRadians();
272 Angle maxLon
= Angle
.fromRadians(m
);
274 return this.sector
= new Sector(minLat
, maxLat
, minLon
, maxLon
);
277 private Vec4
scaleUnitPointToEllipse(Vec4 up
, double invAsq
, double invCsq
)
279 double f
= up
.x
* up
.x
* invAsq
+ up
.y
* up
.y
* invCsq
+ up
.z
* up
.z
* invAsq
;
280 f
= 1 / Math
.sqrt(f
);
281 return new Vec4(up
.x
* f
, up
.y
* f
, up
.z
* f
);
284 private IcosaTile
[] split()
286 Vec4 up01
= getMidPoint(this.p0
, this.p1
);
287 Vec4 up12
= getMidPoint(this.p1
, this.p2
);
288 Vec4 up20
= getMidPoint(this.p2
, this.p0
);
289 up01
= up01
.multiply3(1d
/ up01
.getLength3());
290 up12
= up12
.multiply3(1d
/ up12
.getLength3());
291 up20
= up20
.multiply3(1d
/ up20
.getLength3());
293 IcosaTile
[] subTiles
= new IcosaTile
[4];
294 subTiles
[0] = new IcosaTile(this.globeInfo
, this.level
+ 1, this.unitp0
, up01
, up20
);
295 subTiles
[1] = new IcosaTile(this.globeInfo
, this.level
+ 1, up01
, this.unitp1
, up12
);
296 subTiles
[2] = new IcosaTile(this.globeInfo
, this.level
+ 1, up20
, up12
, this.unitp2
);
297 subTiles
[3] = new IcosaTile(this.globeInfo
, this.level
+ 1, up12
, up20
, up01
);
302 public String
toString()
304 return this.level
+ ": (" + unitp0
.toString() + ", " + unitp1
.toString() + ", " + unitp2
.toString() + ")";
307 public Extent
getExtent()
312 // private Point getPoint(double u, double v)
314 // Point pu = this.unitp1;
315 // Point pv = this.unitp2;
316 // Point pw = this.unitp0;
318 // double w = 1d - u - v;
320 // double x = u * pu.x() + v * pv.x() + w * pw.x();
321 // double y = u * pu.y() + v * pv.y() + w * pw.y();
322 // double z = u * pu.z() + v * pv.z() + w * pw.z();
323 // double f = x * x * this.globeInfo.invAsq + y * y * this.globeInfo.invAsq + z * z * this.globeInfo.invCsq;
324 // f = 1 / Math.sqrt(f);
326 // return new Point(x * f, y * f, z * f);
329 // private Point computePoint(Angle lat, Angle lon)
331 // double u = (lat.getRadians() - this.g0.getLatitude().getRadians())
332 // / (this.g1.getLatitude().getRadians() - this.g0.getLatitude().getRadians());
333 // double v = (lat.getRadians() - this.g0.getLatitude().getRadians())
334 // / (this.g1.getLatitude().getRadians() - this.g0.getLatitude().getRadians());
339 private static class RenderInfo
341 private final int density
;
342 // private int[] bufferIds = new int[2];
343 private DoubleBuffer vertices
;
344 private final DoubleBuffer texCoords
;
346 private RenderInfo(int density
, DoubleBuffer vertices
, DoubleBuffer texCoords
)
348 this.density
= density
;
349 this.vertices
= vertices
;
350 this.texCoords
= texCoords
;
353 private long getSizeInBytes()
355 return 12;// + this.vertices.limit() * 5 * Float.SIZE;
359 private RenderInfo
makeVerts(DrawContext dc
, int density
)
361 ElevationModel elevationModel
= dc
.getGlobe().getElevationModel();
362 int resolution
= elevationModel
.getTargetResolution(dc
, this.getSector(), density
);
363 CacheKey key
= new CacheKey(this, resolution
, dc
.getVerticalExaggeration(), density
);
364 RenderInfo ri
= (RenderInfo
) WorldWind
.memoryCache().getObject(key
);
368 ri
= this.buildVerts(dc
, resolution
);
369 WorldWind
.memoryCache().add(key
, ri
, this.byteSize
= ri
.getSizeInBytes());
374 private RenderInfo
buildVerts(DrawContext dc
, int resolution
)
376 // Density is intended to approximate closely the tessellation's number of intervals along a side.
377 double[] params
= getParameterization(density
); // Parameterization is independent of tile location.
378 int numVertexCoords
= params
.length
+ params
.length
/ 2;
379 int numPositionCoords
= params
.length
;
380 DoubleBuffer verts
= BufferUtil
.newDoubleBuffer(numVertexCoords
);
381 DoubleBuffer positions
= BufferUtil
.newDoubleBuffer(numPositionCoords
);
383 // Determine the elevation model's target resolution.
384 ElevationModel
.Elevations elevations
= dc
.getGlobe().getElevationModel().getElevations(this.getSector(),
387 Vec4 pu
= this.unitp1
; // unit vectors at triangle vertices at sphere surface
388 Vec4 pv
= this.unitp2
;
389 Vec4 pw
= this.unitp0
;
392 while (verts
.hasRemaining())
394 double u
= params
[i
++];
395 double v
= params
[i
++];
396 double w
= 1d
- u
- v
;
398 // Compute point on triangle.
399 double x
= u
* pu
.x
+ v
* pv
.x
+ w
* pw
.x
;
400 double y
= u
* pu
.y
+ v
* pv
.y
+ w
* pw
.y
;
401 double z
= u
* pu
.z
+ v
* pv
.z
+ w
* pw
.z
;
403 // Compute latitude and longitude of the vector through point on triangle.
404 // Do this before applying ellipsoid eccentricity or elevation.
405 double lat
= Math
.atan2(y
, Math
.sqrt(x
* x
+ z
* z
));
406 double lon
= Math
.atan2(x
, z
);
408 // Scale point to lie on the globe's mean ellilpsoid surface.
409 double f
= 1d
/ Math
.sqrt(
410 x
* x
* this.globeInfo
.invAsq
+ y
* y
* this.globeInfo
.invCsq
+ z
* z
* this.globeInfo
.invAsq
);
415 // Scale the point so that it lies at the given elevation.
416 double elevation
= elevations
.getElevation(lat
, lon
);
417 double nx
= 2 * x
* this.globeInfo
.invAsq
;
418 double ny
= 2 * y
* this.globeInfo
.invCsq
;
419 double nz
= 2 * z
* this.globeInfo
.invAsq
;
420 double scale
= elevation
* dc
.getVerticalExaggeration() / Math
.sqrt(nx
* nx
+ ny
* ny
+ nz
* nz
);
424 lat
= Math
.atan2(y
, Math
.sqrt(x
* x
+ z
* z
));
425 lon
= Math
.atan2(x
, z
);
426 x
+= (nx
- this.pCentroid
.x
);
427 y
+= (ny
- this.pCentroid
.y
);
428 z
+= (nz
- this.pCentroid
.z
);
430 // Store point and position
431 verts
.put(x
).put(y
).put(z
);
432 positions
.put(lon
).put(lat
);
433 // TODO: store normal as well
438 return new RenderInfo(density
, verts
, positions
);
441 public void renderMultiTexture(DrawContext dc
, int numTextureUnits
)
443 // TODO: Validate args
444 this.render(dc
, this.density
, numTextureUnits
);
447 public void render(DrawContext dc
)
449 // TODO: Validate args
450 this.render(dc
, this.density
, 1);
453 private long render(DrawContext dc
, int density
, int numTextureUnits
)
455 RenderInfo ri
= this.makeVerts(dc
, density
);
456 java
.nio
.IntBuffer indices
= getIndices(ri
.density
);
459 dc
.getView().pushReferenceCenter(dc
, this.pCentroid
);
462 gl
.glPushClientAttrib(GL
.GL_CLIENT_VERTEX_ARRAY_BIT
);
463 gl
.glEnableClientState(GL
.GL_VERTEX_ARRAY
);
464 gl
.glVertexPointer(3, GL
.GL_DOUBLE
, 0, ri
.vertices
.rewind());
466 for (int i
= 0; i
< numTextureUnits
; i
++)
468 gl
.glClientActiveTexture(GL
.GL_TEXTURE0
+ i
);
469 gl
.glEnableClientState(GL
.GL_TEXTURE_COORD_ARRAY
);
470 gl
.glTexCoordPointer(2, GL
.GL_DOUBLE
, 0, ri
.texCoords
.rewind());
473 gl
.glDrawElements(javax
.media
.opengl
.GL
.GL_TRIANGLE_STRIP
, indices
.limit(),
474 javax
.media
.opengl
.GL
.GL_UNSIGNED_INT
, indices
.rewind());
476 gl
.glPopClientAttrib();
478 dc
.getView().popReferenceCenter(dc
);
480 return indices
.limit() - 2; // return number of triangles rendered
483 public void renderWireframe(DrawContext dc
, boolean showTriangles
, boolean showTileBoundary
)
485 RenderInfo ri
= this.makeVerts(dc
, this.density
);
486 java
.nio
.IntBuffer indices
= getIndices(ri
.density
);
489 dc
.getView().pushReferenceCenter(dc
, this.pCentroid
);
491 javax
.media
.opengl
.GL gl
= dc
.getGL();
492 // TODO: Could be overdoing the attrib push here. Check that all needed and perhaps save/retore instead.
494 GL
.GL_DEPTH_BUFFER_BIT
| GL
.GL_POLYGON_BIT
| GL
.GL_TEXTURE_BIT
| GL
.GL_ENABLE_BIT
| GL
.GL_CURRENT_BIT
);
495 gl
.glEnable(GL
.GL_BLEND
);
496 gl
.glBlendFunc(GL
.GL_SRC_ALPHA
, GL
.GL_ONE
);
497 gl
.glDisable(javax
.media
.opengl
.GL
.GL_DEPTH_TEST
);
498 gl
.glEnable(javax
.media
.opengl
.GL
.GL_CULL_FACE
);
499 gl
.glCullFace(javax
.media
.opengl
.GL
.GL_BACK
);
500 gl
.glDisable(javax
.media
.opengl
.GL
.GL_TEXTURE_2D
);
501 gl
.glColor4d(1d
, 1d
, 1d
, 0.2);
502 gl
.glPolygonMode(javax
.media
.opengl
.GL
.GL_FRONT
, javax
.media
.opengl
.GL
.GL_LINE
);
506 gl
.glPushClientAttrib(GL
.GL_CLIENT_VERTEX_ARRAY_BIT
);
507 gl
.glEnableClientState(GL
.GL_VERTEX_ARRAY
);
509 gl
.glVertexPointer(3, GL
.GL_DOUBLE
, 0, ri
.vertices
);
510 gl
.glDrawElements(javax
.media
.opengl
.GL
.GL_TRIANGLE_STRIP
, indices
.limit(),
511 javax
.media
.opengl
.GL
.GL_UNSIGNED_INT
, indices
);
513 gl
.glPopClientAttrib();
516 dc
.getView().popReferenceCenter(dc
);
518 if (showTileBoundary
)
519 this.renderPatchBoundary(gl
);
524 private void renderPatchBoundary(javax
.media
.opengl
.GL gl
)
526 gl
.glColor4d(1d
, 0, 0, 1d
);
527 gl
.glBegin(javax
.media
.opengl
.GL
.GL_TRIANGLES
);
528 gl
.glVertex3d(this.p0
.x
, this.p0
.y
, this.p0
.z
);
529 gl
.glVertex3d(this.p1
.x
, this.p1
.y
, this.p1
.z
);
530 gl
.glVertex3d(this.p2
.x
, this.p2
.y
, this.p2
.z
);
534 public void renderBoundingVolume(DrawContext dc
)
538 public void renderBoundary(DrawContext dc
)
540 this.renderWireframe(dc
, false, true);
543 public Vec4
getSurfacePoint(Angle latitude
, Angle longitude
, double metersOffset
)
545 // TODO: Replace below with interpolation over containing triangle.
546 return this.globeInfo
.globe
.computePointFromPosition(latitude
, longitude
, metersOffset
);
549 public void pick(DrawContext dc
, java
.awt
.Point pickPoint
)
553 public long getSizeInBytes()
555 return this.byteSize
;
558 public int compareTo(SectorGeometry that
)
562 String msg
= WorldWind
.retrieveErrMsg("nullValue.GeometryIsNull");
563 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, msg
);
564 throw new IllegalArgumentException(msg
);
566 return this.getSector().compareTo(that
.getSector());
569 public boolean equals(Object o
)
573 if (o
== null || getClass() != o
.getClass())
576 IcosaTile icosaTile
= (IcosaTile
) o
;
578 if (density
!= icosaTile
.density
)
580 if (level
!= icosaTile
.level
)
582 if (g0
!= null ?
!g0
.equals(icosaTile
.g0
) : icosaTile
.g0
!= null)
584 if (g1
!= null ?
!g1
.equals(icosaTile
.g1
) : icosaTile
.g1
!= null)
586 if (g2
!= null ?
!g2
.equals(icosaTile
.g2
) : icosaTile
.g2
!= null)
588 if (globeInfo
!= null ?
!globeInfo
.equals(icosaTile
.globeInfo
) : icosaTile
.globeInfo
!= null)
590 if (p0
!= null ?
!p0
.equals(icosaTile
.p0
) : icosaTile
.p0
!= null)
592 if (p1
!= null ?
!p1
.equals(icosaTile
.p1
) : icosaTile
.p1
!= null)
594 if (p2
!= null ?
!p2
.equals(icosaTile
.p2
) : icosaTile
.p2
!= null)
596 if (this.getSector() != null ?
!this.getSector().equals(icosaTile
.getSector()) :
597 icosaTile
.getSector() != null)
599 if (unitp0
!= null ?
!unitp0
.equals(icosaTile
.unitp0
) : icosaTile
.unitp0
!= null)
601 if (unitp1
!= null ?
!unitp1
.equals(icosaTile
.unitp1
) : icosaTile
.unitp1
!= null)
603 //noinspection RedundantIfStatement
604 if (unitp2
!= null ?
!unitp2
.equals(icosaTile
.unitp2
) : icosaTile
.unitp2
!= null)
610 public int hashCode()
614 result
= 31 * result
+ (globeInfo
!= null ? globeInfo
.hashCode() : 0);
615 result
= 31 * result
+ (g0
!= null ? g0
.hashCode() : 0);
616 result
= 31 * result
+ (g1
!= null ? g1
.hashCode() : 0);
617 result
= 31 * result
+ (g2
!= null ? g2
.hashCode() : 0);
618 result
= 31 * result
+ (this.getSector().hashCode());
619 result
= 31 * result
+ (unitp0
!= null ? unitp0
.hashCode() : 0);
620 result
= 31 * result
+ (unitp1
!= null ? unitp1
.hashCode() : 0);
621 result
= 31 * result
+ (unitp2
!= null ? unitp2
.hashCode() : 0);
622 result
= 31 * result
+ (p0
!= null ? p0
.hashCode() : 0);
623 result
= 31 * result
+ (p1
!= null ? p1
.hashCode() : 0);
624 result
= 31 * result
+ (p2
!= null ? p2
.hashCode() : 0);
625 result
= 31 * result
+ density
;
630 // Angles used to form icosahedral triangles.
631 private static Angle P30
= Angle
.fromDegrees(30);
632 private static Angle N30
= Angle
.fromDegrees(-30);
633 private static Angle P36
= Angle
.fromDegrees(36);
634 private static Angle N36
= Angle
.fromDegrees(-36);
635 private static Angle P72
= Angle
.fromDegrees(72);
636 private static Angle N72
= Angle
.fromDegrees(-72);
637 private static Angle P108
= Angle
.fromDegrees(108);
638 private static Angle N108
= Angle
.fromDegrees(-108);
639 private static Angle P144
= Angle
.fromDegrees(144);
640 private static Angle N144
= Angle
.fromDegrees(-144);
642 // Lat/lon of vertices of icosahedron aligned with lat/lon domain boundaries.
643 private static final LatLon
[] L0
= {
644 new LatLon(Angle
.POS90
, N144
), // 0
645 new LatLon(Angle
.POS90
, N72
), // 1
646 new LatLon(Angle
.POS90
, Angle
.ZERO
), // 2
647 new LatLon(Angle
.POS90
, P72
), // 3
648 new LatLon(Angle
.POS90
, P144
), // 4
649 new LatLon(P30
, Angle
.NEG180
), // 5
650 new LatLon(P30
, N144
), // 6
651 new LatLon(P30
, N108
), // 7
652 new LatLon(P30
, N72
), // 8
653 new LatLon(P30
, N36
), // 9
654 new LatLon(P30
, Angle
.ZERO
), // 10
655 new LatLon(P30
, P36
), // 11
656 new LatLon(P30
, P72
), // 12
657 new LatLon(P30
, P108
), // 13
658 new LatLon(P30
, P144
), // 14
659 new LatLon(P30
, Angle
.POS180
), // 15
660 new LatLon(N30
, N144
), // 16
661 new LatLon(N30
, N108
), // 17
662 new LatLon(N30
, N72
), // 18
663 new LatLon(N30
, N36
), // 19
664 new LatLon(N30
, Angle
.ZERO
), // 20
665 new LatLon(N30
, P36
), // 21
666 new LatLon(N30
, P72
), // 22
667 new LatLon(N30
, P108
), // 23
668 new LatLon(N30
, P144
), // 24
669 new LatLon(N30
, Angle
.POS180
), // 25
670 new LatLon(N30
, N144
), // 26
671 new LatLon(Angle
.NEG90
, N108
), // 27
672 new LatLon(Angle
.NEG90
, N36
), // 28
673 new LatLon(Angle
.NEG90
, P36
), // 29
674 new LatLon(Angle
.NEG90
, P108
), // 30
675 new LatLon(Angle
.NEG90
, Angle
.POS180
), // 31
676 new LatLon(P30
, Angle
.NEG180
), // 32
677 new LatLon(N30
, Angle
.NEG180
), // 33
678 new LatLon(Angle
.NEG90
, Angle
.NEG180
),}; // 34
680 public static IcosaTile
createTileFromAngles(GlobeInfo globeInfo
, int level
, LatLon g0
, LatLon g1
, LatLon g2
)
682 return new IcosaTile(globeInfo
, level
, g0
, g1
, g2
);
685 private static java
.util
.ArrayList
<IcosaTile
> makeLevelZeroEquilateralTriangles(GlobeInfo globeInfo
)
687 java
.util
.ArrayList
<IcosaTile
> topLevels
= new java
.util
.ArrayList
<IcosaTile
>(22);
689 // These lines form the level 0 icosahedral triangles. Two of the icosahedral triangles,
690 // however, are split to form right triangles whose sides align with the longitude domain
691 // limits (-180/180) so that no triangle spans the discontinuity between +180 and -180.
692 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[5], L0
[7], L0
[0]));
693 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[7], L0
[9], L0
[1]));
694 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[9], L0
[11], L0
[2]));
695 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[11], L0
[13], L0
[3]));
696 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[13], L0
[15], L0
[4]));
697 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[16], L0
[7], L0
[5]));
698 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[16], L0
[18], L0
[7]));
699 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[18], L0
[9], L0
[7]));
700 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[18], L0
[20], L0
[9]));
701 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[20], L0
[11], L0
[9])); // triangle centered on 0 lat, 0 lon
702 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[20], L0
[22], L0
[11]));
703 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[22], L0
[13], L0
[11]));
704 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[22], L0
[24], L0
[13]));
705 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[24], L0
[15], L0
[13]));
706 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[24], L0
[25], L0
[15])); // right triangle
707 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[33], L0
[26], L0
[32])); // right triangle
708 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[27], L0
[18], L0
[16]));
709 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[28], L0
[20], L0
[18]));
710 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[29], L0
[22], L0
[20]));
711 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[30], L0
[24], L0
[22]));
712 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[25], L0
[24], L0
[31])); // right triangle
713 topLevels
.add(createTileFromAngles(globeInfo
, 0, L0
[26], L0
[33], L0
[34])); // right triangle
718 @SuppressWarnings({"FieldCanBeLocal"})
719 private final Globe globe
;
720 @SuppressWarnings({"FieldCanBeLocal"})
721 private final GlobeInfo globeInfo
;
722 private final java
.util
.ArrayList
<IcosaTile
> topLevels
;
723 private SectorGeometryList currentTiles
= new SectorGeometryList();
724 private Frustum currentFrustum
;
725 private int currentLevel
;
726 private int maxLevel
= DEFAULT_MAX_LEVEL
;//14; // TODO: Make configurable
727 private Sector sector
; // union of all tiles selected during call to render()
728 private int density
= DEFAULT_DENSITY
; // TODO: make configurable
730 public EllipsoidIcosahedralTessellator(Globe globe
)
733 this.globeInfo
= new GlobeInfo(this.globe
);
734 this.topLevels
= makeLevelZeroEquilateralTriangles(this.globeInfo
);
737 public Sector
getSector()
742 public SectorGeometryList
tessellate(DrawContext dc
)
744 View view
= dc
.getView();
746 this.currentTiles
.clear();
747 this.currentLevel
= 0;
750 this.currentFrustum
= view
.getFrustumInModelCoordinates();
752 for (IcosaTile tile
: topLevels
)
754 this.selectVisibleTiles(tile
, view
);
757 dc
.setVisibleSector(this.getSector());
759 return this.currentTiles
;
762 private boolean needToSplit(IcosaTile tile
, View view
)
764 double d1
= view
.getEyePoint().distanceTo3(tile
.p0
);
765 double d2
= view
.getEyePoint().distanceTo3(tile
.p1
);
766 double d3
= view
.getEyePoint().distanceTo3(tile
.p2
);
767 double d4
= view
.getEyePoint().distanceTo3(tile
.pCentroid
);
769 double minDistance
= d1
;
770 if (d2
< minDistance
)
772 if (d3
< minDistance
)
774 if (d4
< minDistance
)
777 // Meets criteria when the texel size is less than the size of some number of pixels.
778 double pixelSize
= view
.computePixelSizeAtDistance(minDistance
);
779 return tile
.edgeLength
/ this.density
>= 30d
* (2d
* pixelSize
); // 2x pixel size to compensate for view bug
782 private void selectVisibleTiles(IcosaTile tile
, View view
)
784 if (!tile
.getExtent().intersects(this.currentFrustum
))
787 if (this.currentLevel
< this.maxLevel
&& this.needToSplit(tile
, view
))
790 IcosaTile
[] subtiles
= tile
.split();
791 for (IcosaTile child
: subtiles
)
793 this.selectVisibleTiles(child
, view
);
798 this.sector
= tile
.getSector().union(this.sector
);
799 this.currentTiles
.add(tile
);
802 private static class CacheKey
804 private final Vec4 centroid
;
805 private int resolution
;
806 private final double verticalExaggeration
;
809 private CacheKey(IcosaTile tile
, int resolution
, double verticalExaggeration
, int density
)
811 // private class, no validation required.
812 this.centroid
= tile
.pCentroid
;
813 this.resolution
= resolution
;
814 this.verticalExaggeration
= verticalExaggeration
;
815 this.density
= density
;
819 public String
toString()
821 return "density " + this.density
+ " ve " + this.verticalExaggeration
+ " resolution " + this.resolution
;
822 // + " g0 " + this.g0 + " g1 " + this.g1 + " g2 " + this.g2;
825 public boolean equals(Object o
)
829 if (o
== null || getClass() != o
.getClass())
832 CacheKey cacheKey
= (CacheKey
) o
;
834 if (density
!= cacheKey
.density
)
836 if (resolution
!= cacheKey
.resolution
)
838 if (Double
.compare(cacheKey
.verticalExaggeration
, verticalExaggeration
) != 0)
840 //noinspection RedundantIfStatement
841 if (centroid
!= null ?
!centroid
.equals(cacheKey
.centroid
) : cacheKey
.centroid
!= null)
847 public int hashCode()
851 result
= (centroid
!= null ? centroid
.hashCode() : 0);
852 result
= 31 * result
+ resolution
;
853 temp
= verticalExaggeration
!= +0.0d ? Double
.doubleToLongBits(verticalExaggeration
) : 0L;
854 result
= 31 * result
+ (int) (temp ^
(temp
>>> 32));
855 result
= 31 * result
+ density
;
861 // private static java.util.ArrayList<IcosaTile> makeRightTrianglesLevel0(GlobeInfo globeInfo)
863 // java.util.ArrayList<IcosaTile> topLevels = new java.util.ArrayList<IcosaTile>(40);
865 // // Creates all right triangles by splitting each of the 20 icosahedral triangles.
866 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[5], L0[6], L0[0]));
867 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[6], L0[0]));
868 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[8], L0[1]));
869 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[8], L0[1]));
870 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[10], L0[2]));
871 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[10], L0[2]));
872 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[12], L0[3]));
873 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[12], L0[3]));
874 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[14], L0[4]));
875 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[15], L0[14], L0[4]));
876 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[5], L0[6], L0[16]));
877 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[6], L0[16]));
878 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[17], L0[7]));
879 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[17], L0[7]));
880 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[8], L0[18]));
881 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[8], L0[18]));
882 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[19], L0[9]));
883 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[19], L0[9]));
884 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[10], L0[20]));
885 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[10], L0[20]));
886 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[21], L0[11]));
887 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[21], L0[11]));
888 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[12], L0[22]));
889 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[12], L0[22]));
890 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[23], L0[13]));
891 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[23], L0[13]));
892 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[14], L0[24]));
893 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[15], L0[14], L0[24]));
894 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[25], L0[15]));
895 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[26], L0[25], L0[15]));
896 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[17], L0[27]));
897 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[17], L0[27]));
898 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[19], L0[28]));
899 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[19], L0[28]));
900 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[21], L0[29]));
901 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[21], L0[29]));
902 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[23], L0[30]));
903 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[23], L0[30]));
904 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[25], L0[31]));
905 // topLevels.add(createTileFromAngles(globeInfo, 0, L0[26], L0[25], L0[31]));