QuantizedMeshTerrainData.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. import BoundingSphere from "./BoundingSphere.js";
  2. import Cartesian2 from "./Cartesian2.js";
  3. import Cartesian3 from "./Cartesian3.js";
  4. import Check from "./Check.js";
  5. import defaultValue from "./defaultValue.js";
  6. import defined from "./defined.js";
  7. import DeveloperError from "./DeveloperError.js";
  8. import IndexDatatype from "./IndexDatatype.js";
  9. import Intersections2D from "./Intersections2D.js";
  10. import CesiumMath from "./Math.js";
  11. import OrientedBoundingBox from "./OrientedBoundingBox.js";
  12. import TaskProcessor from "./TaskProcessor.js";
  13. import TerrainData from "./TerrainData.js";
  14. import TerrainEncoding from "./TerrainEncoding.js";
  15. import TerrainMesh from "./TerrainMesh.js";
  16. /**
  17. * Terrain data for a single tile where the terrain data is represented as a quantized mesh. A quantized
  18. * mesh consists of three vertex attributes, longitude, latitude, and height. All attributes are expressed
  19. * as 16-bit values in the range 0 to 32767. Longitude and latitude are zero at the southwest corner
  20. * of the tile and 32767 at the northeast corner. Height is zero at the minimum height in the tile
  21. * and 32767 at the maximum height in the tile.
  22. *
  23. * @alias QuantizedMeshTerrainData
  24. * @constructor
  25. *
  26. * @param {Object} options Object with the following properties:
  27. * @param {Uint16Array} options.quantizedVertices The buffer containing the quantized mesh.
  28. * @param {Uint16Array|Uint32Array} options.indices The indices specifying how the quantized vertices are linked
  29. * together into triangles. Each three indices specifies one triangle.
  30. * @param {Number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
  31. * @param {Number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
  32. * @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
  33. * @param {OrientedBoundingBox} [options.orientedBoundingBox] An OrientedBoundingBox bounding all of the vertices in the mesh.
  34. * @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh. If this point
  35. * is below the horizon, the entire tile is assumed to be below the horizon as well.
  36. * The point is expressed in ellipsoid-scaled coordinates.
  37. * @param {Number[]} options.westIndices The indices of the vertices on the western edge of the tile.
  38. * @param {Number[]} options.southIndices The indices of the vertices on the southern edge of the tile.
  39. * @param {Number[]} options.eastIndices The indices of the vertices on the eastern edge of the tile.
  40. * @param {Number[]} options.northIndices The indices of the vertices on the northern edge of the tile.
  41. * @param {Number} options.westSkirtHeight The height of the skirt to add on the western edge of the tile.
  42. * @param {Number} options.southSkirtHeight The height of the skirt to add on the southern edge of the tile.
  43. * @param {Number} options.eastSkirtHeight The height of the skirt to add on the eastern edge of the tile.
  44. * @param {Number} options.northSkirtHeight The height of the skirt to add on the northern edge of the tile.
  45. * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
  46. * If a child's bit is set, geometry will be requested for that tile as well when it
  47. * is needed. If the bit is cleared, the child tile is not requested and geometry is
  48. * instead upsampled from the parent. The bit values are as follows:
  49. * <table>
  50. * <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
  51. * <tr><td>0</td><td>1</td><td>Southwest</td></tr>
  52. * <tr><td>1</td><td>2</td><td>Southeast</td></tr>
  53. * <tr><td>2</td><td>4</td><td>Northwest</td></tr>
  54. * <tr><td>3</td><td>8</td><td>Northeast</td></tr>
  55. * </table>
  56. * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
  57. * otherwise, false.
  58. * @param {Uint8Array} [options.encodedNormals] The buffer containing per vertex normals, encoded using 'oct' encoding
  59. * @param {Uint8Array} [options.waterMask] The buffer containing the watermask.
  60. * @param {Credit[]} [options.credits] Array of credits for this tile.
  61. *
  62. *
  63. * @example
  64. * const data = new Cesium.QuantizedMeshTerrainData({
  65. * minimumHeight : -100,
  66. * maximumHeight : 2101,
  67. * quantizedVertices : new Uint16Array([// order is SW NW SE NE
  68. * // longitude
  69. * 0, 0, 32767, 32767,
  70. * // latitude
  71. * 0, 32767, 0, 32767,
  72. * // heights
  73. * 16384, 0, 32767, 16384]),
  74. * indices : new Uint16Array([0, 3, 1,
  75. * 0, 2, 3]),
  76. * boundingSphere : new Cesium.BoundingSphere(new Cesium.Cartesian3(1.0, 2.0, 3.0), 10000),
  77. * orientedBoundingBox : new Cesium.OrientedBoundingBox(new Cesium.Cartesian3(1.0, 2.0, 3.0), Cesium.Matrix3.fromRotationX(Cesium.Math.PI, new Cesium.Matrix3())),
  78. * horizonOcclusionPoint : new Cesium.Cartesian3(3.0, 2.0, 1.0),
  79. * westIndices : [0, 1],
  80. * southIndices : [0, 1],
  81. * eastIndices : [2, 3],
  82. * northIndices : [1, 3],
  83. * westSkirtHeight : 1.0,
  84. * southSkirtHeight : 1.0,
  85. * eastSkirtHeight : 1.0,
  86. * northSkirtHeight : 1.0
  87. * });
  88. *
  89. * @see TerrainData
  90. * @see HeightmapTerrainData
  91. * @see GoogleEarthEnterpriseTerrainData
  92. */
  93. function QuantizedMeshTerrainData(options) {
  94. //>>includeStart('debug', pragmas.debug)
  95. if (!defined(options) || !defined(options.quantizedVertices)) {
  96. throw new DeveloperError("options.quantizedVertices is required.");
  97. }
  98. if (!defined(options.indices)) {
  99. throw new DeveloperError("options.indices is required.");
  100. }
  101. if (!defined(options.minimumHeight)) {
  102. throw new DeveloperError("options.minimumHeight is required.");
  103. }
  104. if (!defined(options.maximumHeight)) {
  105. throw new DeveloperError("options.maximumHeight is required.");
  106. }
  107. if (!defined(options.maximumHeight)) {
  108. throw new DeveloperError("options.maximumHeight is required.");
  109. }
  110. if (!defined(options.boundingSphere)) {
  111. throw new DeveloperError("options.boundingSphere is required.");
  112. }
  113. if (!defined(options.horizonOcclusionPoint)) {
  114. throw new DeveloperError("options.horizonOcclusionPoint is required.");
  115. }
  116. if (!defined(options.westIndices)) {
  117. throw new DeveloperError("options.westIndices is required.");
  118. }
  119. if (!defined(options.southIndices)) {
  120. throw new DeveloperError("options.southIndices is required.");
  121. }
  122. if (!defined(options.eastIndices)) {
  123. throw new DeveloperError("options.eastIndices is required.");
  124. }
  125. if (!defined(options.northIndices)) {
  126. throw new DeveloperError("options.northIndices is required.");
  127. }
  128. if (!defined(options.westSkirtHeight)) {
  129. throw new DeveloperError("options.westSkirtHeight is required.");
  130. }
  131. if (!defined(options.southSkirtHeight)) {
  132. throw new DeveloperError("options.southSkirtHeight is required.");
  133. }
  134. if (!defined(options.eastSkirtHeight)) {
  135. throw new DeveloperError("options.eastSkirtHeight is required.");
  136. }
  137. if (!defined(options.northSkirtHeight)) {
  138. throw new DeveloperError("options.northSkirtHeight is required.");
  139. }
  140. //>>includeEnd('debug');
  141. this._quantizedVertices = options.quantizedVertices;
  142. this._encodedNormals = options.encodedNormals;
  143. this._indices = options.indices;
  144. this._minimumHeight = options.minimumHeight;
  145. this._maximumHeight = options.maximumHeight;
  146. this._boundingSphere = options.boundingSphere;
  147. this._orientedBoundingBox = options.orientedBoundingBox;
  148. this._horizonOcclusionPoint = options.horizonOcclusionPoint;
  149. this._credits = options.credits;
  150. const vertexCount = this._quantizedVertices.length / 3;
  151. const uValues = (this._uValues = this._quantizedVertices.subarray(
  152. 0,
  153. vertexCount
  154. ));
  155. const vValues = (this._vValues = this._quantizedVertices.subarray(
  156. vertexCount,
  157. 2 * vertexCount
  158. ));
  159. this._heightValues = this._quantizedVertices.subarray(
  160. 2 * vertexCount,
  161. 3 * vertexCount
  162. );
  163. // We don't assume that we can count on the edge vertices being sorted by u or v.
  164. function sortByV(a, b) {
  165. return vValues[a] - vValues[b];
  166. }
  167. function sortByU(a, b) {
  168. return uValues[a] - uValues[b];
  169. }
  170. this._westIndices = sortIndicesIfNecessary(
  171. options.westIndices,
  172. sortByV,
  173. vertexCount
  174. );
  175. this._southIndices = sortIndicesIfNecessary(
  176. options.southIndices,
  177. sortByU,
  178. vertexCount
  179. );
  180. this._eastIndices = sortIndicesIfNecessary(
  181. options.eastIndices,
  182. sortByV,
  183. vertexCount
  184. );
  185. this._northIndices = sortIndicesIfNecessary(
  186. options.northIndices,
  187. sortByU,
  188. vertexCount
  189. );
  190. this._westSkirtHeight = options.westSkirtHeight;
  191. this._southSkirtHeight = options.southSkirtHeight;
  192. this._eastSkirtHeight = options.eastSkirtHeight;
  193. this._northSkirtHeight = options.northSkirtHeight;
  194. this._childTileMask = defaultValue(options.childTileMask, 15);
  195. this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
  196. this._waterMask = options.waterMask;
  197. this._mesh = undefined;
  198. }
  199. Object.defineProperties(QuantizedMeshTerrainData.prototype, {
  200. /**
  201. * An array of credits for this tile.
  202. * @memberof QuantizedMeshTerrainData.prototype
  203. * @type {Credit[]}
  204. */
  205. credits: {
  206. get: function () {
  207. return this._credits;
  208. },
  209. },
  210. /**
  211. * The water mask included in this terrain data, if any. A water mask is a rectangular
  212. * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
  213. * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
  214. * @memberof QuantizedMeshTerrainData.prototype
  215. * @type {Uint8Array|HTMLImageElement|HTMLCanvasElement}
  216. */
  217. waterMask: {
  218. get: function () {
  219. return this._waterMask;
  220. },
  221. },
  222. childTileMask: {
  223. get: function () {
  224. return this._childTileMask;
  225. },
  226. },
  227. canUpsample: {
  228. get: function () {
  229. return defined(this._mesh);
  230. },
  231. },
  232. });
  233. const arrayScratch = [];
  234. function sortIndicesIfNecessary(indices, sortFunction, vertexCount) {
  235. arrayScratch.length = indices.length;
  236. let needsSort = false;
  237. for (let i = 0, len = indices.length; i < len; ++i) {
  238. arrayScratch[i] = indices[i];
  239. needsSort =
  240. needsSort || (i > 0 && sortFunction(indices[i - 1], indices[i]) > 0);
  241. }
  242. if (needsSort) {
  243. arrayScratch.sort(sortFunction);
  244. return IndexDatatype.createTypedArray(vertexCount, arrayScratch);
  245. }
  246. return indices;
  247. }
  248. const createMeshTaskName = "createVerticesFromQuantizedTerrainMesh";
  249. const createMeshTaskProcessorNoThrottle = new TaskProcessor(createMeshTaskName);
  250. const createMeshTaskProcessorThrottle = new TaskProcessor(
  251. createMeshTaskName,
  252. TerrainData.maximumAsynchronousTasks
  253. );
  254. /**
  255. * Creates a {@link TerrainMesh} from this terrain data.
  256. *
  257. * @private
  258. *
  259. * @param {Object} options Object with the following properties:
  260. * @param {TilingScheme} options.tilingScheme The tiling scheme to which this tile belongs.
  261. * @param {Number} options.x The X coordinate of the tile for which to create the terrain data.
  262. * @param {Number} options.y The Y coordinate of the tile for which to create the terrain data.
  263. * @param {Number} options.level The level of the tile for which to create the terrain data.
  264. * @param {Number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
  265. * @param {Number} [options.exaggerationRelativeHeight=0.0] The height relative to which terrain is exaggerated.
  266. * @param {Boolean} [options.throttle=true] If true, indicates that this operation will need to be retried if too many asynchronous mesh creations are already in progress.
  267. * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
  268. * asynchronous mesh creations are already in progress and the operation should
  269. * be retried later.
  270. */
  271. QuantizedMeshTerrainData.prototype.createMesh = function (options) {
  272. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  273. //>>includeStart('debug', pragmas.debug);
  274. Check.typeOf.object("options.tilingScheme", options.tilingScheme);
  275. Check.typeOf.number("options.x", options.x);
  276. Check.typeOf.number("options.y", options.y);
  277. Check.typeOf.number("options.level", options.level);
  278. //>>includeEnd('debug');
  279. const tilingScheme = options.tilingScheme;
  280. const x = options.x;
  281. const y = options.y;
  282. const level = options.level;
  283. const exaggeration = defaultValue(options.exaggeration, 1.0);
  284. const exaggerationRelativeHeight = defaultValue(
  285. options.exaggerationRelativeHeight,
  286. 0.0
  287. );
  288. const throttle = defaultValue(options.throttle, true);
  289. const ellipsoid = tilingScheme.ellipsoid;
  290. const rectangle = tilingScheme.tileXYToRectangle(x, y, level);
  291. const createMeshTaskProcessor = throttle
  292. ? createMeshTaskProcessorThrottle
  293. : createMeshTaskProcessorNoThrottle;
  294. const verticesPromise = createMeshTaskProcessor.scheduleTask({
  295. minimumHeight: this._minimumHeight,
  296. maximumHeight: this._maximumHeight,
  297. quantizedVertices: this._quantizedVertices,
  298. octEncodedNormals: this._encodedNormals,
  299. includeWebMercatorT: true,
  300. indices: this._indices,
  301. westIndices: this._westIndices,
  302. southIndices: this._southIndices,
  303. eastIndices: this._eastIndices,
  304. northIndices: this._northIndices,
  305. westSkirtHeight: this._westSkirtHeight,
  306. southSkirtHeight: this._southSkirtHeight,
  307. eastSkirtHeight: this._eastSkirtHeight,
  308. northSkirtHeight: this._northSkirtHeight,
  309. rectangle: rectangle,
  310. relativeToCenter: this._boundingSphere.center,
  311. ellipsoid: ellipsoid,
  312. exaggeration: exaggeration,
  313. exaggerationRelativeHeight: exaggerationRelativeHeight,
  314. });
  315. if (!defined(verticesPromise)) {
  316. // Postponed
  317. return undefined;
  318. }
  319. const that = this;
  320. return Promise.resolve(verticesPromise).then(function (result) {
  321. const vertexCountWithoutSkirts = that._quantizedVertices.length / 3;
  322. const vertexCount =
  323. vertexCountWithoutSkirts +
  324. that._westIndices.length +
  325. that._southIndices.length +
  326. that._eastIndices.length +
  327. that._northIndices.length;
  328. const indicesTypedArray = IndexDatatype.createTypedArray(
  329. vertexCount,
  330. result.indices
  331. );
  332. const vertices = new Float32Array(result.vertices);
  333. const rtc = result.center;
  334. const minimumHeight = result.minimumHeight;
  335. const maximumHeight = result.maximumHeight;
  336. const boundingSphere = that._boundingSphere;
  337. const obb = that._orientedBoundingBox;
  338. const occludeePointInScaledSpace = defaultValue(
  339. Cartesian3.clone(result.occludeePointInScaledSpace),
  340. that._horizonOcclusionPoint
  341. );
  342. const stride = result.vertexStride;
  343. const terrainEncoding = TerrainEncoding.clone(result.encoding);
  344. // Clone complex result objects because the transfer from the web worker
  345. // has stripped them down to JSON-style objects.
  346. that._mesh = new TerrainMesh(
  347. rtc,
  348. vertices,
  349. indicesTypedArray,
  350. result.indexCountWithoutSkirts,
  351. vertexCountWithoutSkirts,
  352. minimumHeight,
  353. maximumHeight,
  354. boundingSphere,
  355. occludeePointInScaledSpace,
  356. stride,
  357. obb,
  358. terrainEncoding,
  359. result.westIndicesSouthToNorth,
  360. result.southIndicesEastToWest,
  361. result.eastIndicesNorthToSouth,
  362. result.northIndicesWestToEast
  363. );
  364. // Free memory received from server after mesh is created.
  365. that._quantizedVertices = undefined;
  366. that._encodedNormals = undefined;
  367. that._indices = undefined;
  368. that._uValues = undefined;
  369. that._vValues = undefined;
  370. that._heightValues = undefined;
  371. that._westIndices = undefined;
  372. that._southIndices = undefined;
  373. that._eastIndices = undefined;
  374. that._northIndices = undefined;
  375. return that._mesh;
  376. });
  377. };
  378. const upsampleTaskProcessor = new TaskProcessor(
  379. "upsampleQuantizedTerrainMesh",
  380. TerrainData.maximumAsynchronousTasks
  381. );
  382. /**
  383. * Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
  384. * vertices in this instance, interpolated if necessary.
  385. *
  386. * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
  387. * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
  388. * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
  389. * @param {Number} thisLevel The level of this tile in the tiling scheme.
  390. * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  391. * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  392. * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
  393. * @returns {Promise.<QuantizedMeshTerrainData>|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
  394. * or undefined if too many asynchronous upsample operations are in progress and the request has been
  395. * deferred.
  396. */
  397. QuantizedMeshTerrainData.prototype.upsample = function (
  398. tilingScheme,
  399. thisX,
  400. thisY,
  401. thisLevel,
  402. descendantX,
  403. descendantY,
  404. descendantLevel
  405. ) {
  406. //>>includeStart('debug', pragmas.debug);
  407. if (!defined(tilingScheme)) {
  408. throw new DeveloperError("tilingScheme is required.");
  409. }
  410. if (!defined(thisX)) {
  411. throw new DeveloperError("thisX is required.");
  412. }
  413. if (!defined(thisY)) {
  414. throw new DeveloperError("thisY is required.");
  415. }
  416. if (!defined(thisLevel)) {
  417. throw new DeveloperError("thisLevel is required.");
  418. }
  419. if (!defined(descendantX)) {
  420. throw new DeveloperError("descendantX is required.");
  421. }
  422. if (!defined(descendantY)) {
  423. throw new DeveloperError("descendantY is required.");
  424. }
  425. if (!defined(descendantLevel)) {
  426. throw new DeveloperError("descendantLevel is required.");
  427. }
  428. const levelDifference = descendantLevel - thisLevel;
  429. if (levelDifference > 1) {
  430. throw new DeveloperError(
  431. "Upsampling through more than one level at a time is not currently supported."
  432. );
  433. }
  434. //>>includeEnd('debug');
  435. const mesh = this._mesh;
  436. if (!defined(this._mesh)) {
  437. return undefined;
  438. }
  439. const isEastChild = thisX * 2 !== descendantX;
  440. const isNorthChild = thisY * 2 === descendantY;
  441. const ellipsoid = tilingScheme.ellipsoid;
  442. const childRectangle = tilingScheme.tileXYToRectangle(
  443. descendantX,
  444. descendantY,
  445. descendantLevel
  446. );
  447. const upsamplePromise = upsampleTaskProcessor.scheduleTask({
  448. vertices: mesh.vertices,
  449. vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
  450. indices: mesh.indices,
  451. indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
  452. encoding: mesh.encoding,
  453. minimumHeight: this._minimumHeight,
  454. maximumHeight: this._maximumHeight,
  455. isEastChild: isEastChild,
  456. isNorthChild: isNorthChild,
  457. childRectangle: childRectangle,
  458. ellipsoid: ellipsoid,
  459. });
  460. if (!defined(upsamplePromise)) {
  461. // Postponed
  462. return undefined;
  463. }
  464. let shortestSkirt = Math.min(this._westSkirtHeight, this._eastSkirtHeight);
  465. shortestSkirt = Math.min(shortestSkirt, this._southSkirtHeight);
  466. shortestSkirt = Math.min(shortestSkirt, this._northSkirtHeight);
  467. const westSkirtHeight = isEastChild
  468. ? shortestSkirt * 0.5
  469. : this._westSkirtHeight;
  470. const southSkirtHeight = isNorthChild
  471. ? shortestSkirt * 0.5
  472. : this._southSkirtHeight;
  473. const eastSkirtHeight = isEastChild
  474. ? this._eastSkirtHeight
  475. : shortestSkirt * 0.5;
  476. const northSkirtHeight = isNorthChild
  477. ? this._northSkirtHeight
  478. : shortestSkirt * 0.5;
  479. const credits = this._credits;
  480. return Promise.resolve(upsamplePromise).then(function (result) {
  481. const quantizedVertices = new Uint16Array(result.vertices);
  482. const indicesTypedArray = IndexDatatype.createTypedArray(
  483. quantizedVertices.length / 3,
  484. result.indices
  485. );
  486. let encodedNormals;
  487. if (defined(result.encodedNormals)) {
  488. encodedNormals = new Uint8Array(result.encodedNormals);
  489. }
  490. return new QuantizedMeshTerrainData({
  491. quantizedVertices: quantizedVertices,
  492. indices: indicesTypedArray,
  493. encodedNormals: encodedNormals,
  494. minimumHeight: result.minimumHeight,
  495. maximumHeight: result.maximumHeight,
  496. boundingSphere: BoundingSphere.clone(result.boundingSphere),
  497. orientedBoundingBox: OrientedBoundingBox.clone(
  498. result.orientedBoundingBox
  499. ),
  500. horizonOcclusionPoint: Cartesian3.clone(result.horizonOcclusionPoint),
  501. westIndices: result.westIndices,
  502. southIndices: result.southIndices,
  503. eastIndices: result.eastIndices,
  504. northIndices: result.northIndices,
  505. westSkirtHeight: westSkirtHeight,
  506. southSkirtHeight: southSkirtHeight,
  507. eastSkirtHeight: eastSkirtHeight,
  508. northSkirtHeight: northSkirtHeight,
  509. childTileMask: 0,
  510. credits: credits,
  511. createdByUpsampling: true,
  512. });
  513. });
  514. };
  515. const maxShort = 32767;
  516. const barycentricCoordinateScratch = new Cartesian3();
  517. /**
  518. * Computes the terrain height at a specified longitude and latitude.
  519. *
  520. * @param {Rectangle} rectangle The rectangle covered by this terrain data.
  521. * @param {Number} longitude The longitude in radians.
  522. * @param {Number} latitude The latitude in radians.
  523. * @returns {Number} The terrain height at the specified position. The position is clamped to
  524. * the rectangle, so expect incorrect results for positions far outside the rectangle.
  525. */
  526. QuantizedMeshTerrainData.prototype.interpolateHeight = function (
  527. rectangle,
  528. longitude,
  529. latitude
  530. ) {
  531. let u = CesiumMath.clamp(
  532. (longitude - rectangle.west) / rectangle.width,
  533. 0.0,
  534. 1.0
  535. );
  536. u *= maxShort;
  537. let v = CesiumMath.clamp(
  538. (latitude - rectangle.south) / rectangle.height,
  539. 0.0,
  540. 1.0
  541. );
  542. v *= maxShort;
  543. if (!defined(this._mesh)) {
  544. return interpolateHeight(this, u, v);
  545. }
  546. return interpolateMeshHeight(this, u, v);
  547. };
  548. function pointInBoundingBox(u, v, u0, v0, u1, v1, u2, v2) {
  549. const minU = Math.min(u0, u1, u2);
  550. const maxU = Math.max(u0, u1, u2);
  551. const minV = Math.min(v0, v1, v2);
  552. const maxV = Math.max(v0, v1, v2);
  553. return u >= minU && u <= maxU && v >= minV && v <= maxV;
  554. }
  555. const texCoordScratch0 = new Cartesian2();
  556. const texCoordScratch1 = new Cartesian2();
  557. const texCoordScratch2 = new Cartesian2();
  558. function interpolateMeshHeight(terrainData, u, v) {
  559. const mesh = terrainData._mesh;
  560. const vertices = mesh.vertices;
  561. const encoding = mesh.encoding;
  562. const indices = mesh.indices;
  563. for (let i = 0, len = indices.length; i < len; i += 3) {
  564. const i0 = indices[i];
  565. const i1 = indices[i + 1];
  566. const i2 = indices[i + 2];
  567. const uv0 = encoding.decodeTextureCoordinates(
  568. vertices,
  569. i0,
  570. texCoordScratch0
  571. );
  572. const uv1 = encoding.decodeTextureCoordinates(
  573. vertices,
  574. i1,
  575. texCoordScratch1
  576. );
  577. const uv2 = encoding.decodeTextureCoordinates(
  578. vertices,
  579. i2,
  580. texCoordScratch2
  581. );
  582. if (pointInBoundingBox(u, v, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y)) {
  583. const barycentric = Intersections2D.computeBarycentricCoordinates(
  584. u,
  585. v,
  586. uv0.x,
  587. uv0.y,
  588. uv1.x,
  589. uv1.y,
  590. uv2.x,
  591. uv2.y,
  592. barycentricCoordinateScratch
  593. );
  594. if (
  595. barycentric.x >= -1e-15 &&
  596. barycentric.y >= -1e-15 &&
  597. barycentric.z >= -1e-15
  598. ) {
  599. const h0 = encoding.decodeHeight(vertices, i0);
  600. const h1 = encoding.decodeHeight(vertices, i1);
  601. const h2 = encoding.decodeHeight(vertices, i2);
  602. return barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
  603. }
  604. }
  605. }
  606. // Position does not lie in any triangle in this mesh.
  607. return undefined;
  608. }
  609. function interpolateHeight(terrainData, u, v) {
  610. const uBuffer = terrainData._uValues;
  611. const vBuffer = terrainData._vValues;
  612. const heightBuffer = terrainData._heightValues;
  613. const indices = terrainData._indices;
  614. for (let i = 0, len = indices.length; i < len; i += 3) {
  615. const i0 = indices[i];
  616. const i1 = indices[i + 1];
  617. const i2 = indices[i + 2];
  618. const u0 = uBuffer[i0];
  619. const u1 = uBuffer[i1];
  620. const u2 = uBuffer[i2];
  621. const v0 = vBuffer[i0];
  622. const v1 = vBuffer[i1];
  623. const v2 = vBuffer[i2];
  624. if (pointInBoundingBox(u, v, u0, v0, u1, v1, u2, v2)) {
  625. const barycentric = Intersections2D.computeBarycentricCoordinates(
  626. u,
  627. v,
  628. u0,
  629. v0,
  630. u1,
  631. v1,
  632. u2,
  633. v2,
  634. barycentricCoordinateScratch
  635. );
  636. if (
  637. barycentric.x >= -1e-15 &&
  638. barycentric.y >= -1e-15 &&
  639. barycentric.z >= -1e-15
  640. ) {
  641. const quantizedHeight =
  642. barycentric.x * heightBuffer[i0] +
  643. barycentric.y * heightBuffer[i1] +
  644. barycentric.z * heightBuffer[i2];
  645. return CesiumMath.lerp(
  646. terrainData._minimumHeight,
  647. terrainData._maximumHeight,
  648. quantizedHeight / maxShort
  649. );
  650. }
  651. }
  652. }
  653. // Position does not lie in any triangle in this mesh.
  654. return undefined;
  655. }
  656. /**
  657. * Determines if a given child tile is available, based on the
  658. * {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed
  659. * to be one of the four children of this tile. If non-child tile coordinates are
  660. * given, the availability of the southeast child tile is returned.
  661. *
  662. * @param {Number} thisX The tile X coordinate of this (the parent) tile.
  663. * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
  664. * @param {Number} childX The tile X coordinate of the child tile to check for availability.
  665. * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
  666. * @returns {Boolean} True if the child tile is available; otherwise, false.
  667. */
  668. QuantizedMeshTerrainData.prototype.isChildAvailable = function (
  669. thisX,
  670. thisY,
  671. childX,
  672. childY
  673. ) {
  674. //>>includeStart('debug', pragmas.debug);
  675. if (!defined(thisX)) {
  676. throw new DeveloperError("thisX is required.");
  677. }
  678. if (!defined(thisY)) {
  679. throw new DeveloperError("thisY is required.");
  680. }
  681. if (!defined(childX)) {
  682. throw new DeveloperError("childX is required.");
  683. }
  684. if (!defined(childY)) {
  685. throw new DeveloperError("childY is required.");
  686. }
  687. //>>includeEnd('debug');
  688. let bitNumber = 2; // northwest child
  689. if (childX !== thisX * 2) {
  690. ++bitNumber; // east child
  691. }
  692. if (childY !== thisY * 2) {
  693. bitNumber -= 2; // south child
  694. }
  695. return (this._childTileMask & (1 << bitNumber)) !== 0;
  696. };
  697. /**
  698. * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
  699. * terrain data. If this value is false, the data was obtained from some other source, such
  700. * as by downloading it from a remote server. This method should return true for instances
  701. * returned from a call to {@link HeightmapTerrainData#upsample}.
  702. *
  703. * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
  704. */
  705. QuantizedMeshTerrainData.prototype.wasCreatedByUpsampling = function () {
  706. return this._createdByUpsampling;
  707. };
  708. export default QuantizedMeshTerrainData;