createVectorTileClampedPolylines.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. import AttributeCompression from "../Core/AttributeCompression.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Cartographic from "../Core/Cartographic.js";
  4. import combine from "../Core/combine.js";
  5. import Ellipsoid from "../Core/Ellipsoid.js";
  6. import IndexDatatype from "../Core/IndexDatatype.js";
  7. import CesiumMath from "../Core/Math.js";
  8. import Rectangle from "../Core/Rectangle.js";
  9. import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
  10. const MAX_SHORT = 32767;
  11. const MITER_BREAK = Math.cos(CesiumMath.toRadians(150.0));
  12. const scratchBVCartographic = new Cartographic();
  13. const scratchEncodedPosition = new Cartesian3();
  14. function decodePositions(
  15. uBuffer,
  16. vBuffer,
  17. heightBuffer,
  18. rectangle,
  19. minimumHeight,
  20. maximumHeight,
  21. ellipsoid
  22. ) {
  23. const positionsLength = uBuffer.length;
  24. const decodedPositions = new Float64Array(positionsLength * 3);
  25. for (let i = 0; i < positionsLength; ++i) {
  26. const u = uBuffer[i];
  27. const v = vBuffer[i];
  28. const h = heightBuffer[i];
  29. const lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / MAX_SHORT);
  30. const lat = CesiumMath.lerp(
  31. rectangle.south,
  32. rectangle.north,
  33. v / MAX_SHORT
  34. );
  35. const alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / MAX_SHORT);
  36. const cartographic = Cartographic.fromRadians(
  37. lon,
  38. lat,
  39. alt,
  40. scratchBVCartographic
  41. );
  42. const decodedPosition = ellipsoid.cartographicToCartesian(
  43. cartographic,
  44. scratchEncodedPosition
  45. );
  46. Cartesian3.pack(decodedPosition, decodedPositions, i * 3);
  47. }
  48. return decodedPositions;
  49. }
  50. function getPositionOffsets(counts) {
  51. const countsLength = counts.length;
  52. const positionOffsets = new Uint32Array(countsLength + 1);
  53. let offset = 0;
  54. for (let i = 0; i < countsLength; ++i) {
  55. positionOffsets[i] = offset;
  56. offset += counts[i];
  57. }
  58. positionOffsets[countsLength] = offset;
  59. return positionOffsets;
  60. }
  61. const previousCompressedCartographicScratch = new Cartographic();
  62. const currentCompressedCartographicScratch = new Cartographic();
  63. function removeDuplicates(uBuffer, vBuffer, heightBuffer, counts) {
  64. const countsLength = counts.length;
  65. const positionsLength = uBuffer.length;
  66. const markRemoval = new Uint8Array(positionsLength);
  67. const previous = previousCompressedCartographicScratch;
  68. const current = currentCompressedCartographicScratch;
  69. let offset = 0;
  70. for (let i = 0; i < countsLength; i++) {
  71. const count = counts[i];
  72. let updatedCount = count;
  73. for (let j = 1; j < count; j++) {
  74. const index = offset + j;
  75. const previousIndex = index - 1;
  76. current.longitude = uBuffer[index];
  77. current.latitude = vBuffer[index];
  78. previous.longitude = uBuffer[previousIndex];
  79. previous.latitude = vBuffer[previousIndex];
  80. if (Cartographic.equals(current, previous)) {
  81. updatedCount--;
  82. markRemoval[previousIndex] = 1;
  83. }
  84. }
  85. counts[i] = updatedCount;
  86. offset += count;
  87. }
  88. let nextAvailableIndex = 0;
  89. for (let k = 0; k < positionsLength; k++) {
  90. if (markRemoval[k] !== 1) {
  91. uBuffer[nextAvailableIndex] = uBuffer[k];
  92. vBuffer[nextAvailableIndex] = vBuffer[k];
  93. heightBuffer[nextAvailableIndex] = heightBuffer[k];
  94. nextAvailableIndex++;
  95. }
  96. }
  97. }
  98. function VertexAttributesAndIndices(volumesCount) {
  99. const vertexCount = volumesCount * 8;
  100. const vec3Floats = vertexCount * 3;
  101. const vec4Floats = vertexCount * 4;
  102. this.startEllipsoidNormals = new Float32Array(vec3Floats);
  103. this.endEllipsoidNormals = new Float32Array(vec3Floats);
  104. this.startPositionAndHeights = new Float32Array(vec4Floats);
  105. this.startFaceNormalAndVertexCornerIds = new Float32Array(vec4Floats);
  106. this.endPositionAndHeights = new Float32Array(vec4Floats);
  107. this.endFaceNormalAndHalfWidths = new Float32Array(vec4Floats);
  108. this.vertexBatchIds = new Uint16Array(vertexCount);
  109. this.indices = IndexDatatype.createTypedArray(vertexCount, 36 * volumesCount);
  110. this.vec3Offset = 0;
  111. this.vec4Offset = 0;
  112. this.batchIdOffset = 0;
  113. this.indexOffset = 0;
  114. this.volumeStartIndex = 0;
  115. }
  116. const towardCurrScratch = new Cartesian3();
  117. const towardNextScratch = new Cartesian3();
  118. function computeMiteredNormal(
  119. previousPosition,
  120. position,
  121. nextPosition,
  122. ellipsoidSurfaceNormal,
  123. result
  124. ) {
  125. const towardNext = Cartesian3.subtract(
  126. nextPosition,
  127. position,
  128. towardNextScratch
  129. );
  130. let towardCurr = Cartesian3.subtract(
  131. position,
  132. previousPosition,
  133. towardCurrScratch
  134. );
  135. Cartesian3.normalize(towardNext, towardNext);
  136. Cartesian3.normalize(towardCurr, towardCurr);
  137. if (Cartesian3.dot(towardNext, towardCurr) < MITER_BREAK) {
  138. towardCurr = Cartesian3.multiplyByScalar(
  139. towardCurr,
  140. -1.0,
  141. towardCurrScratch
  142. );
  143. }
  144. Cartesian3.add(towardNext, towardCurr, result);
  145. if (Cartesian3.equals(result, Cartesian3.ZERO)) {
  146. result = Cartesian3.subtract(previousPosition, position);
  147. }
  148. // Make sure the normal is orthogonal to the ellipsoid surface normal
  149. Cartesian3.cross(result, ellipsoidSurfaceNormal, result);
  150. Cartesian3.cross(ellipsoidSurfaceNormal, result, result);
  151. Cartesian3.normalize(result, result);
  152. return result;
  153. }
  154. // Winding order is reversed so each segment's volume is inside-out
  155. // 3-----------7
  156. // /| left /|
  157. // / | 1 / |
  158. // 2-----------6 5 end
  159. // | / | /
  160. // start |/ right |/
  161. // 0-----------4
  162. //
  163. const REFERENCE_INDICES = [
  164. 0,
  165. 2,
  166. 6,
  167. 0,
  168. 6,
  169. 4, // right
  170. 0,
  171. 1,
  172. 3,
  173. 0,
  174. 3,
  175. 2, // start face
  176. 0,
  177. 4,
  178. 5,
  179. 0,
  180. 5,
  181. 1, // bottom
  182. 5,
  183. 3,
  184. 1,
  185. 5,
  186. 7,
  187. 3, // left
  188. 7,
  189. 5,
  190. 4,
  191. 7,
  192. 4,
  193. 6, // end face
  194. 7,
  195. 6,
  196. 2,
  197. 7,
  198. 2,
  199. 3, // top
  200. ];
  201. const REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
  202. const positionScratch = new Cartesian3();
  203. const scratchStartEllipsoidNormal = new Cartesian3();
  204. const scratchStartFaceNormal = new Cartesian3();
  205. const scratchEndEllipsoidNormal = new Cartesian3();
  206. const scratchEndFaceNormal = new Cartesian3();
  207. VertexAttributesAndIndices.prototype.addVolume = function (
  208. preStartRTC,
  209. startRTC,
  210. endRTC,
  211. postEndRTC,
  212. startHeight,
  213. endHeight,
  214. halfWidth,
  215. batchId,
  216. center,
  217. ellipsoid
  218. ) {
  219. let position = Cartesian3.add(startRTC, center, positionScratch);
  220. const startEllipsoidNormal = ellipsoid.geodeticSurfaceNormal(
  221. position,
  222. scratchStartEllipsoidNormal
  223. );
  224. position = Cartesian3.add(endRTC, center, positionScratch);
  225. const endEllipsoidNormal = ellipsoid.geodeticSurfaceNormal(
  226. position,
  227. scratchEndEllipsoidNormal
  228. );
  229. const startFaceNormal = computeMiteredNormal(
  230. preStartRTC,
  231. startRTC,
  232. endRTC,
  233. startEllipsoidNormal,
  234. scratchStartFaceNormal
  235. );
  236. const endFaceNormal = computeMiteredNormal(
  237. postEndRTC,
  238. endRTC,
  239. startRTC,
  240. endEllipsoidNormal,
  241. scratchEndFaceNormal
  242. );
  243. const startEllipsoidNormals = this.startEllipsoidNormals;
  244. const endEllipsoidNormals = this.endEllipsoidNormals;
  245. const startPositionAndHeights = this.startPositionAndHeights;
  246. const startFaceNormalAndVertexCornerIds = this
  247. .startFaceNormalAndVertexCornerIds;
  248. const endPositionAndHeights = this.endPositionAndHeights;
  249. const endFaceNormalAndHalfWidths = this.endFaceNormalAndHalfWidths;
  250. const vertexBatchIds = this.vertexBatchIds;
  251. let batchIdOffset = this.batchIdOffset;
  252. let vec3Offset = this.vec3Offset;
  253. let vec4Offset = this.vec4Offset;
  254. let i;
  255. for (i = 0; i < 8; i++) {
  256. Cartesian3.pack(startEllipsoidNormal, startEllipsoidNormals, vec3Offset);
  257. Cartesian3.pack(endEllipsoidNormal, endEllipsoidNormals, vec3Offset);
  258. Cartesian3.pack(startRTC, startPositionAndHeights, vec4Offset);
  259. startPositionAndHeights[vec4Offset + 3] = startHeight;
  260. Cartesian3.pack(endRTC, endPositionAndHeights, vec4Offset);
  261. endPositionAndHeights[vec4Offset + 3] = endHeight;
  262. Cartesian3.pack(
  263. startFaceNormal,
  264. startFaceNormalAndVertexCornerIds,
  265. vec4Offset
  266. );
  267. startFaceNormalAndVertexCornerIds[vec4Offset + 3] = i;
  268. Cartesian3.pack(endFaceNormal, endFaceNormalAndHalfWidths, vec4Offset);
  269. endFaceNormalAndHalfWidths[vec4Offset + 3] = halfWidth;
  270. vertexBatchIds[batchIdOffset++] = batchId;
  271. vec3Offset += 3;
  272. vec4Offset += 4;
  273. }
  274. this.batchIdOffset = batchIdOffset;
  275. this.vec3Offset = vec3Offset;
  276. this.vec4Offset = vec4Offset;
  277. const indices = this.indices;
  278. const volumeStartIndex = this.volumeStartIndex;
  279. const indexOffset = this.indexOffset;
  280. for (i = 0; i < REFERENCE_INDICES_LENGTH; i++) {
  281. indices[indexOffset + i] = REFERENCE_INDICES[i] + volumeStartIndex;
  282. }
  283. this.volumeStartIndex += 8;
  284. this.indexOffset += REFERENCE_INDICES_LENGTH;
  285. };
  286. const scratchRectangle = new Rectangle();
  287. const scratchEllipsoid = new Ellipsoid();
  288. const scratchCenter = new Cartesian3();
  289. const scratchPrev = new Cartesian3();
  290. const scratchP0 = new Cartesian3();
  291. const scratchP1 = new Cartesian3();
  292. const scratchNext = new Cartesian3();
  293. function createVectorTileClampedPolylines(parameters, transferableObjects) {
  294. const encodedPositions = new Uint16Array(parameters.positions);
  295. const widths = new Uint16Array(parameters.widths);
  296. const counts = new Uint32Array(parameters.counts);
  297. const batchIds = new Uint16Array(parameters.batchIds);
  298. // Unpack tile decoding parameters
  299. const rectangle = scratchRectangle;
  300. const ellipsoid = scratchEllipsoid;
  301. const center = scratchCenter;
  302. const packedBuffer = new Float64Array(parameters.packedBuffer);
  303. let offset = 0;
  304. const minimumHeight = packedBuffer[offset++];
  305. const maximumHeight = packedBuffer[offset++];
  306. Rectangle.unpack(packedBuffer, offset, rectangle);
  307. offset += Rectangle.packedLength;
  308. Ellipsoid.unpack(packedBuffer, offset, ellipsoid);
  309. offset += Ellipsoid.packedLength;
  310. Cartesian3.unpack(packedBuffer, offset, center);
  311. let i;
  312. // Unpack positions and generate volumes
  313. let positionsLength = encodedPositions.length / 3;
  314. const uBuffer = encodedPositions.subarray(0, positionsLength);
  315. const vBuffer = encodedPositions.subarray(
  316. positionsLength,
  317. 2 * positionsLength
  318. );
  319. const heightBuffer = encodedPositions.subarray(
  320. 2 * positionsLength,
  321. 3 * positionsLength
  322. );
  323. AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
  324. removeDuplicates(uBuffer, vBuffer, heightBuffer, counts);
  325. // Figure out how many volumes and how many vertices there will be.
  326. const countsLength = counts.length;
  327. let volumesCount = 0;
  328. for (i = 0; i < countsLength; i++) {
  329. const polylinePositionCount = counts[i];
  330. volumesCount += polylinePositionCount - 1;
  331. }
  332. const attribsAndIndices = new VertexAttributesAndIndices(volumesCount);
  333. const positions = decodePositions(
  334. uBuffer,
  335. vBuffer,
  336. heightBuffer,
  337. rectangle,
  338. minimumHeight,
  339. maximumHeight,
  340. ellipsoid,
  341. center
  342. );
  343. positionsLength = uBuffer.length;
  344. const positionsRTC = new Float32Array(positionsLength * 3);
  345. for (i = 0; i < positionsLength; ++i) {
  346. positionsRTC[i * 3] = positions[i * 3] - center.x;
  347. positionsRTC[i * 3 + 1] = positions[i * 3 + 1] - center.y;
  348. positionsRTC[i * 3 + 2] = positions[i * 3 + 2] - center.z;
  349. }
  350. let currentPositionIndex = 0;
  351. let currentHeightIndex = 0;
  352. for (i = 0; i < countsLength; i++) {
  353. const polylineVolumeCount = counts[i] - 1;
  354. const halfWidth = widths[i] * 0.5;
  355. const batchId = batchIds[i];
  356. const volumeFirstPositionIndex = currentPositionIndex;
  357. for (let j = 0; j < polylineVolumeCount; j++) {
  358. const volumeStart = Cartesian3.unpack(
  359. positionsRTC,
  360. currentPositionIndex,
  361. scratchP0
  362. );
  363. const volumeEnd = Cartesian3.unpack(
  364. positionsRTC,
  365. currentPositionIndex + 3,
  366. scratchP1
  367. );
  368. let startHeight = heightBuffer[currentHeightIndex];
  369. let endHeight = heightBuffer[currentHeightIndex + 1];
  370. startHeight = CesiumMath.lerp(
  371. minimumHeight,
  372. maximumHeight,
  373. startHeight / MAX_SHORT
  374. );
  375. endHeight = CesiumMath.lerp(
  376. minimumHeight,
  377. maximumHeight,
  378. endHeight / MAX_SHORT
  379. );
  380. currentHeightIndex++;
  381. let preStart = scratchPrev;
  382. let postEnd = scratchNext;
  383. if (j === 0) {
  384. // Check if this volume is like a loop
  385. const finalPositionIndex =
  386. volumeFirstPositionIndex + polylineVolumeCount * 3;
  387. const finalPosition = Cartesian3.unpack(
  388. positionsRTC,
  389. finalPositionIndex,
  390. scratchPrev
  391. );
  392. if (Cartesian3.equals(finalPosition, volumeStart)) {
  393. Cartesian3.unpack(positionsRTC, finalPositionIndex - 3, preStart);
  394. } else {
  395. const offsetPastStart = Cartesian3.subtract(
  396. volumeStart,
  397. volumeEnd,
  398. scratchPrev
  399. );
  400. preStart = Cartesian3.add(offsetPastStart, volumeStart, scratchPrev);
  401. }
  402. } else {
  403. Cartesian3.unpack(positionsRTC, currentPositionIndex - 3, preStart);
  404. }
  405. if (j === polylineVolumeCount - 1) {
  406. // Check if this volume is like a loop
  407. const firstPosition = Cartesian3.unpack(
  408. positionsRTC,
  409. volumeFirstPositionIndex,
  410. scratchNext
  411. );
  412. if (Cartesian3.equals(firstPosition, volumeEnd)) {
  413. Cartesian3.unpack(
  414. positionsRTC,
  415. volumeFirstPositionIndex + 3,
  416. postEnd
  417. );
  418. } else {
  419. const offsetPastEnd = Cartesian3.subtract(
  420. volumeEnd,
  421. volumeStart,
  422. scratchNext
  423. );
  424. postEnd = Cartesian3.add(offsetPastEnd, volumeEnd, scratchNext);
  425. }
  426. } else {
  427. Cartesian3.unpack(positionsRTC, currentPositionIndex + 6, postEnd);
  428. }
  429. attribsAndIndices.addVolume(
  430. preStart,
  431. volumeStart,
  432. volumeEnd,
  433. postEnd,
  434. startHeight,
  435. endHeight,
  436. halfWidth,
  437. batchId,
  438. center,
  439. ellipsoid
  440. );
  441. currentPositionIndex += 3;
  442. }
  443. currentPositionIndex += 3;
  444. currentHeightIndex++;
  445. }
  446. const indices = attribsAndIndices.indices;
  447. transferableObjects.push(attribsAndIndices.startEllipsoidNormals.buffer);
  448. transferableObjects.push(attribsAndIndices.endEllipsoidNormals.buffer);
  449. transferableObjects.push(attribsAndIndices.startPositionAndHeights.buffer);
  450. transferableObjects.push(
  451. attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer
  452. );
  453. transferableObjects.push(attribsAndIndices.endPositionAndHeights.buffer);
  454. transferableObjects.push(attribsAndIndices.endFaceNormalAndHalfWidths.buffer);
  455. transferableObjects.push(attribsAndIndices.vertexBatchIds.buffer);
  456. transferableObjects.push(indices.buffer);
  457. let results = {
  458. indexDatatype:
  459. indices.BYTES_PER_ELEMENT === 2
  460. ? IndexDatatype.UNSIGNED_SHORT
  461. : IndexDatatype.UNSIGNED_INT,
  462. startEllipsoidNormals: attribsAndIndices.startEllipsoidNormals.buffer,
  463. endEllipsoidNormals: attribsAndIndices.endEllipsoidNormals.buffer,
  464. startPositionAndHeights: attribsAndIndices.startPositionAndHeights.buffer,
  465. startFaceNormalAndVertexCornerIds:
  466. attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer,
  467. endPositionAndHeights: attribsAndIndices.endPositionAndHeights.buffer,
  468. endFaceNormalAndHalfWidths:
  469. attribsAndIndices.endFaceNormalAndHalfWidths.buffer,
  470. vertexBatchIds: attribsAndIndices.vertexBatchIds.buffer,
  471. indices: indices.buffer,
  472. };
  473. if (parameters.keepDecodedPositions) {
  474. const positionOffsets = getPositionOffsets(counts);
  475. transferableObjects.push(positions.buffer, positionOffsets.buffer);
  476. results = combine(results, {
  477. decodedPositions: positions.buffer,
  478. decodedPositionOffsets: positionOffsets.buffer,
  479. });
  480. }
  481. return results;
  482. }
  483. export default createTaskProcessorWorker(createVectorTileClampedPolylines);