Source: globe/Tessellator.js

  1. /*
  2. * Copyright 2003-2006, 2009, 2017, 2020 United States Government, as represented
  3. * by the Administrator of the National Aeronautics and Space Administration.
  4. * All rights reserved.
  5. *
  6. * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License,
  7. * Version 2.0 (the "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License
  9. * at http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software distributed
  12. * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  13. * CONDITIONS OF ANY KIND, either express or implied. See the License for the
  14. * specific language governing permissions and limitations under the License.
  15. *
  16. * NASAWorldWind/WebWorldWind also contains the following 3rd party Open Source
  17. * software:
  18. *
  19. * ES6-Promise – under MIT License
  20. * libtess.js – SGI Free Software License B
  21. * Proj4 – under MIT License
  22. * JSZip – under MIT License
  23. *
  24. * A complete listing of 3rd Party software notices and licenses included in
  25. * WebWorldWind can be found in the WebWorldWind 3rd-party notices and licenses
  26. * PDF found in code directory.
  27. */
  28. /**
  29. * @exports Tessellator
  30. */
  31. define(['../geom/Angle',
  32. '../error/ArgumentError',
  33. '../shaders/BasicProgram',
  34. '../globe/Globe',
  35. '../shaders/GpuProgram',
  36. '../util/Level',
  37. '../util/LevelSet',
  38. '../geom/Location',
  39. '../util/Logger',
  40. '../geom/Matrix',
  41. '../cache/MemoryCache',
  42. '../error/NotYetImplementedError',
  43. '../pick/PickedObject',
  44. '../geom/Position',
  45. '../geom/Rectangle',
  46. '../geom/Sector',
  47. '../globe/Terrain',
  48. '../globe/TerrainTile',
  49. '../globe/TerrainTileList',
  50. '../util/Tile',
  51. '../util/WWMath',
  52. '../util/WWUtil'
  53. ],
  54. function (Angle,
  55. ArgumentError,
  56. BasicProgram,
  57. Globe,
  58. GpuProgram,
  59. Level,
  60. LevelSet,
  61. Location,
  62. Logger,
  63. Matrix,
  64. MemoryCache,
  65. NotYetImplementedError,
  66. PickedObject,
  67. Position,
  68. Rectangle,
  69. Sector,
  70. Terrain,
  71. TerrainTile,
  72. TerrainTileList,
  73. Tile,
  74. WWMath,
  75. WWUtil) {
  76. "use strict";
  77. /**
  78. * Constructs a Tessellator.
  79. * @alias Tessellator
  80. * @constructor
  81. * @classdesc Provides terrain tessellation for a globe.
  82. */
  83. var Tessellator = function () {
  84. // Parameterize top level subdivision in one place.
  85. // TilesInTopLevel describes the most coarse tile structure.
  86. this.numRowsTilesInTopLevel = 4; // baseline: 4
  87. this.numColumnsTilesInTopLevel = 8; // baseline: 8
  88. // The maximum number of levels that will ever be tessellated.
  89. this.maximumSubdivisionDepth = 15; // baseline: 15
  90. // tileWidth, tileHeight - the number of subdivisions a single tile has; this determines the sampling grid.
  91. this.tileWidth = 32; // baseline: 32
  92. this.tileHeight = 32; // baseline: 32
  93. /**
  94. * Controls the level of detail switching for this layer. The next highest resolution level is
  95. * used when an elevation tile's cell size is greater than this number of pixels, up to the maximum
  96. * resolution of the elevation model.
  97. * @type {Number}
  98. * @default 1.75
  99. */
  100. this.detailControl = 40;
  101. this.levels = new LevelSet(
  102. Sector.FULL_SPHERE,
  103. new Location(
  104. 180 / this.numRowsTilesInTopLevel,
  105. 360 / this.numColumnsTilesInTopLevel),
  106. this.maximumSubdivisionDepth,
  107. this.tileWidth,
  108. this.tileHeight);
  109. this.topLevelTiles = {};
  110. this.currentTiles = new TerrainTileList(this);
  111. this.tileCache = new MemoryCache(5000000, 4000000); // Holds 316 32x32 tiles.
  112. this.elevationTimestamp = undefined;
  113. this.lastModelViewProjection = Matrix.fromIdentity();
  114. this.vertexPointLocation = -1;
  115. this.vertexTexCoordLocation = -1;
  116. this.texCoords = null;
  117. this.texCoordVboCacheKey = 'global_tex_coords';
  118. this.indices = null;
  119. this.indicesVboCacheKey = 'global_indices';
  120. this.baseIndices = null;
  121. this.baseIndicesOffset = null;
  122. this.numBaseIndices = null;
  123. this.indicesNorth = null;
  124. this.indicesNorthOffset = null;
  125. this.numIndicesNorth = null;
  126. this.indicesSouth = null;
  127. this.indicesSouthOffset = null;
  128. this.numIndicesSouth = null;
  129. this.indicesWest = null;
  130. this.indicesWestOffset = null;
  131. this.numIndicesWest = null;
  132. this.indicesEast = null;
  133. this.indicesEastOffset = null;
  134. this.numIndicesEast = null;
  135. this.indicesLoresNorth = null;
  136. this.indicesLoresNorthOffset = null;
  137. this.numIndicesLoresNorth = null;
  138. this.indicesLoresSouth = null;
  139. this.indicesLoresSouthOffset = null;
  140. this.numIndicesLoresSouth = null;
  141. this.indicesLoresWest = null;
  142. this.indicesLoresWestOffset = null;
  143. this.numIndicesLoresWest = null;
  144. this.indicesLoresEast = null;
  145. this.indicesLoresEastOffset = null;
  146. this.numIndicesLoresEast = null;
  147. this.outlineIndicesOffset = null;
  148. this.numOutlineIndices = null;
  149. this.wireframeIndicesOffset = null;
  150. this.numWireframeIndices = null;
  151. this.scratchMatrix = Matrix.fromIdentity();
  152. this.scratchElevations = null;
  153. this.scratchPrevElevations = null;
  154. this.corners = {};
  155. this.tiles = [];
  156. };
  157. /**
  158. * Creates the visible terrain of the globe associated with the current draw context.
  159. * @param {DrawContext} dc The draw context.
  160. * @returns {Terrain} The computed terrain, or null if terrain could not be computed.
  161. * @throws {ArgumentError} If the dc is null or undefined.
  162. */
  163. Tessellator.prototype.tessellate = function (dc) {
  164. if (!dc) {
  165. throw new ArgumentError(
  166. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "tessellate", "missingDC"));
  167. }
  168. var lastElevationsChange = dc.globe.elevationTimestamp();
  169. if (this.lastGlobeStateKey === dc.globeStateKey
  170. && this.lastVerticalExaggeration === dc.verticalExaggeration
  171. && this.elevationTimestamp === lastElevationsChange
  172. && dc.modelviewProjection.equals(this.lastModelViewProjection)) {
  173. return this.lastTerrain;
  174. }
  175. this.lastModelViewProjection.copy(dc.modelviewProjection);
  176. this.lastGlobeStateKey = dc.globeStateKey;
  177. this.elevationTimestamp = lastElevationsChange;
  178. this.lastVerticalExaggeration = dc.verticalExaggeration;
  179. this.currentTiles.removeAllTiles();
  180. if (!this.topLevelTiles[dc.globeStateKey] || this.topLevelTiles[dc.globeStateKey].length == 0) {
  181. this.createTopLevelTiles(dc);
  182. }
  183. this.corners = {};
  184. this.tiles = [];
  185. for (var index = 0, len = this.topLevelTiles[dc.globeStateKey].length; index < len; index += 1) {
  186. var tile = this.topLevelTiles[dc.globeStateKey][index];
  187. tile.update(dc);
  188. if (this.isTileVisible(dc, tile)) {
  189. this.addTileOrDescendants(dc, tile);
  190. }
  191. }
  192. this.refineNeighbors(dc);
  193. this.finishTessellating(dc);
  194. this.lastTerrain = this.currentTiles.length === 0 ? null
  195. : new Terrain(dc.globe, this, this.currentTiles, dc.verticalExaggeration);
  196. return this.lastTerrain;
  197. };
  198. Tessellator.prototype.createTile = function (tileSector, level, row, column) {
  199. if (!tileSector) {
  200. throw new ArgumentError(
  201. Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor", "missingSector"));
  202. }
  203. if (!level) {
  204. throw new ArgumentError(
  205. Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
  206. "The specified level is null or undefined."));
  207. }
  208. if (row < 0 || column < 0) {
  209. throw new ArgumentError(
  210. Logger.logMessage(Logger.LEVEL_SEVERE, "Tile", "constructor",
  211. "The specified row or column is less than zero."));
  212. }
  213. return new TerrainTile(tileSector, level, row, column);
  214. };
  215. /**
  216. * Initializes rendering state to draw a succession of terrain tiles.
  217. * @param {DrawContext} dc The draw context.
  218. */
  219. Tessellator.prototype.beginRendering = function (dc) {
  220. var program = dc.currentProgram; // use the current program; the caller configures other program state
  221. if (!program) {
  222. Logger.logMessage(Logger.LEVEL_INFO, "Tessellator", "beginRendering", "Current Program is empty");
  223. return;
  224. }
  225. this.buildSharedGeometry();
  226. this.cacheSharedGeometryVBOs(dc);
  227. var gl = dc.currentGlContext,
  228. gpuResourceCache = dc.gpuResourceCache;
  229. // Keep track of the program's attribute locations. The tessellator does not know which program the caller has
  230. // bound, and therefore must look up the location of attributes by name.
  231. this.vertexPointLocation = program.attributeLocation(gl, "vertexPoint");
  232. this.vertexTexCoordLocation = program.attributeLocation(gl, "vertexTexCoord");
  233. gl.enableVertexAttribArray(this.vertexPointLocation);
  234. if (this.vertexTexCoordLocation >= 0) { // location of vertexTexCoord attribute is -1 when the basic program is bound
  235. var texCoordVbo = gpuResourceCache.resourceForKey(this.texCoordVboCacheKey);
  236. gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVbo);
  237. gl.vertexAttribPointer(this.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
  238. gl.enableVertexAttribArray(this.vertexTexCoordLocation);
  239. }
  240. var indicesVbo = gpuResourceCache.resourceForKey(this.indicesVboCacheKey);
  241. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesVbo);
  242. };
  243. /**
  244. * Restores rendering state after drawing a succession of terrain tiles.
  245. * @param {DrawContext} dc The draw context.
  246. */
  247. Tessellator.prototype.endRendering = function (dc) {
  248. var gl = dc.currentGlContext;
  249. gl.bindBuffer(gl.ARRAY_BUFFER, null);
  250. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
  251. // Restore the global OpenGL vertex attribute array state.
  252. if (this.vertexPointLocation >= 0) {
  253. gl.disableVertexAttribArray(this.vertexPointLocation);
  254. }
  255. if (this.vertexTexCoordLocation >= 0) { // location of vertexTexCoord attribute is -1 when the basic program is bound
  256. gl.disableVertexAttribArray(this.vertexTexCoordLocation);
  257. }
  258. };
  259. /**
  260. * Initializes rendering state for drawing a specified terrain tile.
  261. * @param {DrawContext} dc The draw context.
  262. * @param {TerrainTile} terrainTile The terrain tile subsequently drawn via this tessellator's render function.
  263. * @throws {ArgumentError} If the specified tile is null or undefined.
  264. */
  265. Tessellator.prototype.beginRenderingTile = function (dc, terrainTile) {
  266. if (!terrainTile) {
  267. throw new ArgumentError(
  268. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "beginRenderingTile", "missingTile"));
  269. }
  270. var gl = dc.currentGlContext,
  271. gpuResourceCache = dc.gpuResourceCache;
  272. this.scratchMatrix.setToMultiply(dc.modelviewProjection, terrainTile.transformationMatrix);
  273. dc.currentProgram.loadModelviewProjection(gl, this.scratchMatrix);
  274. var vboCacheKey = dc.globeStateKey + terrainTile.tileKey,
  275. vbo = gpuResourceCache.resourceForKey(vboCacheKey);
  276. if (!vbo) {
  277. vbo = gl.createBuffer();
  278. gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
  279. gl.bufferData(gl.ARRAY_BUFFER, terrainTile.points, gl.STATIC_DRAW);
  280. dc.frameStatistics.incrementVboLoadCount(1);
  281. gpuResourceCache.putResource(vboCacheKey, vbo, terrainTile.points.length * 4);
  282. terrainTile.pointsVboStateKey = terrainTile.pointsStateKey;
  283. }
  284. else if (terrainTile.pointsVboStateKey != terrainTile.pointsStateKey) {
  285. gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
  286. gl.bufferSubData(gl.ARRAY_BUFFER, 0, terrainTile.points);
  287. terrainTile.pointsVboStateKey = terrainTile.pointsStateKey;
  288. }
  289. else {
  290. dc.currentGlContext.bindBuffer(gl.ARRAY_BUFFER, vbo);
  291. }
  292. gl.vertexAttribPointer(this.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
  293. };
  294. /**
  295. * Restores rendering state after drawing the most recent tile specified to
  296. * [beginRenderingTile]{@link Tessellator#beginRenderingTile}.
  297. * @param {DrawContext} dc The draw context.
  298. * @param {TerrainTile} terrainTile The terrain tile most recently rendered.
  299. * @throws {ArgumentError} If the specified tile is null or undefined.
  300. */
  301. Tessellator.prototype.endRenderingTile = function (dc, terrainTile) {
  302. // Intentionally empty until there's some reason to add code here.
  303. };
  304. /**
  305. * Renders a specified terrain tile.
  306. * @param {DrawContext} dc The draw context.
  307. * @param {TerrainTile} terrainTile The terrain tile to render.
  308. * @throws {ArgumentError} If the specified tile is null or undefined.
  309. */
  310. Tessellator.prototype.renderTile = function (dc, terrainTile) {
  311. if (!terrainTile) {
  312. throw new ArgumentError(
  313. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderTile", "missingTile"));
  314. }
  315. var gl = dc.currentGlContext,
  316. prim = gl.TRIANGLE_STRIP; // replace TRIANGLE_STRIP with LINE_STRIP to debug borders
  317. /*
  318. * Indices order in the buffer:
  319. *
  320. * base indices
  321. *
  322. * north border
  323. * south border
  324. * west border
  325. * east border
  326. *
  327. * north lores
  328. * south lores
  329. * west lores
  330. * east lores
  331. *
  332. * wireframe
  333. * outline
  334. */
  335. gl.drawElements(
  336. prim,
  337. this.numBaseIndices,
  338. gl.UNSIGNED_SHORT,
  339. this.baseIndicesOffset * 2);
  340. var level = terrainTile.level,
  341. neighborLevel;
  342. neighborLevel = terrainTile.neighborLevel(WorldWind.NORTH);
  343. if (neighborLevel && neighborLevel.compare(level) < 0) {
  344. gl.drawElements(
  345. prim,
  346. this.numIndicesLoresNorth,
  347. gl.UNSIGNED_SHORT,
  348. this.indicesLoresNorthOffset * 2);
  349. }
  350. else {
  351. gl.drawElements(
  352. prim,
  353. this.numIndicesNorth,
  354. gl.UNSIGNED_SHORT,
  355. this.indicesNorthOffset * 2);
  356. }
  357. neighborLevel = terrainTile.neighborLevel(WorldWind.SOUTH);
  358. if (neighborLevel && neighborLevel.compare(level) < 0) {
  359. gl.drawElements(
  360. prim,
  361. this.numIndicesLoresSouth,
  362. gl.UNSIGNED_SHORT,
  363. this.indicesLoresSouthOffset * 2);
  364. }
  365. else {
  366. gl.drawElements(
  367. prim,
  368. this.numIndicesSouth,
  369. gl.UNSIGNED_SHORT,
  370. this.indicesSouthOffset * 2);
  371. }
  372. neighborLevel = terrainTile.neighborLevel(WorldWind.WEST);
  373. if (neighborLevel && neighborLevel.compare(level) < 0) {
  374. gl.drawElements(
  375. prim,
  376. this.numIndicesLoresWest,
  377. gl.UNSIGNED_SHORT,
  378. this.indicesLoresWestOffset * 2);
  379. }
  380. else {
  381. gl.drawElements(
  382. prim,
  383. this.numIndicesWest,
  384. gl.UNSIGNED_SHORT,
  385. this.indicesWestOffset * 2);
  386. }
  387. neighborLevel = terrainTile.neighborLevel(WorldWind.EAST);
  388. if (neighborLevel && neighborLevel.compare(level) < 0) {
  389. gl.drawElements(
  390. prim,
  391. this.numIndicesLoresEast,
  392. gl.UNSIGNED_SHORT,
  393. this.indicesLoresEastOffset * 2);
  394. }
  395. else {
  396. gl.drawElements(
  397. prim,
  398. this.numIndicesEast,
  399. gl.UNSIGNED_SHORT,
  400. this.indicesEastOffset * 2);
  401. }
  402. };
  403. /**
  404. * Draws outlines of the triangles composing the tile.
  405. * @param {DrawContext} dc The current draw context.
  406. * @param {TerrainTile} terrainTile The tile to draw.
  407. * @throws {ArgumentError} If the specified tile is null or undefined.
  408. */
  409. Tessellator.prototype.renderWireframeTile = function (dc, terrainTile) {
  410. if (!terrainTile) {
  411. throw new ArgumentError(
  412. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderWireframeTile", "missingTile"));
  413. }
  414. var gl = dc.currentGlContext;
  415. // Must turn off texture coordinates, which were turned on in beginRendering.
  416. if (this.vertexTexCoordLocation >= 0) {
  417. gl.disableVertexAttribArray(this.vertexTexCoordLocation);
  418. }
  419. gl.drawElements(
  420. gl.LINES,
  421. this.numWireframeIndices,
  422. gl.UNSIGNED_SHORT,
  423. this.wireframeIndicesOffset * 2);
  424. };
  425. /**
  426. * Draws the outer boundary of a specified terrain tile.
  427. * @param {DrawContext} dc The current draw context.
  428. * @param {TerrainTile} terrainTile The tile whose outer boundary to draw.
  429. * @throws {ArgumentError} If the specified tile is null or undefined.
  430. */
  431. Tessellator.prototype.renderTileOutline = function (dc, terrainTile) {
  432. if (!terrainTile) {
  433. throw new ArgumentError(
  434. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "renderTileOutline", "missingTile"));
  435. }
  436. var gl = dc.currentGlContext;
  437. // Must turn off texture coordinates, which were turned on in beginRendering.
  438. if (this.vertexTexCoordLocation >= 0) {
  439. gl.disableVertexAttribArray(this.vertexTexCoordLocation);
  440. }
  441. gl.drawElements(
  442. gl.LINE_LOOP,
  443. this.numOutlineIndices,
  444. gl.UNSIGNED_SHORT,
  445. this.outlineIndicesOffset * 2);
  446. };
  447. /**
  448. * Causes this terrain to perform the picking operations on the specified tiles, as appropriate for the draw
  449. * context's pick settings. Normally, this draws the terrain in a unique pick color and computes the picked
  450. * terrain position. When the draw context is set to region picking mode, this omits the computation of a picked
  451. * terrain position.
  452. * @param {DrawContext} dc The current draw context.
  453. * @param {Array} tileList The list of tiles to pick.
  454. * @param {Object} pickDelegate Indicates the object to use as the picked object's <code>userObject</code>.
  455. * If null, then this tessellator is used as the <code>userObject</code>.
  456. * @throws {ArgumentError} If either the draw context or the tile list are null or undefined.
  457. */
  458. Tessellator.prototype.pick = function (dc, tileList, pickDelegate) {
  459. if (!dc) {
  460. throw new ArgumentError(
  461. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "pick", "missingDc"));
  462. }
  463. if (!tileList) {
  464. throw new ArgumentError(
  465. Logger.logMessage(Logger.LEVEL_SEVERE, "Tessellator", "pick", "missingList"));
  466. }
  467. var color = null,
  468. userObject = pickDelegate || this,
  469. position = new Position(0, 0, 0),
  470. pickableTiles = [];
  471. // Assemble a list of tiles that intersect the pick frustum. This eliminates unnecessary work for tiles that
  472. // do not contribute to the pick result.
  473. for (var i = 0, len = tileList.length; i < len; i++) {
  474. var tile = tileList[i];
  475. if (tile.extent.intersectsFrustum(dc.pickFrustum)) {
  476. pickableTiles.push(tile);
  477. }
  478. }
  479. // Draw the pickable tiles in a unique pick color. Suppress this step when picking the terrain only. In this
  480. // case drawing to the pick framebuffer is unnecessary.
  481. if (!dc.pickTerrainOnly) {
  482. color = dc.uniquePickColor();
  483. this.drawPickTiles(dc, pickableTiles, color);
  484. }
  485. // Determine the terrain position at the pick point. If the terrain is picked, add a corresponding picked
  486. // object to the draw context. Suppress this step in region picking mode.
  487. if (!dc.regionPicking) {
  488. var ray = dc.pickRay.clone(), // Cloning the pick ray is necessary here due to the fact that Tesselator.computeIntersections modifies ray
  489. point = this.computeNearestIntersection(ray, pickableTiles);
  490. if (point) {
  491. dc.globe.computePositionFromPoint(point[0], point[1], point[2], position);
  492. position.altitude = dc.globe.elevationAtLocation(position.latitude, position.longitude);
  493. dc.addPickedObject(new PickedObject(color, userObject, position, null, true));
  494. }
  495. }
  496. };
  497. // Internal function. Intentionally not documented.
  498. Tessellator.prototype.drawPickTiles = function (dc, tileList, color) {
  499. var gl = dc.currentGlContext;
  500. try {
  501. dc.findAndBindProgram(BasicProgram);
  502. dc.currentProgram.loadColor(gl, color);
  503. this.beginRendering(dc);
  504. for (var i = 0, len = tileList.length; i < len; i++) {
  505. var tile = tileList[i];
  506. this.beginRenderingTile(dc, tile);
  507. this.renderTile(dc, tile);
  508. this.endRenderingTile(dc, tile);
  509. }
  510. } finally {
  511. this.endRendering(dc);
  512. }
  513. };
  514. // Internal function. Intentionally not documented.
  515. Tessellator.prototype.computeNearestIntersection = function (line, tileList) {
  516. // Compute all intersections between the specified line and tile list.
  517. var results = [];
  518. for (var i = 0, len = tileList.length; i < len; i++) {
  519. this.computeIntersections(line, tileList[i], results);
  520. }
  521. if (results.length == 0) {
  522. return null; // no intersection
  523. } else {
  524. // Find and return the intersection nearest to the line's origin.
  525. var minDistance = Number.POSITIVE_INFINITY,
  526. minIndex;
  527. for (i = 0, len = results.length; i < len; i++) {
  528. var distance = line.origin.distanceToSquared(results[i]);
  529. if (minDistance > distance) {
  530. minDistance = distance;
  531. minIndex = i;
  532. }
  533. }
  534. return results[minIndex];
  535. }
  536. };
  537. // Internal function. Intentionally not documented.
  538. Tessellator.prototype.computeIntersections = function (line, tile, results) {
  539. var level = tile.level,
  540. neighborLevel,
  541. points = tile.points,
  542. elements,
  543. firstResult = results.length;
  544. // Translate the line from model coordinates to tile local coordinates.
  545. line.origin.subtract(tile.referencePoint);
  546. // Assemble the shared tile index geometry. This initializes the index properties used below.
  547. this.buildSharedGeometry(tile);
  548. // Compute any intersections with the tile's interior triangles..
  549. elements = this.baseIndices;
  550. WWMath.computeTriStripIntersections(line, points, elements, results);
  551. // Compute any intersections with the tile's south border triangles.
  552. neighborLevel = tile.neighborLevel(WorldWind.SOUTH);
  553. elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresSouth : this.indicesSouth;
  554. WWMath.computeTriStripIntersections(line, points, elements, results);
  555. // Compute any intersections with the tile's west border triangles.
  556. neighborLevel = tile.neighborLevel(WorldWind.WEST);
  557. elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresWest : this.indicesWest;
  558. WWMath.computeTriStripIntersections(line, points, elements, results);
  559. // Compute any intersections with the tile's east border triangles.
  560. neighborLevel = tile.neighborLevel(WorldWind.EAST);
  561. elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresEast : this.indicesEast;
  562. WWMath.computeTriStripIntersections(line, points, elements, results);
  563. // Compute any intersections with the tile's north border triangles.
  564. neighborLevel = tile.neighborLevel(WorldWind.NORTH);
  565. elements = neighborLevel && neighborLevel.compare(level) < 0 ? this.indicesLoresNorth : this.indicesNorth;
  566. WWMath.computeTriStripIntersections(line, points, elements, results);
  567. // Translate the line and the intersection results from tile local coordinates to model coordinates.
  568. line.origin.add(tile.referencePoint);
  569. for (var i = firstResult, len = results.length; i < len; i++) {
  570. results[i].add(tile.referencePoint);
  571. }
  572. };
  573. /***********************************************************************
  574. * Internal methods - assume that arguments have been validated already.
  575. ***********************************************************************/
  576. Tessellator.prototype.createTopLevelTiles = function (dc) {
  577. this.topLevelTiles[dc.globeStateKey] = [];
  578. Tile.createTilesForLevel(this.levels.firstLevel(), this, this.topLevelTiles[dc.globeStateKey]);
  579. };
  580. Tessellator.prototype.addTileOrDescendants = function (dc, tile) {
  581. if (this.tileMeetsRenderCriteria(dc, tile)) {
  582. this.addTile(dc, tile);
  583. return;
  584. }
  585. this.addTileDescendants(dc, tile);
  586. };
  587. Tessellator.prototype.addTileDescendants = function (dc, tile) {
  588. var nextLevel = tile.level.nextLevel();
  589. var subTiles = tile.subdivideToCache(nextLevel, this, this.tileCache);
  590. for (var index = 0; index < subTiles.length; index += 1) {
  591. var child = subTiles[index];
  592. child.update(dc);
  593. if (this.levels.sector.intersects(child.sector) && this.isTileVisible(dc, child)) {
  594. this.addTileOrDescendants(dc, child);
  595. }
  596. }
  597. };
  598. Tessellator.prototype.addTile = function (dc, tile) {
  599. // Insert tile at index idx.
  600. var idx = this.tiles.length;
  601. this.tiles.push(tile);
  602. // Insert tile into corner data collection for later LOD neighbor analysis.
  603. var sector = tile.sector;
  604. // Corners of the tile.
  605. var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
  606. seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
  607. nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
  608. swTileCorner = [sector.minLatitude, sector.minLongitude].toString(),
  609. corner;
  610. corner = this.corners[swTileCorner];
  611. if (!corner) {
  612. this.corners[swTileCorner] = {'sw': idx}; //corner;
  613. }
  614. else {
  615. // assert(!corner.sw, "sw already defined");
  616. corner.sw = idx;
  617. }
  618. corner = this.corners[nwTileCorner];
  619. if (!corner) {
  620. this.corners[nwTileCorner] = {'nw': idx};
  621. }
  622. else {
  623. // assert(!corner.nw, "nw already defined");
  624. corner.nw = idx;
  625. }
  626. corner = this.corners[seTileCorner];
  627. if (!corner) {
  628. this.corners[seTileCorner] = {'se': idx};
  629. }
  630. else {
  631. // assert(!corver.se, "se already defined");
  632. corner.se = idx;
  633. }
  634. corner = this.corners[neTileCorner];
  635. if (!corner) {
  636. this.corners[neTileCorner] = {'ne': idx};
  637. }
  638. else {
  639. //assert(!corner.ne, "ne already defined");
  640. corner.ne = idx;
  641. }
  642. };
  643. Tessellator.prototype.refineNeighbors = function (dc) {
  644. var tileRefinementSet = {};
  645. for (var idx = 0, len = this.tiles.length; idx < len; idx += 1) {
  646. var tile = this.tiles[idx],
  647. levelNumber = tile.level.levelNumber,
  648. sector = tile.sector,
  649. corner,
  650. neighbor,
  651. idx,
  652. len;
  653. // Corners of the tile.
  654. var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
  655. seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
  656. nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
  657. swTileCorner = [sector.minLatitude, sector.minLongitude].toString();
  658. corner = this.corners[neTileCorner];
  659. // assert(corner, "northeast corner not found");
  660. if (corner.hasOwnProperty('se')) {
  661. neighbor = corner.se;
  662. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  663. if (!tileRefinementSet[neighbor]) {
  664. tileRefinementSet[neighbor] = true;
  665. }
  666. }
  667. }
  668. if (corner.hasOwnProperty('nw')) {
  669. neighbor = corner.nw;
  670. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  671. if (!tileRefinementSet[neighbor]) {
  672. tileRefinementSet[neighbor] = true;
  673. }
  674. }
  675. }
  676. corner = this.corners[seTileCorner];
  677. // assert(corner, "southeast corner not found");
  678. if (corner.hasOwnProperty('ne')) {
  679. neighbor = corner.ne;
  680. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  681. if (!tileRefinementSet[neighbor]) {
  682. tileRefinementSet[neighbor] = true;
  683. }
  684. }
  685. }
  686. if (corner.hasOwnProperty('sw')) {
  687. neighbor = corner.sw;
  688. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  689. if (!tileRefinementSet[neighbor]) {
  690. tileRefinementSet[neighbor] = true;
  691. }
  692. }
  693. }
  694. corner = this.corners[nwTileCorner];
  695. // assert(corner, "northwest corner not found");
  696. if (corner.hasOwnProperty('ne')) {
  697. neighbor = corner.ne;
  698. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  699. if (!tileRefinementSet[neighbor]) {
  700. tileRefinementSet[neighbor] = true;
  701. }
  702. }
  703. }
  704. if (corner.hasOwnProperty('sw')) {
  705. neighbor = corner.sw;
  706. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  707. if (!tileRefinementSet[neighbor]) {
  708. tileRefinementSet[neighbor] = true;
  709. }
  710. }
  711. }
  712. corner = this.corners[swTileCorner];
  713. // assert(corner, "southwest corner not found");
  714. if (corner.hasOwnProperty('se')) {
  715. neighbor = corner.se;
  716. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  717. if (!tileRefinementSet[neighbor]) {
  718. tileRefinementSet[neighbor] = true;
  719. }
  720. }
  721. }
  722. if (corner.hasOwnProperty('nw')) {
  723. neighbor = corner.nw;
  724. if (this.tiles[neighbor].level.levelNumber < levelNumber - 1) {
  725. if (!tileRefinementSet[neighbor]) {
  726. tileRefinementSet[neighbor] = true;
  727. }
  728. }
  729. }
  730. }
  731. // Partition tiles into those requiring refinement and those that don't need refinement.
  732. var tilesNeedingRefinement = [],
  733. tilesNotNeedingRefinement = [];
  734. for (idx = 0, len = this.tiles.length; idx < len; idx += 1) {
  735. tile = this.tiles[idx];
  736. if (tileRefinementSet[idx]) {
  737. tilesNeedingRefinement.push(tile);
  738. }
  739. else {
  740. tilesNotNeedingRefinement.push(tile);
  741. }
  742. }
  743. // When tiles need refinement, recur.
  744. if (tilesNeedingRefinement.length > 0) {
  745. // Reset refinement state.
  746. this.tiles = [];
  747. this.corners = {};
  748. // For tiles that don't need refinement, simply add the tile.
  749. for (idx = 0, len = tilesNotNeedingRefinement.length; idx < len; idx += 1) {
  750. tile = tilesNotNeedingRefinement[idx];
  751. this.addTile(dc, tile);
  752. }
  753. // For tiles that do need refinement, subdivide the tile and add its descendants.
  754. for (idx = 0, len = tilesNeedingRefinement.length; idx < len; idx += 1) {
  755. var tile = tilesNeedingRefinement[idx];
  756. this.addTileDescendants(dc, tile);
  757. }
  758. // Recur.
  759. this.refineNeighbors(dc);
  760. }
  761. };
  762. Tessellator.prototype.finishTessellating = function (dc) {
  763. for (var idx = 0, len = this.tiles.length; idx < len; idx += 1) {
  764. var tile = this.tiles[idx];
  765. this.setNeighbors(tile);
  766. this.regenerateTileGeometryIfNeeded(dc, tile);
  767. this.currentTiles.addTile(tile);
  768. }
  769. };
  770. Tessellator.prototype.setNeighbors = function (tile) {
  771. var sector = tile.sector;
  772. // Corners of the tile.
  773. var neTileCorner = [sector.maxLatitude, sector.maxLongitude].toString(),
  774. seTileCorner = [sector.minLatitude, sector.maxLongitude].toString(),
  775. nwTileCorner = [sector.maxLatitude, sector.minLongitude].toString(),
  776. swTileCorner = [sector.minLatitude, sector.minLongitude].toString();
  777. var neCorner = this.corners[neTileCorner],
  778. seCorner = this.corners[seTileCorner],
  779. nwCorner = this.corners[nwTileCorner],
  780. swCorner = this.corners[swTileCorner];
  781. var northIdx = -1, // neCorner.hasOwnProperty('se') ? neCorner.se : nwCorner.hasOwnProperty('sw') ? nwCorner.sw : -1,
  782. southIdx = -1, // seCorner.hasOwnProperty('ne') ? seCorner.ne : swCorner.hasOwnProperty('nw') ? swCorner.nw : -1,
  783. eastIdx = -1, // neCorner.hasOwnProperty('nw') ? neCorner.nw : seCorner.hasOwnProperty('sw') ? seCorner.sw : -1,
  784. westIdx = -1; //nwCorner.hasOwnProperty('ne') ? nwCorner.ne : swCorner.hasOwnProperty('se') ? swCorner.se : -1;
  785. if (neCorner.hasOwnProperty('se')) {
  786. northIdx = neCorner.se;
  787. }
  788. else if (nwCorner.hasOwnProperty('sw')) {
  789. northIdx = nwCorner.sw;
  790. }
  791. if (seCorner.hasOwnProperty('ne')) {
  792. southIdx = seCorner.ne;
  793. }
  794. else if (swCorner.hasOwnProperty('nw')) {
  795. southIdx = swCorner.nw;
  796. }
  797. if (neCorner.hasOwnProperty('nw')) {
  798. eastIdx = neCorner.nw;
  799. }
  800. else if (seCorner.hasOwnProperty('sw')) {
  801. eastIdx = seCorner.sw;
  802. }
  803. if (nwCorner.hasOwnProperty('ne')) {
  804. westIdx = nwCorner.ne;
  805. }
  806. else if (swCorner.hasOwnProperty('se')) {
  807. westIdx = swCorner.se;
  808. }
  809. tile.setNeighborLevel(WorldWind.NORTH, (northIdx >= 0) ? this.tiles[northIdx].level : null);
  810. tile.setNeighborLevel(WorldWind.SOUTH, (southIdx >= 0) ? this.tiles[southIdx].level : null);
  811. tile.setNeighborLevel(WorldWind.EAST, (eastIdx >= 0) ? this.tiles[eastIdx].level : null);
  812. tile.setNeighborLevel(WorldWind.WEST, (westIdx >= 0) ? this.tiles[westIdx].level : null);
  813. };
  814. Tessellator.prototype.isTileVisible = function (dc, tile) {
  815. if (dc.globe.projectionLimits && !tile.sector.overlaps(dc.globe.projectionLimits)) {
  816. return false;
  817. }
  818. return tile.extent.intersectsFrustum(dc.frustumInModelCoordinates);
  819. };
  820. Tessellator.prototype.tileMeetsRenderCriteria = function (dc, tile) {
  821. var s = this.detailControl;
  822. if (tile.sector.minLatitude >= 75 || tile.sector.maxLatitude <= -75) {
  823. s *= 2;
  824. }
  825. return tile.level.isLastLevel() || !tile.mustSubdivide(dc, s);
  826. };
  827. Tessellator.prototype.regenerateTileGeometryIfNeeded = function (dc, tile) {
  828. var stateKey = dc.globeStateKey + tile.stateKey + dc.verticalExaggeration;
  829. if (!tile.points || tile.pointsStateKey != stateKey) {
  830. this.regenerateTileGeometry(dc, tile);
  831. tile.pointsStateKey = stateKey;
  832. }
  833. };
  834. /**
  835. * Internal use only.
  836. * TODO: Remove this function when Tessellator and ElevationModel are refactored
  837. * Artificially calculates an adjusted target resolution for the given texel size to more
  838. * optimally select elevation coverages until later refactoring.
  839. * @returns {Number} An adjusted target resolution in degrees.
  840. * @ignore
  841. */
  842. Tessellator.prototype.coverageTargetResolution = function (texelSize) {
  843. return (texelSize / 8) * Angle.RADIANS_TO_DEGREES;
  844. };
  845. Tessellator.prototype.regenerateTileGeometry = function (dc, tile) {
  846. var numLat = tile.tileHeight + 1, // num points in each dimension is 1 more than the number of tile cells
  847. numLon = tile.tileWidth + 1,
  848. refPoint = tile.referencePoint,
  849. elevations = this.scratchElevations;
  850. // Allocate space for the tile's elevations.
  851. if (!elevations) {
  852. elevations = new Float64Array(numLat * numLon);
  853. this.scratchElevations = elevations;
  854. }
  855. // Allocate space for the tile's Cartesian coordinates.
  856. if (!tile.points) {
  857. tile.points = new Float32Array(numLat * numLon * 3);
  858. }
  859. // Retrieve the elevations for all points in the tile.
  860. WWUtil.fillArray(elevations, 0);
  861. dc.globe.elevationsForGrid(tile.sector, numLat, numLon, this.coverageTargetResolution(tile.texelSize), elevations);
  862. // Modify the elevations around the tile's border to match neighbors of lower resolution, if any.
  863. if (this.mustAlignNeighborElevations(dc, tile)) {
  864. this.alignNeighborElevations(dc, tile, elevations);
  865. }
  866. // Compute the tile's Cartesian coordinates relative to a local origin, called the reference point.
  867. WWUtil.multiplyArray(elevations, dc.verticalExaggeration);
  868. dc.globe.computePointsForGrid(tile.sector, numLat, numLon, elevations, refPoint, tile.points);
  869. // Establish a transform that is used later to move the tile coordinates into place relative to the globe.
  870. tile.transformationMatrix.setTranslation(refPoint[0], refPoint[1], refPoint[2]);
  871. };
  872. Tessellator.prototype.mustAlignNeighborElevations = function (dc, tile) {
  873. var level = tile.level,
  874. northLevel = tile.neighborLevel(WorldWind.NORTH),
  875. southLevel = tile.neighborLevel(WorldWind.SOUTH),
  876. eastLevel = tile.neighborLevel(WorldWind.EAST),
  877. westLevel = tile.neighborLevel(WorldWind.WEST);
  878. return (northLevel && northLevel.compare(level) < 0) ||
  879. (southLevel && southLevel.compare(level) < 0) ||
  880. (eastLevel && eastLevel.compare(level) < 0) ||
  881. (westLevel && westLevel.compare(level) < 0);
  882. };
  883. Tessellator.prototype.alignNeighborElevations = function (dc, tile, elevations) {
  884. var numLat = tile.tileHeight + 1, // num points in each dimension is 1 more than the number of tile cells
  885. numLon = tile.tileWidth + 1,
  886. level = tile.level,
  887. prevNumLat = Math.floor(numLat / 2) + 1, // num prev level points is 1 more than 1/2 the number of cells
  888. prevNumLon = Math.floor(numLon / 2) + 1,
  889. prevLevel = level.previousLevel(),
  890. prevElevations = this.scratchPrevElevations,
  891. neighborLevel,
  892. i, index, prevIndex;
  893. // Allocate space for the previous level elevations.
  894. if (!prevElevations) {
  895. prevElevations = new Float64Array(prevNumLat * prevNumLon);
  896. this.scratchPrevElevations = prevElevations;
  897. }
  898. // Retrieve the previous level elevations, using 1/2 the number of tile cells.
  899. WWUtil.fillArray(prevElevations, 0);
  900. dc.globe.elevationsForGrid(tile.sector, prevNumLat, prevNumLon, this.coverageTargetResolution(prevLevel.texelSize), prevElevations);
  901. // Use previous level elevations along the north edge when the northern neighbor is lower resolution.
  902. neighborLevel = tile.neighborLevel(WorldWind.NORTH);
  903. if (neighborLevel && neighborLevel.compare(level) < 0) {
  904. index = (numLat - 1) * numLon;
  905. prevIndex = (prevNumLat - 1) * prevNumLon;
  906. for (i = 0; i < prevNumLon; i++, index += 2, prevIndex += 1) {
  907. elevations[index] = prevElevations[prevIndex];
  908. if (i < prevNumLon - 1) {
  909. elevations[index + 1] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + 1]);
  910. }
  911. }
  912. }
  913. // Use previous level elevations along the south edge when the southern neighbor is lower resolution.
  914. neighborLevel = tile.neighborLevel(WorldWind.SOUTH);
  915. if (neighborLevel && neighborLevel.compare(level) < 0) {
  916. index = 0;
  917. prevIndex = 0;
  918. for (i = 0; i < prevNumLon; i++, index += 2, prevIndex += 1) {
  919. elevations[index] = prevElevations[prevIndex];
  920. if (i < prevNumLon - 1) {
  921. elevations[index + 1] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + 1]);
  922. }
  923. }
  924. }
  925. // Use previous level elevations along the east edge when the eastern neighbor is lower resolution.
  926. neighborLevel = tile.neighborLevel(WorldWind.EAST);
  927. if (neighborLevel && neighborLevel.compare(level) < 0) {
  928. index = numLon - 1;
  929. prevIndex = prevNumLon - 1;
  930. for (i = 0; i < prevNumLat; i++, index += 2 * numLon, prevIndex += prevNumLon) {
  931. elevations[index] = prevElevations[prevIndex];
  932. if (i < prevNumLat - 1) {
  933. elevations[index + numLon] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + prevNumLon]);
  934. }
  935. }
  936. }
  937. // Use previous level elevations along the west edge when the western neighbor is lower resolution.
  938. neighborLevel = tile.neighborLevel(WorldWind.WEST);
  939. if (neighborLevel && neighborLevel.compare(level) < 0) {
  940. index = 0;
  941. prevIndex = 0;
  942. for (i = 0; i < prevNumLat; i++, index += 2 * numLon, prevIndex += prevNumLon) {
  943. elevations[index] = prevElevations[prevIndex];
  944. if (i < prevNumLat - 1) {
  945. elevations[index + numLon] = 0.5 * (prevElevations[prevIndex] + prevElevations[prevIndex + prevNumLon]);
  946. }
  947. }
  948. }
  949. };
  950. Tessellator.prototype.buildSharedGeometry = function () {
  951. // TODO: put all indices into a single buffer
  952. var tileWidth = this.levels.tileWidth,
  953. tileHeight = this.levels.tileHeight;
  954. if (!this.texCoords) {
  955. this.buildTexCoords(tileWidth, tileHeight);
  956. }
  957. if (!this.indices) {
  958. this.buildIndices(tileWidth, tileHeight);
  959. }
  960. };
  961. Tessellator.prototype.buildTexCoords = function (tileWidth, tileHeight) {
  962. var numCols = tileWidth + 1,
  963. numRows = tileHeight + 1,
  964. colDelta = 1 / tileWidth,
  965. rowDelta = 1 / tileHeight,
  966. buffer = new Float32Array(numCols * numRows * 2),
  967. index = 0;
  968. for (var row = 0, t = 0; row < numRows; row++, t += rowDelta) {
  969. if (row == numRows - 1) {
  970. t = 1; // explicitly set the last row coordinate to ensure alignment
  971. }
  972. for (var col = 0, s = 0; col < numCols; col++, s += colDelta) {
  973. if (col == numCols - 1) {
  974. s = 1; // explicitly set the last column coordinate to ensure alignment
  975. }
  976. buffer[index++] = s;
  977. buffer[index++] = t;
  978. }
  979. }
  980. this.texCoords = buffer;
  981. };
  982. Tessellator.prototype.buildIndices = function (tileWidth, tileHeight) {
  983. var vertexIndex; // The index of the vertex in the sample grid.
  984. // The number of vertices in each dimension is 1 more than the number of cells.
  985. var numLatVertices = tileHeight + 1,
  986. numLonVertices = tileWidth + 1,
  987. latIndexMid = tileHeight / 2, // Assumption: tileHeight is even, so that there is a midpoint!
  988. lonIndexMid = tileWidth / 2; // Assumption: tileWidth is even, so that there is a midpoint!
  989. // Each vertex has two indices associated with it: the current vertex index and the index of the row.
  990. // There are tileHeight rows.
  991. // There are tileHeight + 2 columns
  992. var numIndices = 2 * (numLatVertices - 3) * (numLonVertices - 2) + 2 * (numLatVertices - 3);
  993. var indices = [];
  994. // Inset core by one round of sub-tiles. Full grid is numLatVertices x numLonVertices. This must be used
  995. // to address vertices in the core as well.
  996. var index = 0;
  997. for (var lonIndex = 1; lonIndex < numLonVertices - 2; lonIndex += 1) {
  998. for (var latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
  999. vertexIndex = lonIndex + latIndex * numLonVertices;
  1000. // Create a triangle strip joining each adjacent column of vertices, starting in the top left corner and
  1001. // proceeding to the right. The first vertex starts with the left row of vertices and moves right to create a
  1002. // counterclockwise winding order.
  1003. indices[index++] = vertexIndex;
  1004. indices[index++] = vertexIndex + 1;
  1005. }
  1006. // Insert indices to create 2 degenerate triangles:
  1007. // one for the end of the current row, and
  1008. // one for the beginning of the next row.
  1009. indices[index++] = vertexIndex + 1;
  1010. vertexIndex = (lonIndex + 1) + 1 * numLonVertices;
  1011. indices[index++] = vertexIndex;
  1012. }
  1013. this.baseIndicesOffset = indices.length - numIndices;
  1014. this.baseIndices = new Uint16Array(indices.slice(this.baseIndicesOffset));
  1015. this.numBaseIndices = numIndices;
  1016. // TODO: parameterize and refactor!!!!!
  1017. // Software engineering notes: There are patterns being used in the following code that should be abstracted.
  1018. // However, I suspect that the process of abstracting the patterns will result in as much code created
  1019. // as gets removed. YMMV. If JavaScript had a meta-programming (a.k.a., macro) facility, that code would be
  1020. // processed at "compile" time rather than "runtime". But it doesn't have such a facility that I know of.
  1021. //
  1022. // Patterns used:
  1023. // 0) Each tile has four borders: north, south, east, and west.
  1024. // 1) Counter-clockwise traversal around the outside results in clockwise meshes amendable to back-face elimination.
  1025. // 2) For each vertex on the exterior, there corresponds a vertex on the interior that creates a diagonal.
  1026. // 3) Each border construction is broken into three phases:
  1027. // a) The starting phase to generate the first half of the border,
  1028. // b) The middle phase, where a single vertex reference gets created, and
  1029. // c) The ending phase to complete the generation of the border.
  1030. // 4) Each border is generated in two variants:
  1031. // a) one variant that mates with a tile at the same level of detail, and
  1032. // b) another variant that mates with a tile at the next lower level of detail.
  1033. // 5) Borders that mate with the next lower level of detail are constrained to lie on even indices.
  1034. // 6) Evenness is generated by ANDing the index with a mask that has 1's in all bits except for the LSB,
  1035. // which results in clearing the LSB os the index, making it even.
  1036. // 7) The section that generates lower level LOD borders gives up any attempt to be optimal because of the
  1037. // complexity. Instead, correctness was preferred. That said, any performance lost is in the noise,
  1038. // since this code only gets run once.
  1039. /*
  1040. * The following section of code generates full resolution boundary meshes. These are used to mate
  1041. * with neighboring tiles that are at the same level of detail.
  1042. */
  1043. // North border.
  1044. numIndices = 2 * numLonVertices - 2;
  1045. latIndex = numLatVertices - 1;
  1046. // Corner vertex.
  1047. lonIndex = numLonVertices - 1;
  1048. vertexIndex = lonIndex + latIndex * numLonVertices;
  1049. indices[index++] = vertexIndex;
  1050. for (lonIndex = numLonVertices - 2; lonIndex > 0; lonIndex -= 1) {
  1051. vertexIndex = lonIndex + latIndex * numLonVertices;
  1052. indices[index++] = vertexIndex;
  1053. indices[index++] = vertexIndex - numLonVertices;
  1054. }
  1055. // Corner vertex.
  1056. lonIndex = 0;
  1057. vertexIndex = lonIndex + latIndex * numLonVertices;
  1058. indices[index++] = vertexIndex;
  1059. this.indicesNorthOffset = indices.length - numIndices;
  1060. this.indicesNorth = new Uint16Array(indices.slice(this.indicesNorthOffset));
  1061. this.numIndicesNorth = numIndices;
  1062. // South border.
  1063. numIndices = 2 * numLonVertices - 2;
  1064. latIndex = 0;
  1065. // Corner vertex.
  1066. lonIndex = 0;
  1067. vertexIndex = lonIndex + latIndex * numLonVertices;
  1068. indices[index++] = vertexIndex;
  1069. for (lonIndex = 1; lonIndex < numLonVertices - 1; lonIndex += 1) {
  1070. vertexIndex = lonIndex + latIndex * numLonVertices;
  1071. indices[index++] = vertexIndex;
  1072. indices[index++] = vertexIndex + numLonVertices;
  1073. }
  1074. // Corner vertex.
  1075. lonIndex = numLonVertices - 1;
  1076. vertexIndex = lonIndex + latIndex * numLonVertices;
  1077. indices[index++] = vertexIndex;
  1078. this.indicesSouthOffset = indices.length - numIndices;
  1079. this.indicesSouth = new Uint16Array(indices.slice(this.indicesSouthOffset));
  1080. this.numIndicesSouth = numIndices;
  1081. // West border.
  1082. numIndices = 2 * numLatVertices - 2;
  1083. lonIndex = 0;
  1084. // Corner vertex.
  1085. latIndex = numLatVertices - 1;
  1086. vertexIndex = lonIndex + latIndex * numLonVertices;
  1087. indices[index++] = vertexIndex;
  1088. for (latIndex = numLatVertices - 2; latIndex > 0; latIndex -= 1) {
  1089. vertexIndex = lonIndex + latIndex * numLonVertices;
  1090. indices[index++] = vertexIndex;
  1091. indices[index++] = vertexIndex + 1;
  1092. }
  1093. // Corner vertex.
  1094. latIndex = 0;
  1095. vertexIndex = lonIndex + latIndex * numLonVertices;
  1096. indices[index++] = vertexIndex;
  1097. this.indicesWestOffset = indices.length - numIndices;
  1098. this.indicesWest = new Uint16Array(indices.slice(this.indicesWestOffset));
  1099. this.numIndicesWest = numIndices;
  1100. // East border.
  1101. numIndices = 2 * numLatVertices - 2;
  1102. lonIndex = numLonVertices - 1;
  1103. // Corner vertex.
  1104. latIndex = 0;
  1105. vertexIndex = lonIndex + latIndex * numLonVertices;
  1106. indices[index++] = vertexIndex;
  1107. for (latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
  1108. vertexIndex = lonIndex + latIndex * numLonVertices;
  1109. indices[index++] = vertexIndex;
  1110. indices[index++] = vertexIndex - 1;
  1111. }
  1112. // Corner vertex.
  1113. latIndex = numLatVertices - 1;
  1114. vertexIndex = lonIndex + latIndex * numLonVertices;
  1115. indices[index++] = vertexIndex;
  1116. this.indicesEastOffset = indices.length - numIndices;
  1117. this.indicesEast = new Uint16Array(indices.slice(this.indicesEastOffset));
  1118. this.numIndicesEast = numIndices;
  1119. /*
  1120. * The following section of code generates "lores" low resolution boundary meshes. These are used to mate
  1121. * with neighboring tiles that are at a lower level of detail. The property of these lower level meshes is that
  1122. * they have half the number of vertices.
  1123. *
  1124. * To generate the boundary meshes, force the use of only even boundary vertex indices.
  1125. */
  1126. // North border.
  1127. numIndices = 2 * numLonVertices - 2;
  1128. latIndex = numLatVertices - 1;
  1129. // Corner vertex.
  1130. lonIndex = numLonVertices - 1;
  1131. vertexIndex = lonIndex + latIndex * numLonVertices;
  1132. indices[index++] = vertexIndex;
  1133. for (lonIndex = numLonVertices - 2; lonIndex > 0; lonIndex -= 1) {
  1134. // Exterior vertex rounded up to even index.
  1135. vertexIndex = ((lonIndex + 1) & ~1) + latIndex * numLonVertices;
  1136. indices[index++] = vertexIndex;
  1137. // Interior vertex.
  1138. vertexIndex = lonIndex + (latIndex - 1) * numLonVertices;
  1139. indices[index++] = vertexIndex;
  1140. }
  1141. // Corner vertex.
  1142. lonIndex = 0;
  1143. vertexIndex = lonIndex + latIndex * numLonVertices;
  1144. indices[index++] = vertexIndex;
  1145. this.indicesLoresNorthOffset = indices.length - numIndices;
  1146. this.indicesLoresNorth = new Uint16Array(indices.slice(this.indicesLoresNorthOffset));
  1147. this.numIndicesLoresNorth = numIndices;
  1148. // South border.
  1149. numIndices = 2 * numLonVertices - 2;
  1150. latIndex = 0;
  1151. // Corner vertex.
  1152. lonIndex = 0;
  1153. vertexIndex = lonIndex + latIndex * numLonVertices;
  1154. indices[index++] = vertexIndex;
  1155. for (lonIndex = 1; lonIndex < numLonVertices - 1; lonIndex += 1) {
  1156. // Exterior Vertex rounded down to even index.
  1157. vertexIndex = (lonIndex & ~1) + latIndex * numLonVertices;
  1158. indices[index++] = vertexIndex;
  1159. // Interior vertex.
  1160. vertexIndex = lonIndex + (latIndex + 1) * numLonVertices;
  1161. indices[index++] = vertexIndex;
  1162. }
  1163. // Corner vertex.
  1164. lonIndex = numLonVertices - 1;
  1165. vertexIndex = lonIndex + latIndex * numLonVertices;
  1166. indices[index++] = vertexIndex;
  1167. this.indicesLoresSouthOffset = indices.length - numIndices;
  1168. this.indicesLoresSouth = new Uint16Array(indices.slice(this.indicesLoresSouthOffset));
  1169. this.numIndicesLoresSouth = numIndices;
  1170. // West border.
  1171. numIndices = 2 * numLatVertices - 2;
  1172. lonIndex = 0;
  1173. // Corner vertex.
  1174. latIndex = numLatVertices - 1;
  1175. vertexIndex = lonIndex + latIndex * numLonVertices;
  1176. indices[index++] = vertexIndex;
  1177. for (latIndex = numLatVertices - 2; latIndex > 0; latIndex -= 1) {
  1178. // Exterior Vertex rounded up to even index.
  1179. vertexIndex = lonIndex + ((latIndex + 1) & ~1) * numLonVertices;
  1180. indices[index++] = vertexIndex;
  1181. // Interior vertex.
  1182. vertexIndex = (lonIndex + 1) + latIndex * numLonVertices;
  1183. indices[index++] = vertexIndex;
  1184. }
  1185. // Corner vertex.
  1186. latIndex = 0;
  1187. vertexIndex = lonIndex + latIndex * numLonVertices;
  1188. indices[index++] = vertexIndex;
  1189. this.indicesLoresWestOffset = indices.length - numIndices;
  1190. this.indicesLoresWest = new Uint16Array(indices.slice(this.indicesLoresWestOffset));
  1191. this.numIndicesLoresWest = numIndices;
  1192. // East border.
  1193. numIndices = 2 * numLatVertices - 2;
  1194. lonIndex = numLonVertices - 1;
  1195. // Corner vertex.
  1196. latIndex = 0;
  1197. vertexIndex = lonIndex + latIndex * numLonVertices;
  1198. indices[index++] = vertexIndex;
  1199. for (latIndex = 1; latIndex < numLatVertices - 1; latIndex += 1) {
  1200. // Exterior vertex rounded down to even index.
  1201. vertexIndex = lonIndex + (latIndex & ~1) * numLonVertices;
  1202. indices[index++] = vertexIndex;
  1203. // Interior vertex.
  1204. vertexIndex = (lonIndex - 1) + latIndex * numLonVertices;
  1205. indices[index++] = vertexIndex;
  1206. }
  1207. // Corner vertex.
  1208. latIndex = numLatVertices - 1;
  1209. vertexIndex = lonIndex + latIndex * numLonVertices;
  1210. indices[index++] = vertexIndex;
  1211. this.indicesLoresEastOffset = indices.length - numIndices;
  1212. this.indicesLoresEast = new Uint16Array(indices.slice(this.indicesLoresEastOffset));
  1213. this.numIndicesLoresEast = numIndices;
  1214. var wireframeIndices = this.buildWireframeIndices(tileWidth, tileHeight);
  1215. var outlineIndices = this.buildOutlineIndices(tileWidth, tileHeight);
  1216. indices = indices.concat(wireframeIndices);
  1217. this.wireframeIndicesOffset = indices.length - this.numWireframeIndices;
  1218. indices = indices.concat(outlineIndices);
  1219. this.outlineIndicesOffset = indices.length - this.numOutlineIndices;
  1220. this.indices = new Uint16Array(indices);
  1221. };
  1222. Tessellator.prototype.buildWireframeIndices = function (tileWidth, tileHeight) {
  1223. // The wireframe representation draws the vertices that appear on the surface.
  1224. // The number of vertices in each dimension is 1 more than the number of cells.
  1225. var numLatVertices = tileHeight + 1;
  1226. var numLonVertices = tileWidth + 1;
  1227. // Allocate an array to hold the computed indices.
  1228. var numIndices = 2 * tileWidth * numLatVertices + 2 * tileHeight * numLonVertices;
  1229. var indices = [];
  1230. var rowStride = numLonVertices;
  1231. var index = 0,
  1232. lonIndex,
  1233. latIndex,
  1234. vertexIndex;
  1235. // Add a line between each row to define the horizontal cell outlines.
  1236. for (latIndex = 0; latIndex < numLatVertices; latIndex += 1) {
  1237. for (lonIndex = 0; lonIndex < tileWidth; lonIndex += 1) {
  1238. vertexIndex = lonIndex + latIndex * rowStride;
  1239. indices[index] = vertexIndex;
  1240. indices[index + 1] = (vertexIndex + 1);
  1241. index += 2
  1242. }
  1243. }
  1244. // Add a line between each column to define the vertical cell outlines.
  1245. for (lonIndex = 0; lonIndex < numLonVertices; lonIndex += 1) {
  1246. for (latIndex = 0; latIndex < tileHeight; latIndex += 1) {
  1247. vertexIndex = lonIndex + latIndex * rowStride;
  1248. indices[index] = vertexIndex;
  1249. indices[index + 1] = (vertexIndex + rowStride);
  1250. index += 2;
  1251. }
  1252. }
  1253. this.numWireframeIndices = numIndices;
  1254. return indices;
  1255. };
  1256. Tessellator.prototype.buildOutlineIndices = function (tileWidth, tileHeight) {
  1257. // The outline representation traces the tile's outer edge on the surface.
  1258. // The number of vertices in each dimension is 1 more than the number of cells.
  1259. var numLatVertices = tileHeight + 1;
  1260. var numLonVertices = tileWidth + 1;
  1261. // Allocate an array to hold the computed indices.
  1262. var numIndices = 2 * (numLatVertices - 2) + 2 * numLonVertices + 1;
  1263. var indices = [];
  1264. var rowStride = numLatVertices;
  1265. var index = 0,
  1266. lonIndex,
  1267. latIndex,
  1268. vertexIndex;
  1269. // Bottom row, starting at the left and going right.
  1270. latIndex = 0;
  1271. for (lonIndex = 0; lonIndex < numLonVertices; lonIndex += 1) {
  1272. vertexIndex = lonIndex + latIndex * numLonVertices;
  1273. indices[index] = vertexIndex;
  1274. index += 1;
  1275. }
  1276. // Right column, starting at the bottom and going up.
  1277. lonIndex = numLonVertices - 1;
  1278. for (latIndex = 1; latIndex < numLatVertices; latIndex += 1) {
  1279. vertexIndex = lonIndex + latIndex * numLonVertices;
  1280. indices[index] = vertexIndex;
  1281. index += 1
  1282. }
  1283. // Top row, starting on the right and going to the left.
  1284. latIndex = numLatVertices - 1;
  1285. for (lonIndex = numLonVertices - 1; lonIndex >= 0; lonIndex -= 1) {
  1286. vertexIndex = lonIndex + latIndex * numLonVertices;
  1287. indices[index] = vertexIndex;
  1288. index += 1
  1289. }
  1290. // Leftmost column, starting at the top and going down.
  1291. lonIndex = 0;
  1292. for (latIndex = numLatVertices - 1; latIndex >= 0; latIndex -= 1) {
  1293. vertexIndex = lonIndex + latIndex * numLonVertices;
  1294. indices[index] = vertexIndex;
  1295. index += 1
  1296. }
  1297. this.numOutlineIndices = numIndices;
  1298. return indices;
  1299. };
  1300. Tessellator.prototype.cacheSharedGeometryVBOs = function (dc) {
  1301. var gl = dc.currentGlContext,
  1302. gpuResourceCache = dc.gpuResourceCache;
  1303. var texCoordVbo = gpuResourceCache.resourceForKey(this.texCoordVboCacheKey);
  1304. if (!texCoordVbo) {
  1305. texCoordVbo = gl.createBuffer();
  1306. gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVbo);
  1307. gl.bufferData(gl.ARRAY_BUFFER, this.texCoords, gl.STATIC_DRAW);
  1308. dc.frameStatistics.incrementVboLoadCount(1);
  1309. gpuResourceCache.putResource(this.texCoordVboCacheKey, texCoordVbo, this.texCoords.length * 4 / 2);
  1310. }
  1311. var indicesVbo = gpuResourceCache.resourceForKey(this.indicesVboCacheKey);
  1312. if (!indicesVbo) {
  1313. indicesVbo = gl.createBuffer();
  1314. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesVbo);
  1315. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
  1316. dc.frameStatistics.incrementVboLoadCount(1);
  1317. gpuResourceCache.putResource(this.indicesVboCacheKey, indicesVbo, this.indices.length * 2);
  1318. }
  1319. };
  1320. return Tessellator;
  1321. });