Source: Core/HeightmapTerrainData.js

  1. /*global define*/
  2. define([
  3. '../ThirdParty/when',
  4. './defaultValue',
  5. './defined',
  6. './defineProperties',
  7. './DeveloperError',
  8. './GeographicTilingScheme',
  9. './HeightmapTessellator',
  10. './Math',
  11. './Rectangle',
  12. './TaskProcessor',
  13. './TerrainEncoding',
  14. './TerrainMesh',
  15. './TerrainProvider'
  16. ], function(
  17. when,
  18. defaultValue,
  19. defined,
  20. defineProperties,
  21. DeveloperError,
  22. GeographicTilingScheme,
  23. HeightmapTessellator,
  24. CesiumMath,
  25. Rectangle,
  26. TaskProcessor,
  27. TerrainEncoding,
  28. TerrainMesh,
  29. TerrainProvider) {
  30. 'use strict';
  31. /**
  32. * Terrain data for a single tile where the terrain data is represented as a heightmap. A heightmap
  33. * is a rectangular array of heights in row-major order from south to north and west to east.
  34. *
  35. * @alias HeightmapTerrainData
  36. * @constructor
  37. *
  38. * @param {Object} options Object with the following properties:
  39. * @param {TypedArray} options.buffer The buffer containing height data.
  40. * @param {Number} options.width The width (longitude direction) of the heightmap, in samples.
  41. * @param {Number} options.height The height (latitude direction) of the heightmap, in samples.
  42. * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
  43. * If a child's bit is set, geometry will be requested for that tile as well when it
  44. * is needed. If the bit is cleared, the child tile is not requested and geometry is
  45. * instead upsampled from the parent. The bit values are as follows:
  46. * <table>
  47. * <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
  48. * <tr><td>0</td><td>1</td><td>Southwest</td></tr>
  49. * <tr><td>1</td><td>2</td><td>Southeast</td></tr>
  50. * <tr><td>2</td><td>4</td><td>Northwest</td></tr>
  51. * <tr><td>3</td><td>8</td><td>Northeast</td></tr>
  52. * </table>
  53. * @param {Object} [options.structure] An object describing the structure of the height data.
  54. * @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
  55. * the height above the heightOffset, in meters. The heightOffset is added to the resulting
  56. * height after multiplying by the scale.
  57. * @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
  58. * height in meters. The offset is added after the height sample is multiplied by the
  59. * heightScale.
  60. * @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
  61. * sample. This is usually 1, indicating that each element is a separate height sample. If
  62. * it is greater than 1, that number of elements together form the height sample, which is
  63. * computed according to the structure.elementMultiplier and structure.isBigEndian properties.
  64. * @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
  65. * one height to the first element of the next height.
  66. * @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
  67. * stride property is greater than 1. For example, if the stride is 4 and the strideMultiplier
  68. * is 256, the height is computed as follows:
  69. * `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
  70. * This is assuming that the isBigEndian property is false. If it is true, the order of the
  71. * elements is reversed.
  72. * @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
  73. * stride property is greater than 1. If this property is false, the first element is the
  74. * low-order element. If it is true, the first element is the high-order element.
  75. * @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower
  76. * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
  77. * buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is
  78. * not specified, no minimum value is enforced.
  79. * @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher
  80. * than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
  81. * buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
  82. * than 65535. If this parameter is not specified, no maximum value is enforced.
  83. * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
  84. * otherwise, false.
  85. *
  86. *
  87. * @example
  88. * var buffer = ...
  89. * var heightBuffer = new Uint16Array(buffer, 0, that._heightmapWidth * that._heightmapWidth);
  90. * var childTileMask = new Uint8Array(buffer, heightBuffer.byteLength, 1)[0];
  91. * var waterMask = new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1);
  92. * var terrainData = new Cesium.HeightmapTerrainData({
  93. * buffer : heightBuffer,
  94. * width : 65,
  95. * height : 65,
  96. * childTileMask : childTileMask,
  97. * waterMask : waterMask
  98. * });
  99. *
  100. * @see TerrainData
  101. * @see QuantizedMeshTerrainData
  102. */
  103. function HeightmapTerrainData(options) {
  104. //>>includeStart('debug', pragmas.debug);
  105. if (!defined(options) || !defined(options.buffer)) {
  106. throw new DeveloperError('options.buffer is required.');
  107. }
  108. if (!defined(options.width)) {
  109. throw new DeveloperError('options.width is required.');
  110. }
  111. if (!defined(options.height)) {
  112. throw new DeveloperError('options.height is required.');
  113. }
  114. //>>includeEnd('debug');
  115. this._buffer = options.buffer;
  116. this._width = options.width;
  117. this._height = options.height;
  118. this._childTileMask = defaultValue(options.childTileMask, 15);
  119. var defaultStructure = HeightmapTessellator.DEFAULT_STRUCTURE;
  120. var structure = options.structure;
  121. if (!defined(structure)) {
  122. structure = defaultStructure;
  123. } else if (structure !== defaultStructure) {
  124. structure.heightScale = defaultValue(structure.heightScale, defaultStructure.heightScale);
  125. structure.heightOffset = defaultValue(structure.heightOffset, defaultStructure.heightOffset);
  126. structure.elementsPerHeight = defaultValue(structure.elementsPerHeight, defaultStructure.elementsPerHeight);
  127. structure.stride = defaultValue(structure.stride, defaultStructure.stride);
  128. structure.elementMultiplier = defaultValue(structure.elementMultiplier, defaultStructure.elementMultiplier);
  129. structure.isBigEndian = defaultValue(structure.isBigEndian, defaultStructure.isBigEndian);
  130. }
  131. this._structure = structure;
  132. this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
  133. this._waterMask = options.waterMask;
  134. this._skirtHeight = undefined;
  135. this._bufferType = this._buffer.constructor;
  136. this._mesh = undefined;
  137. }
  138. defineProperties(HeightmapTerrainData.prototype, {
  139. /**
  140. * The water mask included in this terrain data, if any. A water mask is a rectangular
  141. * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
  142. * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
  143. * @memberof HeightmapTerrainData.prototype
  144. * @type {Uint8Array|Image|Canvas}
  145. */
  146. waterMask : {
  147. get : function() {
  148. return this._waterMask;
  149. }
  150. }
  151. });
  152. var taskProcessor = new TaskProcessor('createVerticesFromHeightmap');
  153. /**
  154. * Creates a {@link TerrainMesh} from this terrain data.
  155. *
  156. * @private
  157. *
  158. * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
  159. * @param {Number} x The X coordinate of the tile for which to create the terrain data.
  160. * @param {Number} y The Y coordinate of the tile for which to create the terrain data.
  161. * @param {Number} level The level of the tile for which to create the terrain data.
  162. * @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
  163. * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
  164. * asynchronous mesh creations are already in progress and the operation should
  165. * be retried later.
  166. */
  167. HeightmapTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
  168. //>>includeStart('debug', pragmas.debug);
  169. if (!defined(tilingScheme)) {
  170. throw new DeveloperError('tilingScheme is required.');
  171. }
  172. if (!defined(x)) {
  173. throw new DeveloperError('x is required.');
  174. }
  175. if (!defined(y)) {
  176. throw new DeveloperError('y is required.');
  177. }
  178. if (!defined(level)) {
  179. throw new DeveloperError('level is required.');
  180. }
  181. //>>includeEnd('debug');
  182. var ellipsoid = tilingScheme.ellipsoid;
  183. var nativeRectangle = tilingScheme.tileXYToNativeRectangle(x, y, level);
  184. var rectangle = tilingScheme.tileXYToRectangle(x, y, level);
  185. exaggeration = defaultValue(exaggeration, 1.0);
  186. // Compute the center of the tile for RTC rendering.
  187. var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle));
  188. var structure = this._structure;
  189. var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, this._width, tilingScheme.getNumberOfXTilesAtLevel(0));
  190. var thisLevelMaxError = levelZeroMaxError / (1 << level);
  191. this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0);
  192. var verticesPromise = taskProcessor.scheduleTask({
  193. heightmap : this._buffer,
  194. structure : structure,
  195. includeWebMercatorT : true,
  196. width : this._width,
  197. height : this._height,
  198. nativeRectangle : nativeRectangle,
  199. rectangle : rectangle,
  200. relativeToCenter : center,
  201. ellipsoid : ellipsoid,
  202. skirtHeight : this._skirtHeight,
  203. isGeographic : tilingScheme instanceof GeographicTilingScheme,
  204. exaggeration : exaggeration
  205. });
  206. if (!defined(verticesPromise)) {
  207. // Postponed
  208. return undefined;
  209. }
  210. var that = this;
  211. return when(verticesPromise, function(result) {
  212. that._mesh = new TerrainMesh(
  213. center,
  214. new Float32Array(result.vertices),
  215. TerrainProvider.getRegularGridIndices(result.gridWidth, result.gridHeight),
  216. result.minimumHeight,
  217. result.maximumHeight,
  218. result.boundingSphere3D,
  219. result.occludeePointInScaledSpace,
  220. result.numberOfAttributes,
  221. result.orientedBoundingBox,
  222. TerrainEncoding.clone(result.encoding),
  223. exaggeration);
  224. // Free memory received from server after mesh is created.
  225. that._buffer = undefined;
  226. return that._mesh;
  227. });
  228. };
  229. /**
  230. * Computes the terrain height at a specified longitude and latitude.
  231. *
  232. * @param {Rectangle} rectangle The rectangle covered by this terrain data.
  233. * @param {Number} longitude The longitude in radians.
  234. * @param {Number} latitude The latitude in radians.
  235. * @returns {Number} The terrain height at the specified position. If the position
  236. * is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
  237. * incorrect for positions far outside the rectangle.
  238. */
  239. HeightmapTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
  240. var width = this._width;
  241. var height = this._height;
  242. var structure = this._structure;
  243. var stride = structure.stride;
  244. var elementsPerHeight = structure.elementsPerHeight;
  245. var elementMultiplier = structure.elementMultiplier;
  246. var isBigEndian = structure.isBigEndian;
  247. var heightOffset = structure.heightOffset;
  248. var heightScale = structure.heightScale;
  249. var heightSample;
  250. if (defined(this._mesh)) {
  251. var buffer = this._mesh.vertices;
  252. var encoding = this._mesh.encoding;
  253. var skirtHeight = this._skirtHeight;
  254. var exaggeration = this._mesh.exaggeration;
  255. heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, rectangle, width, height, longitude, latitude, exaggeration);
  256. } else {
  257. heightSample = interpolateHeight(this._buffer, elementsPerHeight, elementMultiplier, stride, isBigEndian, rectangle, width, height, longitude, latitude);
  258. heightSample = heightSample * heightScale + heightOffset;
  259. }
  260. return heightSample;
  261. };
  262. /**
  263. * Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
  264. * height samples in this instance, interpolated if necessary.
  265. *
  266. * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
  267. * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
  268. * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
  269. * @param {Number} thisLevel The level of this tile in the tiling scheme.
  270. * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  271. * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
  272. * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
  273. * @returns {Promise.<HeightmapTerrainData>|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
  274. * or undefined if too many asynchronous upsample operations are in progress and the request has been
  275. * deferred.
  276. */
  277. HeightmapTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
  278. //>>includeStart('debug', pragmas.debug);
  279. if (!defined(tilingScheme)) {
  280. throw new DeveloperError('tilingScheme is required.');
  281. }
  282. if (!defined(thisX)) {
  283. throw new DeveloperError('thisX is required.');
  284. }
  285. if (!defined(thisY)) {
  286. throw new DeveloperError('thisY is required.');
  287. }
  288. if (!defined(thisLevel)) {
  289. throw new DeveloperError('thisLevel is required.');
  290. }
  291. if (!defined(descendantX)) {
  292. throw new DeveloperError('descendantX is required.');
  293. }
  294. if (!defined(descendantY)) {
  295. throw new DeveloperError('descendantY is required.');
  296. }
  297. if (!defined(descendantLevel)) {
  298. throw new DeveloperError('descendantLevel is required.');
  299. }
  300. var levelDifference = descendantLevel - thisLevel;
  301. if (levelDifference > 1) {
  302. throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
  303. }
  304. //>>includeEnd('debug');
  305. var width = this._width;
  306. var height = this._height;
  307. var structure = this._structure;
  308. var skirtHeight = this._skirtHeight;
  309. var stride = structure.stride;
  310. var heights = new this._bufferType(width * height * stride);
  311. var meshData = this._mesh;
  312. if (!defined(meshData)) {
  313. return undefined;
  314. }
  315. var buffer = meshData.vertices;
  316. var encoding = meshData.encoding;
  317. // PERFORMANCE_IDEA: don't recompute these rectangles - the caller already knows them.
  318. var sourceRectangle = tilingScheme.tileXYToRectangle(thisX, thisY, thisLevel);
  319. var destinationRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);
  320. var heightOffset = structure.heightOffset;
  321. var heightScale = structure.heightScale;
  322. var exaggeration = meshData.exaggeration;
  323. var elementsPerHeight = structure.elementsPerHeight;
  324. var elementMultiplier = structure.elementMultiplier;
  325. var isBigEndian = structure.isBigEndian;
  326. var divisor = Math.pow(elementMultiplier, elementsPerHeight - 1);
  327. for (var j = 0; j < height; ++j) {
  328. var latitude = CesiumMath.lerp(destinationRectangle.north, destinationRectangle.south, j / (height - 1));
  329. for (var i = 0; i < width; ++i) {
  330. var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1));
  331. var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration);
  332. // Use conditionals here instead of Math.min and Math.max so that an undefined
  333. // lowestEncodedHeight or highestEncodedHeight has no effect.
  334. heightSample = heightSample < structure.lowestEncodedHeight ? structure.lowestEncodedHeight : heightSample;
  335. heightSample = heightSample > structure.highestEncodedHeight ? structure.highestEncodedHeight : heightSample;
  336. setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, j * width + i, heightSample);
  337. }
  338. }
  339. return new HeightmapTerrainData({
  340. buffer : heights,
  341. width : width,
  342. height : height,
  343. childTileMask : 0,
  344. structure : this._structure,
  345. createdByUpsampling : true
  346. });
  347. };
  348. /**
  349. * Determines if a given child tile is available, based on the
  350. * {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed
  351. * to be one of the four children of this tile. If non-child tile coordinates are
  352. * given, the availability of the southeast child tile is returned.
  353. *
  354. * @param {Number} thisX The tile X coordinate of this (the parent) tile.
  355. * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
  356. * @param {Number} childX The tile X coordinate of the child tile to check for availability.
  357. * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
  358. * @returns {Boolean} True if the child tile is available; otherwise, false.
  359. */
  360. HeightmapTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
  361. //>>includeStart('debug', pragmas.debug);
  362. if (!defined(thisX)) {
  363. throw new DeveloperError('thisX is required.');
  364. }
  365. if (!defined(thisY)) {
  366. throw new DeveloperError('thisY is required.');
  367. }
  368. if (!defined(childX)) {
  369. throw new DeveloperError('childX is required.');
  370. }
  371. if (!defined(childY)) {
  372. throw new DeveloperError('childY is required.');
  373. }
  374. //>>includeEnd('debug');
  375. var bitNumber = 2; // northwest child
  376. if (childX !== thisX * 2) {
  377. ++bitNumber; // east child
  378. }
  379. if (childY !== thisY * 2) {
  380. bitNumber -= 2; // south child
  381. }
  382. return (this._childTileMask & (1 << bitNumber)) !== 0;
  383. };
  384. /**
  385. * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
  386. * terrain data. If this value is false, the data was obtained from some other source, such
  387. * as by downloading it from a remote server. This method should return true for instances
  388. * returned from a call to {@link HeightmapTerrainData#upsample}.
  389. *
  390. * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
  391. */
  392. HeightmapTerrainData.prototype.wasCreatedByUpsampling = function() {
  393. return this._createdByUpsampling;
  394. };
  395. function interpolateHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, sourceRectangle, width, height, longitude, latitude) {
  396. var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
  397. var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);
  398. var westInteger = fromWest | 0;
  399. var eastInteger = westInteger + 1;
  400. if (eastInteger >= width) {
  401. eastInteger = width - 1;
  402. westInteger = width - 2;
  403. }
  404. var southInteger = fromSouth | 0;
  405. var northInteger = southInteger + 1;
  406. if (northInteger >= height) {
  407. northInteger = height - 1;
  408. southInteger = height - 2;
  409. }
  410. var dx = fromWest - westInteger;
  411. var dy = fromSouth - southInteger;
  412. southInteger = height - 1 - southInteger;
  413. northInteger = height - 1 - northInteger;
  414. var southwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + westInteger);
  415. var southeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + eastInteger);
  416. var northwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + westInteger);
  417. var northeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + eastInteger);
  418. return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
  419. }
  420. function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration) {
  421. // returns a height encoded according to the structure's heightScale and heightOffset.
  422. var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
  423. var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);
  424. if (skirtHeight > 0) {
  425. fromWest += 1.0;
  426. fromSouth += 1.0;
  427. width += 2;
  428. height += 2;
  429. }
  430. var widthEdge = (skirtHeight > 0) ? width - 1 : width;
  431. var westInteger = fromWest | 0;
  432. var eastInteger = westInteger + 1;
  433. if (eastInteger >= widthEdge) {
  434. eastInteger = width - 1;
  435. westInteger = width - 2;
  436. }
  437. var heightEdge = (skirtHeight > 0) ? height - 1 : height;
  438. var southInteger = fromSouth | 0;
  439. var northInteger = southInteger + 1;
  440. if (northInteger >= heightEdge) {
  441. northInteger = height - 1;
  442. southInteger = height - 2;
  443. }
  444. var dx = fromWest - westInteger;
  445. var dy = fromSouth - southInteger;
  446. southInteger = height - 1 - southInteger;
  447. northInteger = height - 1 - northInteger;
  448. var southwestHeight = (encoding.decodeHeight(buffer, southInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
  449. var southeastHeight = (encoding.decodeHeight(buffer, southInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;
  450. var northwestHeight = (encoding.decodeHeight(buffer, northInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
  451. var northeastHeight = (encoding.decodeHeight(buffer, northInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;
  452. return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
  453. }
  454. function triangleInterpolateHeight(dX, dY, southwestHeight, southeastHeight, northwestHeight, northeastHeight) {
  455. // The HeightmapTessellator bisects the quad from southwest to northeast.
  456. if (dY < dX) {
  457. // Lower right triangle
  458. return southwestHeight + (dX * (southeastHeight - southwestHeight)) + (dY * (northeastHeight - southeastHeight));
  459. }
  460. // Upper left triangle
  461. return southwestHeight + (dX * (northeastHeight - northwestHeight)) + (dY * (northwestHeight - southwestHeight));
  462. }
  463. function getHeight(heights, elementsPerHeight, elementMultiplier, stride, isBigEndian, index) {
  464. index *= stride;
  465. var height = 0;
  466. var i;
  467. if (isBigEndian) {
  468. for (i = 0; i < elementsPerHeight; ++i) {
  469. height = (height * elementMultiplier) + heights[index + i];
  470. }
  471. } else {
  472. for (i = elementsPerHeight - 1; i >= 0; --i) {
  473. height = (height * elementMultiplier) + heights[index + i];
  474. }
  475. }
  476. return height;
  477. }
  478. function setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, index, height) {
  479. index *= stride;
  480. var i;
  481. if (isBigEndian) {
  482. for (i = 0; i < elementsPerHeight - 1; ++i) {
  483. heights[index + i] = (height / divisor) | 0;
  484. height -= heights[index + i] * divisor;
  485. divisor /= elementMultiplier;
  486. }
  487. } else {
  488. for (i = elementsPerHeight - 1; i > 0; --i) {
  489. heights[index + i] = (height / divisor) | 0;
  490. height -= heights[index + i] * divisor;
  491. divisor /= elementMultiplier;
  492. }
  493. }
  494. heights[index + i] = height;
  495. }
  496. return HeightmapTerrainData;
  497. });