Worldwind public release 0.2.1
[worldwind-tracker.git] / gov / nasa / worldwind / globes / EllipsoidIcosahedralTessellator.java
blob2a091b5a098e53f6abfb0d8c0fe6dd109a427f67
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.globes;
9 import com.sun.opengl.util.*;
10 import gov.nasa.worldwind.*;
11 import gov.nasa.worldwind.geom.*;
13 import javax.media.opengl.*;
14 import java.nio.*;
16 /**
17 * @author Tom Gaskins
18 * @version $Id$
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)
39 this.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);
60 if (p != null)
61 return p;
63 int coordCount = (density * density + 3 * density + 2) / 2;
64 p = new double[2 * coordCount];
65 double delta = 1d / density;
66 int k = 0;
67 for (int j = 0; j <= density; j++)
69 double v = j * delta;
70 for (int i = 0; i <= density - j; i++)
72 p[k++] = i * delta; // u
73 p[k++] = v;
77 parameterizations.put(density, p);
79 return p;
82 protected static java.nio.IntBuffer getIndices(int density)
84 java.nio.IntBuffer buffer = indexLists.get(density);
85 if (buffer != null)
86 return buffer;
88 int indexCount = density * density + 4 * density - 2;
89 buffer = com.sun.opengl.util.BufferUtil.newIntBuffer(indexCount);
90 int k = 0;
91 for (int i = 0; i < density; i++)
93 buffer.put(k);
94 if (i > 0)
96 k = buffer.get(buffer.position() - 3);
97 buffer.put(k);
98 buffer.put(k);
101 if (i % 2 == 0) // even
103 for (int j = 0; j < density - i; j++)
105 ++k;
106 buffer.put(k);
107 k += density - j;
108 buffer.put(k);
111 else // odd
113 for (int j = density - i - 1; j >= 0; j--)
115 k -= density - j;
116 buffer.put(k);
117 --k;
118 buffer.put(k);
123 indexLists.put(density, buffer);
125 return 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)
141 return new Vec4(
142 (p0.x + p1.x) / 2.0,
143 (p0.y + p1.y) / 2.0,
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
167 this.level = level;
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);
190 double a = 1d / 3d;
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
208 this.level = level;
209 this.globeInfo = globeInfo;
211 this.g0 = g0;
212 this.g1 = g1;
213 this.g2 = g2;
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);
224 double a = 1d / 3d;
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)
242 return this.sector;
244 double m;
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);
299 return subTiles;
302 public String toString()
304 return this.level + ": (" + unitp0.toString() + ", " + unitp1.toString() + ", " + unitp2.toString() + ")";
307 public Extent getExtent()
309 return this.extent;
312 // private Point getPoint(double u, double v)
313 // {
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);
327 // }
329 // private Point computePoint(Angle lat, Angle lon)
330 // {
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());
336 // return null;
337 // }
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);
365 if (ri != null)
366 return ri;
368 ri = this.buildVerts(dc, resolution);
369 WorldWind.memoryCache().add(key, ri, this.byteSize = ri.getSizeInBytes());
371 return ri;
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(),
385 resolution);
387 Vec4 pu = this.unitp1; // unit vectors at triangle vertices at sphere surface
388 Vec4 pv = this.unitp2;
389 Vec4 pw = this.unitp0;
391 int i = 0;
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);
411 x *= f;
412 y *= f;
413 z *= f;
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);
421 nx *= scale;
422 ny *= scale;
423 nz *= scale;
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
436 verts.rewind();
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);
457 indices.rewind();
459 dc.getView().pushReferenceCenter(dc, this.pCentroid);
461 GL gl = dc.getGL();
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);
487 indices.rewind();
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.
493 gl.glPushAttrib(
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);
504 if (showTriangles)
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);
521 gl.glPopAttrib();
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);
531 gl.glEnd();
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)
560 if (that == null)
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)
571 if (this == o)
572 return true;
573 if (o == null || getClass() != o.getClass())
574 return false;
576 IcosaTile icosaTile = (IcosaTile) o;
578 if (density != icosaTile.density)
579 return false;
580 if (level != icosaTile.level)
581 return false;
582 if (g0 != null ? !g0.equals(icosaTile.g0) : icosaTile.g0 != null)
583 return false;
584 if (g1 != null ? !g1.equals(icosaTile.g1) : icosaTile.g1 != null)
585 return false;
586 if (g2 != null ? !g2.equals(icosaTile.g2) : icosaTile.g2 != null)
587 return false;
588 if (globeInfo != null ? !globeInfo.equals(icosaTile.globeInfo) : icosaTile.globeInfo != null)
589 return false;
590 if (p0 != null ? !p0.equals(icosaTile.p0) : icosaTile.p0 != null)
591 return false;
592 if (p1 != null ? !p1.equals(icosaTile.p1) : icosaTile.p1 != null)
593 return false;
594 if (p2 != null ? !p2.equals(icosaTile.p2) : icosaTile.p2 != null)
595 return false;
596 if (this.getSector() != null ? !this.getSector().equals(icosaTile.getSector()) :
597 icosaTile.getSector() != null)
598 return false;
599 if (unitp0 != null ? !unitp0.equals(icosaTile.unitp0) : icosaTile.unitp0 != null)
600 return false;
601 if (unitp1 != null ? !unitp1.equals(icosaTile.unitp1) : icosaTile.unitp1 != null)
602 return false;
603 //noinspection RedundantIfStatement
604 if (unitp2 != null ? !unitp2.equals(icosaTile.unitp2) : icosaTile.unitp2 != null)
605 return false;
607 return true;
610 public int hashCode()
612 int result;
613 result = level;
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;
626 return result;
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
715 return topLevels;
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)
732 this.globe = globe;
733 this.globeInfo = new GlobeInfo(this.globe);
734 this.topLevels = makeLevelZeroEquilateralTriangles(this.globeInfo);
737 public Sector getSector()
739 return this.sector;
742 public SectorGeometryList tessellate(DrawContext dc)
744 View view = dc.getView();
746 this.currentTiles.clear();
747 this.currentLevel = 0;
748 this.sector = null;
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)
771 minDistance = d2;
772 if (d3 < minDistance)
773 minDistance = d3;
774 if (d4 < minDistance)
775 minDistance = d4;
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))
785 return;
787 if (this.currentLevel < this.maxLevel && this.needToSplit(tile, view))
789 ++this.currentLevel;
790 IcosaTile[] subtiles = tile.split();
791 for (IcosaTile child : subtiles)
793 this.selectVisibleTiles(child, view);
795 --this.currentLevel;
796 return;
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;
807 private int density;
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;
818 @Override
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)
827 if (this == o)
828 return true;
829 if (o == null || getClass() != o.getClass())
830 return false;
832 CacheKey cacheKey = (CacheKey) o;
834 if (density != cacheKey.density)
835 return false;
836 if (resolution != cacheKey.resolution)
837 return false;
838 if (Double.compare(cacheKey.verticalExaggeration, verticalExaggeration) != 0)
839 return false;
840 //noinspection RedundantIfStatement
841 if (centroid != null ? !centroid.equals(cacheKey.centroid) : cacheKey.centroid != null)
842 return false;
844 return true;
847 public int hashCode()
849 int result;
850 long temp;
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;
856 return result;
861 // private static java.util.ArrayList<IcosaTile> makeRightTrianglesLevel0(GlobeInfo globeInfo)
862 // {
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]));
907 // return topLevels;
908 // }