upsampleQuantizedTerrainMesh.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. import AttributeCompression from "../Core/AttributeCompression.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian2 from "../Core/Cartesian2.js";
  4. import Cartesian3 from "../Core/Cartesian3.js";
  5. import Cartographic from "../Core/Cartographic.js";
  6. import defined from "../Core/defined.js";
  7. import Ellipsoid from "../Core/Ellipsoid.js";
  8. import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";
  9. import IndexDatatype from "../Core/IndexDatatype.js";
  10. import Intersections2D from "../Core/Intersections2D.js";
  11. import CesiumMath from "../Core/Math.js";
  12. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  13. import Rectangle from "../Core/Rectangle.js";
  14. import TerrainEncoding from "../Core/TerrainEncoding.js";
  15. import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
  16. const maxShort = 32767;
  17. const halfMaxShort = (maxShort / 2) | 0;
  18. const clipScratch = [];
  19. const clipScratch2 = [];
  20. const verticesScratch = [];
  21. const cartographicScratch = new Cartographic();
  22. let cartesian3Scratch = new Cartesian3();
  23. const uScratch = [];
  24. const vScratch = [];
  25. const heightScratch = [];
  26. const indicesScratch = [];
  27. const normalsScratch = [];
  28. const horizonOcclusionPointScratch = new Cartesian3();
  29. const boundingSphereScratch = new BoundingSphere();
  30. const orientedBoundingBoxScratch = new OrientedBoundingBox();
  31. const decodeTexCoordsScratch = new Cartesian2();
  32. const octEncodedNormalScratch = new Cartesian3();
  33. function upsampleQuantizedTerrainMesh(parameters, transferableObjects) {
  34. const isEastChild = parameters.isEastChild;
  35. const isNorthChild = parameters.isNorthChild;
  36. const minU = isEastChild ? halfMaxShort : 0;
  37. const maxU = isEastChild ? maxShort : halfMaxShort;
  38. const minV = isNorthChild ? halfMaxShort : 0;
  39. const maxV = isNorthChild ? maxShort : halfMaxShort;
  40. const uBuffer = uScratch;
  41. const vBuffer = vScratch;
  42. const heightBuffer = heightScratch;
  43. const normalBuffer = normalsScratch;
  44. uBuffer.length = 0;
  45. vBuffer.length = 0;
  46. heightBuffer.length = 0;
  47. normalBuffer.length = 0;
  48. const indices = indicesScratch;
  49. indices.length = 0;
  50. const vertexMap = {};
  51. const parentVertices = parameters.vertices;
  52. let parentIndices = parameters.indices;
  53. parentIndices = parentIndices.subarray(0, parameters.indexCountWithoutSkirts);
  54. const encoding = TerrainEncoding.clone(parameters.encoding);
  55. const hasVertexNormals = encoding.hasVertexNormals;
  56. let vertexCount = 0;
  57. const quantizedVertexCount = parameters.vertexCountWithoutSkirts;
  58. const parentMinimumHeight = parameters.minimumHeight;
  59. const parentMaximumHeight = parameters.maximumHeight;
  60. const parentUBuffer = new Array(quantizedVertexCount);
  61. const parentVBuffer = new Array(quantizedVertexCount);
  62. const parentHeightBuffer = new Array(quantizedVertexCount);
  63. const parentNormalBuffer = hasVertexNormals
  64. ? new Array(quantizedVertexCount * 2)
  65. : undefined;
  66. const threshold = 20;
  67. let height;
  68. let i, n;
  69. let u, v;
  70. for (i = 0, n = 0; i < quantizedVertexCount; ++i, n += 2) {
  71. const texCoords = encoding.decodeTextureCoordinates(
  72. parentVertices,
  73. i,
  74. decodeTexCoordsScratch
  75. );
  76. height = encoding.decodeHeight(parentVertices, i);
  77. u = CesiumMath.clamp((texCoords.x * maxShort) | 0, 0, maxShort);
  78. v = CesiumMath.clamp((texCoords.y * maxShort) | 0, 0, maxShort);
  79. parentHeightBuffer[i] = CesiumMath.clamp(
  80. (((height - parentMinimumHeight) /
  81. (parentMaximumHeight - parentMinimumHeight)) *
  82. maxShort) |
  83. 0,
  84. 0,
  85. maxShort
  86. );
  87. if (u < threshold) {
  88. u = 0;
  89. }
  90. if (v < threshold) {
  91. v = 0;
  92. }
  93. if (maxShort - u < threshold) {
  94. u = maxShort;
  95. }
  96. if (maxShort - v < threshold) {
  97. v = maxShort;
  98. }
  99. parentUBuffer[i] = u;
  100. parentVBuffer[i] = v;
  101. if (hasVertexNormals) {
  102. const encodedNormal = encoding.getOctEncodedNormal(
  103. parentVertices,
  104. i,
  105. octEncodedNormalScratch
  106. );
  107. parentNormalBuffer[n] = encodedNormal.x;
  108. parentNormalBuffer[n + 1] = encodedNormal.y;
  109. }
  110. if (
  111. ((isEastChild && u >= halfMaxShort) ||
  112. (!isEastChild && u <= halfMaxShort)) &&
  113. ((isNorthChild && v >= halfMaxShort) ||
  114. (!isNorthChild && v <= halfMaxShort))
  115. ) {
  116. vertexMap[i] = vertexCount;
  117. uBuffer.push(u);
  118. vBuffer.push(v);
  119. heightBuffer.push(parentHeightBuffer[i]);
  120. if (hasVertexNormals) {
  121. normalBuffer.push(parentNormalBuffer[n]);
  122. normalBuffer.push(parentNormalBuffer[n + 1]);
  123. }
  124. ++vertexCount;
  125. }
  126. }
  127. const triangleVertices = [];
  128. triangleVertices.push(new Vertex());
  129. triangleVertices.push(new Vertex());
  130. triangleVertices.push(new Vertex());
  131. const clippedTriangleVertices = [];
  132. clippedTriangleVertices.push(new Vertex());
  133. clippedTriangleVertices.push(new Vertex());
  134. clippedTriangleVertices.push(new Vertex());
  135. let clippedIndex;
  136. let clipped2;
  137. for (i = 0; i < parentIndices.length; i += 3) {
  138. const i0 = parentIndices[i];
  139. const i1 = parentIndices[i + 1];
  140. const i2 = parentIndices[i + 2];
  141. const u0 = parentUBuffer[i0];
  142. const u1 = parentUBuffer[i1];
  143. const u2 = parentUBuffer[i2];
  144. triangleVertices[0].initializeIndexed(
  145. parentUBuffer,
  146. parentVBuffer,
  147. parentHeightBuffer,
  148. parentNormalBuffer,
  149. i0
  150. );
  151. triangleVertices[1].initializeIndexed(
  152. parentUBuffer,
  153. parentVBuffer,
  154. parentHeightBuffer,
  155. parentNormalBuffer,
  156. i1
  157. );
  158. triangleVertices[2].initializeIndexed(
  159. parentUBuffer,
  160. parentVBuffer,
  161. parentHeightBuffer,
  162. parentNormalBuffer,
  163. i2
  164. );
  165. // Clip triangle on the east-west boundary.
  166. const clipped = Intersections2D.clipTriangleAtAxisAlignedThreshold(
  167. halfMaxShort,
  168. isEastChild,
  169. u0,
  170. u1,
  171. u2,
  172. clipScratch
  173. );
  174. // Get the first clipped triangle, if any.
  175. clippedIndex = 0;
  176. if (clippedIndex >= clipped.length) {
  177. continue;
  178. }
  179. clippedIndex = clippedTriangleVertices[0].initializeFromClipResult(
  180. clipped,
  181. clippedIndex,
  182. triangleVertices
  183. );
  184. if (clippedIndex >= clipped.length) {
  185. continue;
  186. }
  187. clippedIndex = clippedTriangleVertices[1].initializeFromClipResult(
  188. clipped,
  189. clippedIndex,
  190. triangleVertices
  191. );
  192. if (clippedIndex >= clipped.length) {
  193. continue;
  194. }
  195. clippedIndex = clippedTriangleVertices[2].initializeFromClipResult(
  196. clipped,
  197. clippedIndex,
  198. triangleVertices
  199. );
  200. // Clip the triangle against the North-south boundary.
  201. clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(
  202. halfMaxShort,
  203. isNorthChild,
  204. clippedTriangleVertices[0].getV(),
  205. clippedTriangleVertices[1].getV(),
  206. clippedTriangleVertices[2].getV(),
  207. clipScratch2
  208. );
  209. addClippedPolygon(
  210. uBuffer,
  211. vBuffer,
  212. heightBuffer,
  213. normalBuffer,
  214. indices,
  215. vertexMap,
  216. clipped2,
  217. clippedTriangleVertices,
  218. hasVertexNormals
  219. );
  220. // If there's another vertex in the original clipped result,
  221. // it forms a second triangle. Clip it as well.
  222. if (clippedIndex < clipped.length) {
  223. clippedTriangleVertices[2].clone(clippedTriangleVertices[1]);
  224. clippedTriangleVertices[2].initializeFromClipResult(
  225. clipped,
  226. clippedIndex,
  227. triangleVertices
  228. );
  229. clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(
  230. halfMaxShort,
  231. isNorthChild,
  232. clippedTriangleVertices[0].getV(),
  233. clippedTriangleVertices[1].getV(),
  234. clippedTriangleVertices[2].getV(),
  235. clipScratch2
  236. );
  237. addClippedPolygon(
  238. uBuffer,
  239. vBuffer,
  240. heightBuffer,
  241. normalBuffer,
  242. indices,
  243. vertexMap,
  244. clipped2,
  245. clippedTriangleVertices,
  246. hasVertexNormals
  247. );
  248. }
  249. }
  250. const uOffset = isEastChild ? -maxShort : 0;
  251. const vOffset = isNorthChild ? -maxShort : 0;
  252. const westIndices = [];
  253. const southIndices = [];
  254. const eastIndices = [];
  255. const northIndices = [];
  256. let minimumHeight = Number.MAX_VALUE;
  257. let maximumHeight = -minimumHeight;
  258. const cartesianVertices = verticesScratch;
  259. cartesianVertices.length = 0;
  260. const ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
  261. const rectangle = Rectangle.clone(parameters.childRectangle);
  262. const north = rectangle.north;
  263. const south = rectangle.south;
  264. let east = rectangle.east;
  265. const west = rectangle.west;
  266. if (east < west) {
  267. east += CesiumMath.TWO_PI;
  268. }
  269. for (i = 0; i < uBuffer.length; ++i) {
  270. u = Math.round(uBuffer[i]);
  271. if (u <= minU) {
  272. westIndices.push(i);
  273. u = 0;
  274. } else if (u >= maxU) {
  275. eastIndices.push(i);
  276. u = maxShort;
  277. } else {
  278. u = u * 2 + uOffset;
  279. }
  280. uBuffer[i] = u;
  281. v = Math.round(vBuffer[i]);
  282. if (v <= minV) {
  283. southIndices.push(i);
  284. v = 0;
  285. } else if (v >= maxV) {
  286. northIndices.push(i);
  287. v = maxShort;
  288. } else {
  289. v = v * 2 + vOffset;
  290. }
  291. vBuffer[i] = v;
  292. height = CesiumMath.lerp(
  293. parentMinimumHeight,
  294. parentMaximumHeight,
  295. heightBuffer[i] / maxShort
  296. );
  297. if (height < minimumHeight) {
  298. minimumHeight = height;
  299. }
  300. if (height > maximumHeight) {
  301. maximumHeight = height;
  302. }
  303. heightBuffer[i] = height;
  304. cartographicScratch.longitude = CesiumMath.lerp(west, east, u / maxShort);
  305. cartographicScratch.latitude = CesiumMath.lerp(south, north, v / maxShort);
  306. cartographicScratch.height = height;
  307. ellipsoid.cartographicToCartesian(cartographicScratch, cartesian3Scratch);
  308. cartesianVertices.push(cartesian3Scratch.x);
  309. cartesianVertices.push(cartesian3Scratch.y);
  310. cartesianVertices.push(cartesian3Scratch.z);
  311. }
  312. const boundingSphere = BoundingSphere.fromVertices(
  313. cartesianVertices,
  314. Cartesian3.ZERO,
  315. 3,
  316. boundingSphereScratch
  317. );
  318. const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
  319. rectangle,
  320. minimumHeight,
  321. maximumHeight,
  322. ellipsoid,
  323. orientedBoundingBoxScratch
  324. );
  325. const occluder = new EllipsoidalOccluder(ellipsoid);
  326. const horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid(
  327. boundingSphere.center,
  328. cartesianVertices,
  329. 3,
  330. boundingSphere.center,
  331. minimumHeight,
  332. horizonOcclusionPointScratch
  333. );
  334. const heightRange = maximumHeight - minimumHeight;
  335. const vertices = new Uint16Array(
  336. uBuffer.length + vBuffer.length + heightBuffer.length
  337. );
  338. for (i = 0; i < uBuffer.length; ++i) {
  339. vertices[i] = uBuffer[i];
  340. }
  341. let start = uBuffer.length;
  342. for (i = 0; i < vBuffer.length; ++i) {
  343. vertices[start + i] = vBuffer[i];
  344. }
  345. start += vBuffer.length;
  346. for (i = 0; i < heightBuffer.length; ++i) {
  347. vertices[start + i] =
  348. (maxShort * (heightBuffer[i] - minimumHeight)) / heightRange;
  349. }
  350. const indicesTypedArray = IndexDatatype.createTypedArray(
  351. uBuffer.length,
  352. indices
  353. );
  354. let encodedNormals;
  355. if (hasVertexNormals) {
  356. const normalArray = new Uint8Array(normalBuffer);
  357. transferableObjects.push(
  358. vertices.buffer,
  359. indicesTypedArray.buffer,
  360. normalArray.buffer
  361. );
  362. encodedNormals = normalArray.buffer;
  363. } else {
  364. transferableObjects.push(vertices.buffer, indicesTypedArray.buffer);
  365. }
  366. return {
  367. vertices: vertices.buffer,
  368. encodedNormals: encodedNormals,
  369. indices: indicesTypedArray.buffer,
  370. minimumHeight: minimumHeight,
  371. maximumHeight: maximumHeight,
  372. westIndices: westIndices,
  373. southIndices: southIndices,
  374. eastIndices: eastIndices,
  375. northIndices: northIndices,
  376. boundingSphere: boundingSphere,
  377. orientedBoundingBox: orientedBoundingBox,
  378. horizonOcclusionPoint: horizonOcclusionPoint,
  379. };
  380. }
  381. function Vertex() {
  382. this.vertexBuffer = undefined;
  383. this.index = undefined;
  384. this.first = undefined;
  385. this.second = undefined;
  386. this.ratio = undefined;
  387. }
  388. Vertex.prototype.clone = function (result) {
  389. if (!defined(result)) {
  390. result = new Vertex();
  391. }
  392. result.uBuffer = this.uBuffer;
  393. result.vBuffer = this.vBuffer;
  394. result.heightBuffer = this.heightBuffer;
  395. result.normalBuffer = this.normalBuffer;
  396. result.index = this.index;
  397. result.first = this.first;
  398. result.second = this.second;
  399. result.ratio = this.ratio;
  400. return result;
  401. };
  402. Vertex.prototype.initializeIndexed = function (
  403. uBuffer,
  404. vBuffer,
  405. heightBuffer,
  406. normalBuffer,
  407. index
  408. ) {
  409. this.uBuffer = uBuffer;
  410. this.vBuffer = vBuffer;
  411. this.heightBuffer = heightBuffer;
  412. this.normalBuffer = normalBuffer;
  413. this.index = index;
  414. this.first = undefined;
  415. this.second = undefined;
  416. this.ratio = undefined;
  417. };
  418. Vertex.prototype.initializeFromClipResult = function (
  419. clipResult,
  420. index,
  421. vertices
  422. ) {
  423. let nextIndex = index + 1;
  424. if (clipResult[index] !== -1) {
  425. vertices[clipResult[index]].clone(this);
  426. } else {
  427. this.vertexBuffer = undefined;
  428. this.index = undefined;
  429. this.first = vertices[clipResult[nextIndex]];
  430. ++nextIndex;
  431. this.second = vertices[clipResult[nextIndex]];
  432. ++nextIndex;
  433. this.ratio = clipResult[nextIndex];
  434. ++nextIndex;
  435. }
  436. return nextIndex;
  437. };
  438. Vertex.prototype.getKey = function () {
  439. if (this.isIndexed()) {
  440. return this.index;
  441. }
  442. return JSON.stringify({
  443. first: this.first.getKey(),
  444. second: this.second.getKey(),
  445. ratio: this.ratio,
  446. });
  447. };
  448. Vertex.prototype.isIndexed = function () {
  449. return defined(this.index);
  450. };
  451. Vertex.prototype.getH = function () {
  452. if (defined(this.index)) {
  453. return this.heightBuffer[this.index];
  454. }
  455. return CesiumMath.lerp(this.first.getH(), this.second.getH(), this.ratio);
  456. };
  457. Vertex.prototype.getU = function () {
  458. if (defined(this.index)) {
  459. return this.uBuffer[this.index];
  460. }
  461. return CesiumMath.lerp(this.first.getU(), this.second.getU(), this.ratio);
  462. };
  463. Vertex.prototype.getV = function () {
  464. if (defined(this.index)) {
  465. return this.vBuffer[this.index];
  466. }
  467. return CesiumMath.lerp(this.first.getV(), this.second.getV(), this.ratio);
  468. };
  469. let encodedScratch = new Cartesian2();
  470. // An upsampled triangle may be clipped twice before it is assigned an index
  471. // In this case, we need a buffer to handle the recursion of getNormalX() and getNormalY().
  472. let depth = -1;
  473. const cartesianScratch1 = [new Cartesian3(), new Cartesian3()];
  474. const cartesianScratch2 = [new Cartesian3(), new Cartesian3()];
  475. function lerpOctEncodedNormal(vertex, result) {
  476. ++depth;
  477. let first = cartesianScratch1[depth];
  478. let second = cartesianScratch2[depth];
  479. first = AttributeCompression.octDecode(
  480. vertex.first.getNormalX(),
  481. vertex.first.getNormalY(),
  482. first
  483. );
  484. second = AttributeCompression.octDecode(
  485. vertex.second.getNormalX(),
  486. vertex.second.getNormalY(),
  487. second
  488. );
  489. cartesian3Scratch = Cartesian3.lerp(
  490. first,
  491. second,
  492. vertex.ratio,
  493. cartesian3Scratch
  494. );
  495. Cartesian3.normalize(cartesian3Scratch, cartesian3Scratch);
  496. AttributeCompression.octEncode(cartesian3Scratch, result);
  497. --depth;
  498. return result;
  499. }
  500. Vertex.prototype.getNormalX = function () {
  501. if (defined(this.index)) {
  502. return this.normalBuffer[this.index * 2];
  503. }
  504. encodedScratch = lerpOctEncodedNormal(this, encodedScratch);
  505. return encodedScratch.x;
  506. };
  507. Vertex.prototype.getNormalY = function () {
  508. if (defined(this.index)) {
  509. return this.normalBuffer[this.index * 2 + 1];
  510. }
  511. encodedScratch = lerpOctEncodedNormal(this, encodedScratch);
  512. return encodedScratch.y;
  513. };
  514. const polygonVertices = [];
  515. polygonVertices.push(new Vertex());
  516. polygonVertices.push(new Vertex());
  517. polygonVertices.push(new Vertex());
  518. polygonVertices.push(new Vertex());
  519. function addClippedPolygon(
  520. uBuffer,
  521. vBuffer,
  522. heightBuffer,
  523. normalBuffer,
  524. indices,
  525. vertexMap,
  526. clipped,
  527. triangleVertices,
  528. hasVertexNormals
  529. ) {
  530. if (clipped.length === 0) {
  531. return;
  532. }
  533. let numVertices = 0;
  534. let clippedIndex = 0;
  535. while (clippedIndex < clipped.length) {
  536. clippedIndex = polygonVertices[numVertices++].initializeFromClipResult(
  537. clipped,
  538. clippedIndex,
  539. triangleVertices
  540. );
  541. }
  542. for (let i = 0; i < numVertices; ++i) {
  543. const polygonVertex = polygonVertices[i];
  544. if (!polygonVertex.isIndexed()) {
  545. const key = polygonVertex.getKey();
  546. if (defined(vertexMap[key])) {
  547. polygonVertex.newIndex = vertexMap[key];
  548. } else {
  549. const newIndex = uBuffer.length;
  550. uBuffer.push(polygonVertex.getU());
  551. vBuffer.push(polygonVertex.getV());
  552. heightBuffer.push(polygonVertex.getH());
  553. if (hasVertexNormals) {
  554. normalBuffer.push(polygonVertex.getNormalX());
  555. normalBuffer.push(polygonVertex.getNormalY());
  556. }
  557. polygonVertex.newIndex = newIndex;
  558. vertexMap[key] = newIndex;
  559. }
  560. } else {
  561. polygonVertex.newIndex = vertexMap[polygonVertex.index];
  562. polygonVertex.uBuffer = uBuffer;
  563. polygonVertex.vBuffer = vBuffer;
  564. polygonVertex.heightBuffer = heightBuffer;
  565. if (hasVertexNormals) {
  566. polygonVertex.normalBuffer = normalBuffer;
  567. }
  568. }
  569. }
  570. if (numVertices === 3) {
  571. // A triangle.
  572. indices.push(polygonVertices[0].newIndex);
  573. indices.push(polygonVertices[1].newIndex);
  574. indices.push(polygonVertices[2].newIndex);
  575. } else if (numVertices === 4) {
  576. // A quad - two triangles.
  577. indices.push(polygonVertices[0].newIndex);
  578. indices.push(polygonVertices[1].newIndex);
  579. indices.push(polygonVertices[2].newIndex);
  580. indices.push(polygonVertices[0].newIndex);
  581. indices.push(polygonVertices[2].newIndex);
  582. indices.push(polygonVertices[3].newIndex);
  583. }
  584. }
  585. export default createTaskProcessorWorker(upsampleQuantizedTerrainMesh);