TerrainEncoding.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. import AttributeCompression from "./AttributeCompression.js";
  2. import Cartesian2 from "./Cartesian2.js";
  3. import Cartesian3 from "./Cartesian3.js";
  4. import ComponentDatatype from "./ComponentDatatype.js";
  5. import defaultValue from "./defaultValue.js";
  6. import defined from "./defined.js";
  7. import CesiumMath from "./Math.js";
  8. import Matrix4 from "./Matrix4.js";
  9. import TerrainExaggeration from "./TerrainExaggeration.js";
  10. import TerrainQuantization from "./TerrainQuantization.js";
  11. const cartesian3Scratch = new Cartesian3();
  12. const cartesian3DimScratch = new Cartesian3();
  13. const cartesian2Scratch = new Cartesian2();
  14. const matrix4Scratch = new Matrix4();
  15. const matrix4Scratch2 = new Matrix4();
  16. const SHIFT_LEFT_12 = Math.pow(2.0, 12.0);
  17. /**
  18. * Data used to quantize and pack the terrain mesh. The position can be unpacked for picking and all attributes
  19. * are unpacked in the vertex shader.
  20. *
  21. * @alias TerrainEncoding
  22. * @constructor
  23. *
  24. * @param {Cartesian3} center The center point of the vertices.
  25. * @param {AxisAlignedBoundingBox} axisAlignedBoundingBox The bounds of the tile in the east-north-up coordinates at the tiles center.
  26. * @param {Number} minimumHeight The minimum height.
  27. * @param {Number} maximumHeight The maximum height.
  28. * @param {Matrix4} fromENU The east-north-up to fixed frame matrix at the center of the terrain mesh.
  29. * @param {Boolean} hasVertexNormals If the mesh has vertex normals.
  30. * @param {Boolean} [hasWebMercatorT=false] true if the terrain data includes a Web Mercator texture coordinate; otherwise, false.
  31. * @param {Boolean} [hasGeodeticSurfaceNormals=false] true if the terrain data includes geodetic surface normals; otherwise, false.
  32. * @param {Number} [exaggeration=1.0] A scalar used to exaggerate terrain.
  33. * @param {Number} [exaggerationRelativeHeight=0.0] The relative height from which terrain is exaggerated.
  34. *
  35. * @private
  36. */
  37. function TerrainEncoding(
  38. center,
  39. axisAlignedBoundingBox,
  40. minimumHeight,
  41. maximumHeight,
  42. fromENU,
  43. hasVertexNormals,
  44. hasWebMercatorT,
  45. hasGeodeticSurfaceNormals,
  46. exaggeration,
  47. exaggerationRelativeHeight
  48. ) {
  49. let quantization = TerrainQuantization.NONE;
  50. let toENU;
  51. let matrix;
  52. if (
  53. defined(axisAlignedBoundingBox) &&
  54. defined(minimumHeight) &&
  55. defined(maximumHeight) &&
  56. defined(fromENU)
  57. ) {
  58. const minimum = axisAlignedBoundingBox.minimum;
  59. const maximum = axisAlignedBoundingBox.maximum;
  60. const dimensions = Cartesian3.subtract(
  61. maximum,
  62. minimum,
  63. cartesian3DimScratch
  64. );
  65. const hDim = maximumHeight - minimumHeight;
  66. const maxDim = Math.max(Cartesian3.maximumComponent(dimensions), hDim);
  67. if (maxDim < SHIFT_LEFT_12 - 1.0) {
  68. quantization = TerrainQuantization.BITS12;
  69. } else {
  70. quantization = TerrainQuantization.NONE;
  71. }
  72. toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
  73. const translation = Cartesian3.negate(minimum, cartesian3Scratch);
  74. Matrix4.multiply(
  75. Matrix4.fromTranslation(translation, matrix4Scratch),
  76. toENU,
  77. toENU
  78. );
  79. const scale = cartesian3Scratch;
  80. scale.x = 1.0 / dimensions.x;
  81. scale.y = 1.0 / dimensions.y;
  82. scale.z = 1.0 / dimensions.z;
  83. Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch), toENU, toENU);
  84. matrix = Matrix4.clone(fromENU);
  85. Matrix4.setTranslation(matrix, Cartesian3.ZERO, matrix);
  86. fromENU = Matrix4.clone(fromENU, new Matrix4());
  87. const translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch);
  88. const scaleMatrix = Matrix4.fromScale(dimensions, matrix4Scratch2);
  89. const st = Matrix4.multiply(translationMatrix, scaleMatrix, matrix4Scratch);
  90. Matrix4.multiply(fromENU, st, fromENU);
  91. Matrix4.multiply(matrix, st, matrix);
  92. }
  93. /**
  94. * How the vertices of the mesh were compressed.
  95. * @type {TerrainQuantization}
  96. */
  97. this.quantization = quantization;
  98. /**
  99. * The minimum height of the tile including the skirts.
  100. * @type {Number}
  101. */
  102. this.minimumHeight = minimumHeight;
  103. /**
  104. * The maximum height of the tile.
  105. * @type {Number}
  106. */
  107. this.maximumHeight = maximumHeight;
  108. /**
  109. * The center of the tile.
  110. * @type {Cartesian3}
  111. */
  112. this.center = Cartesian3.clone(center);
  113. /**
  114. * A matrix that takes a vertex from the tile, transforms it to east-north-up at the center and scales
  115. * it so each component is in the [0, 1] range.
  116. * @type {Matrix4}
  117. */
  118. this.toScaledENU = toENU;
  119. /**
  120. * A matrix that restores a vertex transformed with toScaledENU back to the earth fixed reference frame
  121. * @type {Matrix4}
  122. */
  123. this.fromScaledENU = fromENU;
  124. /**
  125. * The matrix used to decompress the terrain vertices in the shader for RTE rendering.
  126. * @type {Matrix4}
  127. */
  128. this.matrix = matrix;
  129. /**
  130. * The terrain mesh contains normals.
  131. * @type {Boolean}
  132. */
  133. this.hasVertexNormals = hasVertexNormals;
  134. /**
  135. * The terrain mesh contains a vertical texture coordinate following the Web Mercator projection.
  136. * @type {Boolean}
  137. */
  138. this.hasWebMercatorT = defaultValue(hasWebMercatorT, false);
  139. /**
  140. * The terrain mesh contains geodetic surface normals, used for terrain exaggeration.
  141. * @type {Boolean}
  142. */
  143. this.hasGeodeticSurfaceNormals = defaultValue(
  144. hasGeodeticSurfaceNormals,
  145. false
  146. );
  147. /**
  148. * A scalar used to exaggerate terrain.
  149. * @type {Number}
  150. */
  151. this.exaggeration = defaultValue(exaggeration, 1.0);
  152. /**
  153. * The relative height from which terrain is exaggerated.
  154. */
  155. this.exaggerationRelativeHeight = defaultValue(
  156. exaggerationRelativeHeight,
  157. 0.0
  158. );
  159. /**
  160. * The number of components in each vertex. This value can differ with different quantizations.
  161. * @type {Number}
  162. */
  163. this.stride = 0;
  164. this._offsetGeodeticSurfaceNormal = 0;
  165. this._offsetVertexNormal = 0;
  166. // Calculate the stride and offsets declared above
  167. this._calculateStrideAndOffsets();
  168. }
  169. TerrainEncoding.prototype.encode = function (
  170. vertexBuffer,
  171. bufferIndex,
  172. position,
  173. uv,
  174. height,
  175. normalToPack,
  176. webMercatorT,
  177. geodeticSurfaceNormal
  178. ) {
  179. const u = uv.x;
  180. const v = uv.y;
  181. if (this.quantization === TerrainQuantization.BITS12) {
  182. position = Matrix4.multiplyByPoint(
  183. this.toScaledENU,
  184. position,
  185. cartesian3Scratch
  186. );
  187. position.x = CesiumMath.clamp(position.x, 0.0, 1.0);
  188. position.y = CesiumMath.clamp(position.y, 0.0, 1.0);
  189. position.z = CesiumMath.clamp(position.z, 0.0, 1.0);
  190. const hDim = this.maximumHeight - this.minimumHeight;
  191. const h = CesiumMath.clamp((height - this.minimumHeight) / hDim, 0.0, 1.0);
  192. Cartesian2.fromElements(position.x, position.y, cartesian2Scratch);
  193. const compressed0 = AttributeCompression.compressTextureCoordinates(
  194. cartesian2Scratch
  195. );
  196. Cartesian2.fromElements(position.z, h, cartesian2Scratch);
  197. const compressed1 = AttributeCompression.compressTextureCoordinates(
  198. cartesian2Scratch
  199. );
  200. Cartesian2.fromElements(u, v, cartesian2Scratch);
  201. const compressed2 = AttributeCompression.compressTextureCoordinates(
  202. cartesian2Scratch
  203. );
  204. vertexBuffer[bufferIndex++] = compressed0;
  205. vertexBuffer[bufferIndex++] = compressed1;
  206. vertexBuffer[bufferIndex++] = compressed2;
  207. if (this.hasWebMercatorT) {
  208. Cartesian2.fromElements(webMercatorT, 0.0, cartesian2Scratch);
  209. const compressed3 = AttributeCompression.compressTextureCoordinates(
  210. cartesian2Scratch
  211. );
  212. vertexBuffer[bufferIndex++] = compressed3;
  213. }
  214. } else {
  215. Cartesian3.subtract(position, this.center, cartesian3Scratch);
  216. vertexBuffer[bufferIndex++] = cartesian3Scratch.x;
  217. vertexBuffer[bufferIndex++] = cartesian3Scratch.y;
  218. vertexBuffer[bufferIndex++] = cartesian3Scratch.z;
  219. vertexBuffer[bufferIndex++] = height;
  220. vertexBuffer[bufferIndex++] = u;
  221. vertexBuffer[bufferIndex++] = v;
  222. if (this.hasWebMercatorT) {
  223. vertexBuffer[bufferIndex++] = webMercatorT;
  224. }
  225. }
  226. if (this.hasVertexNormals) {
  227. vertexBuffer[bufferIndex++] = AttributeCompression.octPackFloat(
  228. normalToPack
  229. );
  230. }
  231. if (this.hasGeodeticSurfaceNormals) {
  232. vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.x;
  233. vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.y;
  234. vertexBuffer[bufferIndex++] = geodeticSurfaceNormal.z;
  235. }
  236. return bufferIndex;
  237. };
  238. const scratchPosition = new Cartesian3();
  239. const scratchGeodeticSurfaceNormal = new Cartesian3();
  240. TerrainEncoding.prototype.addGeodeticSurfaceNormals = function (
  241. oldBuffer,
  242. newBuffer,
  243. ellipsoid
  244. ) {
  245. if (this.hasGeodeticSurfaceNormals) {
  246. return;
  247. }
  248. const oldStride = this.stride;
  249. const vertexCount = oldBuffer.length / oldStride;
  250. this.hasGeodeticSurfaceNormals = true;
  251. this._calculateStrideAndOffsets();
  252. const newStride = this.stride;
  253. for (let index = 0; index < vertexCount; index++) {
  254. for (let offset = 0; offset < oldStride; offset++) {
  255. const oldIndex = index * oldStride + offset;
  256. const newIndex = index * newStride + offset;
  257. newBuffer[newIndex] = oldBuffer[oldIndex];
  258. }
  259. const position = this.decodePosition(newBuffer, index, scratchPosition);
  260. const geodeticSurfaceNormal = ellipsoid.geodeticSurfaceNormal(
  261. position,
  262. scratchGeodeticSurfaceNormal
  263. );
  264. const bufferIndex = index * newStride + this._offsetGeodeticSurfaceNormal;
  265. newBuffer[bufferIndex] = geodeticSurfaceNormal.x;
  266. newBuffer[bufferIndex + 1] = geodeticSurfaceNormal.y;
  267. newBuffer[bufferIndex + 2] = geodeticSurfaceNormal.z;
  268. }
  269. };
  270. TerrainEncoding.prototype.removeGeodeticSurfaceNormals = function (
  271. oldBuffer,
  272. newBuffer
  273. ) {
  274. if (!this.hasGeodeticSurfaceNormals) {
  275. return;
  276. }
  277. const oldStride = this.stride;
  278. const vertexCount = oldBuffer.length / oldStride;
  279. this.hasGeodeticSurfaceNormals = false;
  280. this._calculateStrideAndOffsets();
  281. const newStride = this.stride;
  282. for (let index = 0; index < vertexCount; index++) {
  283. for (let offset = 0; offset < newStride; offset++) {
  284. const oldIndex = index * oldStride + offset;
  285. const newIndex = index * newStride + offset;
  286. newBuffer[newIndex] = oldBuffer[oldIndex];
  287. }
  288. }
  289. };
  290. TerrainEncoding.prototype.decodePosition = function (buffer, index, result) {
  291. if (!defined(result)) {
  292. result = new Cartesian3();
  293. }
  294. index *= this.stride;
  295. if (this.quantization === TerrainQuantization.BITS12) {
  296. const xy = AttributeCompression.decompressTextureCoordinates(
  297. buffer[index],
  298. cartesian2Scratch
  299. );
  300. result.x = xy.x;
  301. result.y = xy.y;
  302. const zh = AttributeCompression.decompressTextureCoordinates(
  303. buffer[index + 1],
  304. cartesian2Scratch
  305. );
  306. result.z = zh.x;
  307. return Matrix4.multiplyByPoint(this.fromScaledENU, result, result);
  308. }
  309. result.x = buffer[index];
  310. result.y = buffer[index + 1];
  311. result.z = buffer[index + 2];
  312. return Cartesian3.add(result, this.center, result);
  313. };
  314. TerrainEncoding.prototype.getExaggeratedPosition = function (
  315. buffer,
  316. index,
  317. result
  318. ) {
  319. result = this.decodePosition(buffer, index, result);
  320. const exaggeration = this.exaggeration;
  321. const exaggerationRelativeHeight = this.exaggerationRelativeHeight;
  322. const hasExaggeration = exaggeration !== 1.0;
  323. if (hasExaggeration && this.hasGeodeticSurfaceNormals) {
  324. const geodeticSurfaceNormal = this.decodeGeodeticSurfaceNormal(
  325. buffer,
  326. index,
  327. scratchGeodeticSurfaceNormal
  328. );
  329. const rawHeight = this.decodeHeight(buffer, index);
  330. const heightDifference =
  331. TerrainExaggeration.getHeight(
  332. rawHeight,
  333. exaggeration,
  334. exaggerationRelativeHeight
  335. ) - rawHeight;
  336. // some math is unrolled for better performance
  337. result.x += geodeticSurfaceNormal.x * heightDifference;
  338. result.y += geodeticSurfaceNormal.y * heightDifference;
  339. result.z += geodeticSurfaceNormal.z * heightDifference;
  340. }
  341. return result;
  342. };
  343. TerrainEncoding.prototype.decodeTextureCoordinates = function (
  344. buffer,
  345. index,
  346. result
  347. ) {
  348. if (!defined(result)) {
  349. result = new Cartesian2();
  350. }
  351. index *= this.stride;
  352. if (this.quantization === TerrainQuantization.BITS12) {
  353. return AttributeCompression.decompressTextureCoordinates(
  354. buffer[index + 2],
  355. result
  356. );
  357. }
  358. return Cartesian2.fromElements(buffer[index + 4], buffer[index + 5], result);
  359. };
  360. TerrainEncoding.prototype.decodeHeight = function (buffer, index) {
  361. index *= this.stride;
  362. if (this.quantization === TerrainQuantization.BITS12) {
  363. const zh = AttributeCompression.decompressTextureCoordinates(
  364. buffer[index + 1],
  365. cartesian2Scratch
  366. );
  367. return (
  368. zh.y * (this.maximumHeight - this.minimumHeight) + this.minimumHeight
  369. );
  370. }
  371. return buffer[index + 3];
  372. };
  373. TerrainEncoding.prototype.decodeWebMercatorT = function (buffer, index) {
  374. index *= this.stride;
  375. if (this.quantization === TerrainQuantization.BITS12) {
  376. return AttributeCompression.decompressTextureCoordinates(
  377. buffer[index + 3],
  378. cartesian2Scratch
  379. ).x;
  380. }
  381. return buffer[index + 6];
  382. };
  383. TerrainEncoding.prototype.getOctEncodedNormal = function (
  384. buffer,
  385. index,
  386. result
  387. ) {
  388. index = index * this.stride + this._offsetVertexNormal;
  389. const temp = buffer[index] / 256.0;
  390. const x = Math.floor(temp);
  391. const y = (temp - x) * 256.0;
  392. return Cartesian2.fromElements(x, y, result);
  393. };
  394. TerrainEncoding.prototype.decodeGeodeticSurfaceNormal = function (
  395. buffer,
  396. index,
  397. result
  398. ) {
  399. index = index * this.stride + this._offsetGeodeticSurfaceNormal;
  400. result.x = buffer[index];
  401. result.y = buffer[index + 1];
  402. result.z = buffer[index + 2];
  403. return result;
  404. };
  405. TerrainEncoding.prototype._calculateStrideAndOffsets = function () {
  406. let vertexStride = 0;
  407. switch (this.quantization) {
  408. case TerrainQuantization.BITS12:
  409. vertexStride += 3;
  410. break;
  411. default:
  412. vertexStride += 6;
  413. }
  414. if (this.hasWebMercatorT) {
  415. vertexStride += 1;
  416. }
  417. if (this.hasVertexNormals) {
  418. this._offsetVertexNormal = vertexStride;
  419. vertexStride += 1;
  420. }
  421. if (this.hasGeodeticSurfaceNormals) {
  422. this._offsetGeodeticSurfaceNormal = vertexStride;
  423. vertexStride += 3;
  424. }
  425. this.stride = vertexStride;
  426. };
  427. const attributesIndicesNone = {
  428. position3DAndHeight: 0,
  429. textureCoordAndEncodedNormals: 1,
  430. geodeticSurfaceNormal: 2,
  431. };
  432. const attributesIndicesBits12 = {
  433. compressed0: 0,
  434. compressed1: 1,
  435. geodeticSurfaceNormal: 2,
  436. };
  437. TerrainEncoding.prototype.getAttributes = function (buffer) {
  438. const datatype = ComponentDatatype.FLOAT;
  439. const sizeInBytes = ComponentDatatype.getSizeInBytes(datatype);
  440. const strideInBytes = this.stride * sizeInBytes;
  441. let offsetInBytes = 0;
  442. const attributes = [];
  443. function addAttribute(index, componentsPerAttribute) {
  444. attributes.push({
  445. index: index,
  446. vertexBuffer: buffer,
  447. componentDatatype: datatype,
  448. componentsPerAttribute: componentsPerAttribute,
  449. offsetInBytes: offsetInBytes,
  450. strideInBytes: strideInBytes,
  451. });
  452. offsetInBytes += componentsPerAttribute * sizeInBytes;
  453. }
  454. if (this.quantization === TerrainQuantization.NONE) {
  455. addAttribute(attributesIndicesNone.position3DAndHeight, 4);
  456. let componentsTexCoordAndNormals = 2;
  457. componentsTexCoordAndNormals += this.hasWebMercatorT ? 1 : 0;
  458. componentsTexCoordAndNormals += this.hasVertexNormals ? 1 : 0;
  459. addAttribute(
  460. attributesIndicesNone.textureCoordAndEncodedNormals,
  461. componentsTexCoordAndNormals
  462. );
  463. if (this.hasGeodeticSurfaceNormals) {
  464. addAttribute(attributesIndicesNone.geodeticSurfaceNormal, 3);
  465. }
  466. } else {
  467. // When there is no webMercatorT or vertex normals, the attribute only needs 3 components: x/y, z/h, u/v.
  468. // WebMercatorT and vertex normals each take up one component, so if only one of them is present the first
  469. // attribute gets a 4th component. If both are present, we need an additional attribute that has 1 component.
  470. const usingAttribute0Component4 =
  471. this.hasWebMercatorT || this.hasVertexNormals;
  472. const usingAttribute1Component1 =
  473. this.hasWebMercatorT && this.hasVertexNormals;
  474. addAttribute(
  475. attributesIndicesBits12.compressed0,
  476. usingAttribute0Component4 ? 4 : 3
  477. );
  478. if (usingAttribute1Component1) {
  479. addAttribute(attributesIndicesBits12.compressed1, 1);
  480. }
  481. if (this.hasGeodeticSurfaceNormals) {
  482. addAttribute(attributesIndicesBits12.geodeticSurfaceNormal, 3);
  483. }
  484. }
  485. return attributes;
  486. };
  487. TerrainEncoding.prototype.getAttributeLocations = function () {
  488. if (this.quantization === TerrainQuantization.NONE) {
  489. return attributesIndicesNone;
  490. }
  491. return attributesIndicesBits12;
  492. };
  493. TerrainEncoding.clone = function (encoding, result) {
  494. if (!defined(encoding)) {
  495. return undefined;
  496. }
  497. if (!defined(result)) {
  498. result = new TerrainEncoding();
  499. }
  500. result.quantization = encoding.quantization;
  501. result.minimumHeight = encoding.minimumHeight;
  502. result.maximumHeight = encoding.maximumHeight;
  503. result.center = Cartesian3.clone(encoding.center);
  504. result.toScaledENU = Matrix4.clone(encoding.toScaledENU);
  505. result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU);
  506. result.matrix = Matrix4.clone(encoding.matrix);
  507. result.hasVertexNormals = encoding.hasVertexNormals;
  508. result.hasWebMercatorT = encoding.hasWebMercatorT;
  509. result.hasGeodeticSurfaceNormals = encoding.hasGeodeticSurfaceNormals;
  510. result.exaggeration = encoding.exaggeration;
  511. result.exaggerationRelativeHeight = encoding.exaggerationRelativeHeight;
  512. result._calculateStrideAndOffsets();
  513. return result;
  514. };
  515. export default TerrainEncoding;