TerrainProvider.js 14 KB

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