TerrainProvider.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import defined from "./defined.js";
  2. import DeveloperError from "./DeveloperError.js";
  3. import IndexDatatype from "./IndexDatatype.js";
  4. import CesiumMath from "./Math.js";
  5. /**
  6. * Provides terrain or other geometry for the surface of an ellipsoid. The surface geometry is
  7. * organized into a pyramid of tiles according to a {@link TilingScheme}. This type describes an
  8. * interface and is not intended to be instantiated directly.
  9. *
  10. * @alias TerrainProvider
  11. * @constructor
  12. *
  13. * @see EllipsoidTerrainProvider
  14. * @see CesiumTerrainProvider
  15. * @see VRTheWorldTerrainProvider
  16. * @see GoogleEarthEnterpriseTerrainProvider
  17. */
  18. function TerrainProvider() {
  19. DeveloperError.throwInstantiationError();
  20. }
  21. Object.defineProperties(TerrainProvider.prototype, {
  22. /**
  23. * Gets an event that is raised when the terrain provider encounters an asynchronous error.. By subscribing
  24. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  25. * are passed an instance of {@link TileProviderError}.
  26. * @memberof TerrainProvider.prototype
  27. * @type {Event}
  28. */
  29. errorEvent: {
  30. get: DeveloperError.throwInstantiationError,
  31. },
  32. /**
  33. * Gets the credit to display when this terrain provider is active. Typically this is used to credit
  34. * the source of the terrain. This function should
  35. * not be called before {@link TerrainProvider#ready} returns true.
  36. * @memberof TerrainProvider.prototype
  37. * @type {Credit}
  38. */
  39. credit: {
  40. get: DeveloperError.throwInstantiationError,
  41. },
  42. /**
  43. * Gets the tiling scheme used by the provider. This function should
  44. * not be called before {@link TerrainProvider#ready} returns true.
  45. * @memberof TerrainProvider.prototype
  46. * @type {TilingScheme}
  47. */
  48. tilingScheme: {
  49. get: DeveloperError.throwInstantiationError,
  50. },
  51. /**
  52. * Gets a value indicating whether or not the provider is ready for use.
  53. * @memberof TerrainProvider.prototype
  54. * @type {Boolean}
  55. */
  56. ready: {
  57. get: DeveloperError.throwInstantiationError,
  58. },
  59. /**
  60. * Gets a promise that resolves to true when the provider is ready for use.
  61. * @memberof TerrainProvider.prototype
  62. * @type {Promise.<Boolean>}
  63. * @readonly
  64. */
  65. readyPromise: {
  66. get: DeveloperError.throwInstantiationError,
  67. },
  68. /**
  69. * Gets a value indicating whether or not the provider includes a water mask. The water mask
  70. * indicates which areas of the globe are water rather than land, so they can be rendered
  71. * as a reflective surface with animated waves. This function should not be
  72. * called before {@link TerrainProvider#ready} returns true.
  73. * @memberof TerrainProvider.prototype
  74. * @type {Boolean}
  75. */
  76. hasWaterMask: {
  77. get: DeveloperError.throwInstantiationError,
  78. },
  79. /**
  80. * Gets a value indicating whether or not the requested tiles include vertex normals.
  81. * This function should not be called before {@link TerrainProvider#ready} returns true.
  82. * @memberof TerrainProvider.prototype
  83. * @type {Boolean}
  84. */
  85. hasVertexNormals: {
  86. get: DeveloperError.throwInstantiationError,
  87. },
  88. /**
  89. * Gets an object that can be used to determine availability of terrain from this provider, such as
  90. * at points and in rectangles. This function should not be called before
  91. * {@link TerrainProvider#ready} returns true. This property may be undefined if availability
  92. * information is not available.
  93. * @memberof TerrainProvider.prototype
  94. * @type {TileAvailability}
  95. */
  96. availability: {
  97. get: DeveloperError.throwInstantiationError,
  98. },
  99. });
  100. var regularGridIndicesCache = [];
  101. /**
  102. * Gets a list of indices for a triangle mesh representing a regular grid. Calling
  103. * this function multiple times with the same grid width and height returns the
  104. * same list of indices. The total number of vertices must be less than or equal
  105. * to 65536.
  106. *
  107. * @param {Number} width The number of vertices in the regular grid in the horizontal direction.
  108. * @param {Number} height The number of vertices in the regular grid in the vertical direction.
  109. * @returns {Uint16Array|Uint32Array} The list of indices. Uint16Array gets returned for 64KB or less and Uint32Array for 4GB or less.
  110. */
  111. TerrainProvider.getRegularGridIndices = function (width, height) {
  112. //>>includeStart('debug', pragmas.debug);
  113. if (width * height >= CesiumMath.FOUR_GIGABYTES) {
  114. throw new DeveloperError(
  115. "The total number of vertices (width * height) must be less than 4,294,967,296."
  116. );
  117. }
  118. //>>includeEnd('debug');
  119. var byWidth = regularGridIndicesCache[width];
  120. if (!defined(byWidth)) {
  121. regularGridIndicesCache[width] = byWidth = [];
  122. }
  123. var indices = byWidth[height];
  124. if (!defined(indices)) {
  125. if (width * height < CesiumMath.SIXTY_FOUR_KILOBYTES) {
  126. indices = byWidth[height] = new Uint16Array(
  127. (width - 1) * (height - 1) * 6
  128. );
  129. } else {
  130. indices = byWidth[height] = new Uint32Array(
  131. (width - 1) * (height - 1) * 6
  132. );
  133. }
  134. addRegularGridIndices(width, height, indices, 0);
  135. }
  136. return indices;
  137. };
  138. var regularGridAndEdgeIndicesCache = [];
  139. /**
  140. * @private
  141. */
  142. TerrainProvider.getRegularGridIndicesAndEdgeIndices = function (width, height) {
  143. //>>includeStart('debug', pragmas.debug);
  144. if (width * height >= CesiumMath.FOUR_GIGABYTES) {
  145. throw new DeveloperError(
  146. "The total number of vertices (width * height) must be less than 4,294,967,296."
  147. );
  148. }
  149. //>>includeEnd('debug');
  150. var byWidth = regularGridAndEdgeIndicesCache[width];
  151. if (!defined(byWidth)) {
  152. regularGridAndEdgeIndicesCache[width] = byWidth = [];
  153. }
  154. var indicesAndEdges = byWidth[height];
  155. if (!defined(indicesAndEdges)) {
  156. var indices = TerrainProvider.getRegularGridIndices(width, height);
  157. var edgeIndices = getEdgeIndices(width, height);
  158. var westIndicesSouthToNorth = edgeIndices.westIndicesSouthToNorth;
  159. var southIndicesEastToWest = edgeIndices.southIndicesEastToWest;
  160. var eastIndicesNorthToSouth = edgeIndices.eastIndicesNorthToSouth;
  161. var northIndicesWestToEast = edgeIndices.northIndicesWestToEast;
  162. indicesAndEdges = byWidth[height] = {
  163. indices: indices,
  164. westIndicesSouthToNorth: westIndicesSouthToNorth,
  165. southIndicesEastToWest: southIndicesEastToWest,
  166. eastIndicesNorthToSouth: eastIndicesNorthToSouth,
  167. northIndicesWestToEast: northIndicesWestToEast,
  168. };
  169. }
  170. return indicesAndEdges;
  171. };
  172. var regularGridAndSkirtAndEdgeIndicesCache = [];
  173. /**
  174. * @private
  175. */
  176. TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices = function (
  177. width,
  178. height
  179. ) {
  180. //>>includeStart('debug', pragmas.debug);
  181. if (width * height >= CesiumMath.FOUR_GIGABYTES) {
  182. throw new DeveloperError(
  183. "The total number of vertices (width * height) must be less than 4,294,967,296."
  184. );
  185. }
  186. //>>includeEnd('debug');
  187. var byWidth = regularGridAndSkirtAndEdgeIndicesCache[width];
  188. if (!defined(byWidth)) {
  189. regularGridAndSkirtAndEdgeIndicesCache[width] = byWidth = [];
  190. }
  191. var indicesAndEdges = byWidth[height];
  192. if (!defined(indicesAndEdges)) {
  193. var gridVertexCount = width * height;
  194. var gridIndexCount = (width - 1) * (height - 1) * 6;
  195. var edgeVertexCount = width * 2 + height * 2;
  196. var edgeIndexCount = Math.max(0, edgeVertexCount - 4) * 6;
  197. var vertexCount = gridVertexCount + edgeVertexCount;
  198. var indexCount = gridIndexCount + edgeIndexCount;
  199. var edgeIndices = getEdgeIndices(width, height);
  200. var westIndicesSouthToNorth = edgeIndices.westIndicesSouthToNorth;
  201. var southIndicesEastToWest = edgeIndices.southIndicesEastToWest;
  202. var eastIndicesNorthToSouth = edgeIndices.eastIndicesNorthToSouth;
  203. var northIndicesWestToEast = edgeIndices.northIndicesWestToEast;
  204. var indices = IndexDatatype.createTypedArray(vertexCount, indexCount);
  205. addRegularGridIndices(width, height, indices, 0);
  206. TerrainProvider.addSkirtIndices(
  207. westIndicesSouthToNorth,
  208. southIndicesEastToWest,
  209. eastIndicesNorthToSouth,
  210. northIndicesWestToEast,
  211. gridVertexCount,
  212. indices,
  213. gridIndexCount
  214. );
  215. indicesAndEdges = byWidth[height] = {
  216. indices: indices,
  217. westIndicesSouthToNorth: westIndicesSouthToNorth,
  218. southIndicesEastToWest: southIndicesEastToWest,
  219. eastIndicesNorthToSouth: eastIndicesNorthToSouth,
  220. northIndicesWestToEast: northIndicesWestToEast,
  221. indexCountWithoutSkirts: gridIndexCount,
  222. };
  223. }
  224. return indicesAndEdges;
  225. };
  226. /**
  227. * @private
  228. */
  229. TerrainProvider.addSkirtIndices = function (
  230. westIndicesSouthToNorth,
  231. southIndicesEastToWest,
  232. eastIndicesNorthToSouth,
  233. northIndicesWestToEast,
  234. vertexCount,
  235. indices,
  236. offset
  237. ) {
  238. var vertexIndex = vertexCount;
  239. offset = addSkirtIndices(
  240. westIndicesSouthToNorth,
  241. vertexIndex,
  242. indices,
  243. offset
  244. );
  245. vertexIndex += westIndicesSouthToNorth.length;
  246. offset = addSkirtIndices(
  247. southIndicesEastToWest,
  248. vertexIndex,
  249. indices,
  250. offset
  251. );
  252. vertexIndex += southIndicesEastToWest.length;
  253. offset = addSkirtIndices(
  254. eastIndicesNorthToSouth,
  255. vertexIndex,
  256. indices,
  257. offset
  258. );
  259. vertexIndex += eastIndicesNorthToSouth.length;
  260. addSkirtIndices(northIndicesWestToEast, vertexIndex, indices, offset);
  261. };
  262. function getEdgeIndices(width, height) {
  263. var westIndicesSouthToNorth = new Array(height);
  264. var southIndicesEastToWest = new Array(width);
  265. var eastIndicesNorthToSouth = new Array(height);
  266. var northIndicesWestToEast = new Array(width);
  267. var i;
  268. for (i = 0; i < width; ++i) {
  269. northIndicesWestToEast[i] = i;
  270. southIndicesEastToWest[i] = width * height - 1 - i;
  271. }
  272. for (i = 0; i < height; ++i) {
  273. eastIndicesNorthToSouth[i] = (i + 1) * width - 1;
  274. westIndicesSouthToNorth[i] = (height - i - 1) * width;
  275. }
  276. return {
  277. westIndicesSouthToNorth: westIndicesSouthToNorth,
  278. southIndicesEastToWest: southIndicesEastToWest,
  279. eastIndicesNorthToSouth: eastIndicesNorthToSouth,
  280. northIndicesWestToEast: northIndicesWestToEast,
  281. };
  282. }
  283. function addRegularGridIndices(width, height, indices, offset) {
  284. var index = 0;
  285. for (var j = 0; j < height - 1; ++j) {
  286. for (var i = 0; i < width - 1; ++i) {
  287. var upperLeft = index;
  288. var lowerLeft = upperLeft + width;
  289. var lowerRight = lowerLeft + 1;
  290. var upperRight = upperLeft + 1;
  291. indices[offset++] = upperLeft;
  292. indices[offset++] = lowerLeft;
  293. indices[offset++] = upperRight;
  294. indices[offset++] = upperRight;
  295. indices[offset++] = lowerLeft;
  296. indices[offset++] = lowerRight;
  297. ++index;
  298. }
  299. ++index;
  300. }
  301. }
  302. function addSkirtIndices(edgeIndices, vertexIndex, indices, offset) {
  303. var previousIndex = edgeIndices[0];
  304. var length = edgeIndices.length;
  305. for (var i = 1; i < length; ++i) {
  306. var index = edgeIndices[i];
  307. indices[offset++] = previousIndex;
  308. indices[offset++] = index;
  309. indices[offset++] = vertexIndex;
  310. indices[offset++] = vertexIndex;
  311. indices[offset++] = index;
  312. indices[offset++] = vertexIndex + 1;
  313. previousIndex = index;
  314. ++vertexIndex;
  315. }
  316. return offset;
  317. }
  318. /**
  319. * Specifies the quality of terrain created from heightmaps. A value of 1.0 will
  320. * ensure that adjacent heightmap vertices are separated by no more than
  321. * {@link Globe.maximumScreenSpaceError} screen pixels and will probably go very slowly.
  322. * A value of 0.5 will cut the estimated level zero geometric error in half, allowing twice the
  323. * screen pixels between adjacent heightmap vertices and thus rendering more quickly.
  324. * @type {Number}
  325. */
  326. TerrainProvider.heightmapTerrainQuality = 0.25;
  327. /**
  328. * Determines an appropriate geometric error estimate when the geometry comes from a heightmap.
  329. *
  330. * @param {Ellipsoid} ellipsoid The ellipsoid to which the terrain is attached.
  331. * @param {Number} tileImageWidth The width, in pixels, of the heightmap associated with a single tile.
  332. * @param {Number} numberOfTilesAtLevelZero The number of tiles in the horizontal direction at tile level zero.
  333. * @returns {Number} An estimated geometric error.
  334. */
  335. TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap = function (
  336. ellipsoid,
  337. tileImageWidth,
  338. numberOfTilesAtLevelZero
  339. ) {
  340. return (
  341. (ellipsoid.maximumRadius *
  342. 2 *
  343. Math.PI *
  344. TerrainProvider.heightmapTerrainQuality) /
  345. (tileImageWidth * numberOfTilesAtLevelZero)
  346. );
  347. };
  348. /**
  349. * Requests the geometry for a given tile. This function should not be called before
  350. * {@link TerrainProvider#ready} returns true. The result must include terrain data and
  351. * may optionally include a water mask and an indication of which child tiles are available.
  352. * @function
  353. *
  354. * @param {Number} x The X coordinate of the tile for which to request geometry.
  355. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  356. * @param {Number} level The level of the tile for which to request geometry.
  357. * @param {Request} [request] The request object. Intended for internal use only.
  358. *
  359. * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry. If this method
  360. * returns undefined instead of a promise, it is an indication that too many requests are already
  361. * pending and the request will be retried later.
  362. */
  363. TerrainProvider.prototype.requestTileGeometry =
  364. DeveloperError.throwInstantiationError;
  365. /**
  366. * Gets the maximum geometric error allowed in a tile at a given level. This function should not be
  367. * called before {@link TerrainProvider#ready} returns true.
  368. * @function
  369. *
  370. * @param {Number} level The tile level for which to get the maximum geometric error.
  371. * @returns {Number} The maximum geometric error.
  372. */
  373. TerrainProvider.prototype.getLevelMaximumGeometricError =
  374. DeveloperError.throwInstantiationError;
  375. /**
  376. * Determines whether data for a tile is available to be loaded.
  377. * @function
  378. *
  379. * @param {Number} x The X coordinate of the tile for which to request geometry.
  380. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  381. * @param {Number} level The level of the tile for which to request geometry.
  382. * @returns {Boolean} Undefined if not supported by the terrain provider, otherwise true or false.
  383. */
  384. TerrainProvider.prototype.getTileDataAvailable =
  385. DeveloperError.throwInstantiationError;
  386. /**
  387. * Makes sure we load availability data for a tile
  388. * @function
  389. *
  390. * @param {Number} x The X coordinate of the tile for which to request geometry.
  391. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  392. * @param {Number} level The level of the tile for which to request geometry.
  393. * @returns {undefined|Promise<void>} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded
  394. */
  395. TerrainProvider.prototype.loadTileDataAvailability =
  396. DeveloperError.throwInstantiationError;
  397. export default TerrainProvider;