PntsParser.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Check from "../Core/Check.js";
  3. import Color from "../Core/Color.js";
  4. import combine from "../Core/combine.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  9. import RuntimeError from "../Core/RuntimeError.js";
  10. import AttributeType from "./AttributeType.js";
  11. import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js";
  12. import VertexAttributeSemantic from "./VertexAttributeSemantic.js";
  13. /**
  14. * Handles parsing of a Point Cloud
  15. *
  16. * @namespace PntsParser
  17. * @private
  18. */
  19. const PntsParser = {};
  20. const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  21. /**
  22. * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/PointCloud|Point Cloud}.
  23. *
  24. * @private
  25. *
  26. * @param {*} arrayBuffer The array buffer containing the pnts
  27. * @param {*} [byteOffset=0] The byte offset of the beginning of the pnts in the array buffer
  28. * @returns {object} An object containing a parsed representation of the point cloud
  29. */
  30. PntsParser.parse = function (arrayBuffer, byteOffset) {
  31. byteOffset = defaultValue(byteOffset, 0);
  32. //>>includeStart('debug', pragmas.debug);
  33. Check.defined("arrayBuffer", arrayBuffer);
  34. //>>includeEnd('debug');
  35. const uint8Array = new Uint8Array(arrayBuffer);
  36. const view = new DataView(arrayBuffer);
  37. byteOffset += sizeOfUint32; // Skip magic
  38. const version = view.getUint32(byteOffset, true);
  39. if (version !== 1) {
  40. throw new RuntimeError(
  41. `Only Point Cloud tile version 1 is supported. Version ${version} is not.`
  42. );
  43. }
  44. byteOffset += sizeOfUint32;
  45. // Skip byteLength
  46. byteOffset += sizeOfUint32;
  47. const featureTableJsonByteLength = view.getUint32(byteOffset, true);
  48. if (featureTableJsonByteLength === 0) {
  49. throw new RuntimeError(
  50. "Feature table must have a byte length greater than zero"
  51. );
  52. }
  53. byteOffset += sizeOfUint32;
  54. const featureTableBinaryByteLength = view.getUint32(byteOffset, true);
  55. byteOffset += sizeOfUint32;
  56. const batchTableJsonByteLength = view.getUint32(byteOffset, true);
  57. byteOffset += sizeOfUint32;
  58. const batchTableBinaryByteLength = view.getUint32(byteOffset, true);
  59. byteOffset += sizeOfUint32;
  60. const featureTableJson = getJsonFromTypedArray(
  61. uint8Array,
  62. byteOffset,
  63. featureTableJsonByteLength
  64. );
  65. byteOffset += featureTableJsonByteLength;
  66. const featureTableBinary = new Uint8Array(
  67. arrayBuffer,
  68. byteOffset,
  69. featureTableBinaryByteLength
  70. );
  71. byteOffset += featureTableBinaryByteLength;
  72. // Get the batch table JSON and binary
  73. let batchTableJson;
  74. let batchTableBinary;
  75. if (batchTableJsonByteLength > 0) {
  76. // Has a batch table JSON
  77. batchTableJson = getJsonFromTypedArray(
  78. uint8Array,
  79. byteOffset,
  80. batchTableJsonByteLength
  81. );
  82. byteOffset += batchTableJsonByteLength;
  83. if (batchTableBinaryByteLength > 0) {
  84. // Has a batch table binary
  85. batchTableBinary = new Uint8Array(
  86. arrayBuffer,
  87. byteOffset,
  88. batchTableBinaryByteLength
  89. );
  90. byteOffset += batchTableBinaryByteLength;
  91. }
  92. }
  93. const featureTable = new Cesium3DTileFeatureTable(
  94. featureTableJson,
  95. featureTableBinary
  96. );
  97. const pointsLength = featureTable.getGlobalProperty("POINTS_LENGTH");
  98. featureTable.featuresLength = pointsLength;
  99. if (!defined(pointsLength)) {
  100. throw new RuntimeError(
  101. "Feature table global property: POINTS_LENGTH must be defined"
  102. );
  103. }
  104. let rtcCenter = featureTable.getGlobalProperty(
  105. "RTC_CENTER",
  106. ComponentDatatype.FLOAT,
  107. 3
  108. );
  109. if (defined(rtcCenter)) {
  110. rtcCenter = Cartesian3.unpack(rtcCenter);
  111. }
  112. // Start with the draco compressed properties and add in uncompressed
  113. // properties.
  114. const parsedContent = parseDracoProperties(featureTable, batchTableJson);
  115. parsedContent.rtcCenter = rtcCenter;
  116. parsedContent.pointsLength = pointsLength;
  117. if (!parsedContent.hasPositions) {
  118. const positions = parsePositions(featureTable);
  119. parsedContent.positions = positions;
  120. parsedContent.hasPositions =
  121. parsedContent.hasPositions || defined(positions);
  122. }
  123. if (!parsedContent.hasPositions) {
  124. throw new RuntimeError(
  125. "Either POSITION or POSITION_QUANTIZED must be defined."
  126. );
  127. }
  128. if (!parsedContent.hasNormals) {
  129. const normals = parseNormals(featureTable);
  130. parsedContent.normals = normals;
  131. parsedContent.hasNormals = parsedContent.hasNormals || defined(normals);
  132. }
  133. if (!parsedContent.hasColors) {
  134. const colors = parseColors(featureTable);
  135. parsedContent.colors = colors;
  136. parsedContent.hasColors = parsedContent.hasColors || defined(colors);
  137. parsedContent.hasConstantColor = defined(parsedContent.constantColor);
  138. parsedContent.isTranslucent = defined(colors) && colors.isTranslucent;
  139. }
  140. if (!parsedContent.hasBatchIds) {
  141. const batchIds = parseBatchIds(featureTable);
  142. parsedContent.batchIds = batchIds;
  143. parsedContent.hasBatchIds = parsedContent.hasBatchIds || defined(batchIds);
  144. }
  145. if (parsedContent.hasBatchIds) {
  146. const batchLength = featureTable.getGlobalProperty("BATCH_LENGTH");
  147. if (!defined(batchLength)) {
  148. throw new RuntimeError(
  149. "Global property: BATCH_LENGTH must be defined when BATCH_ID is defined."
  150. );
  151. }
  152. parsedContent.batchLength = batchLength;
  153. }
  154. if (defined(batchTableBinary)) {
  155. // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
  156. batchTableBinary = new Uint8Array(batchTableBinary);
  157. parsedContent.batchTableJson = batchTableJson;
  158. parsedContent.batchTableBinary = batchTableBinary;
  159. }
  160. return parsedContent;
  161. };
  162. function parseDracoProperties(featureTable, batchTableJson) {
  163. const featureTableJson = featureTable.json;
  164. let dracoBuffer;
  165. let dracoFeatureTableProperties;
  166. let dracoBatchTableProperties;
  167. const featureTableDraco = defined(featureTableJson.extensions)
  168. ? featureTableJson.extensions["3DTILES_draco_point_compression"]
  169. : undefined;
  170. const batchTableDraco =
  171. defined(batchTableJson) && defined(batchTableJson.extensions)
  172. ? batchTableJson.extensions["3DTILES_draco_point_compression"]
  173. : undefined;
  174. if (defined(batchTableDraco)) {
  175. dracoBatchTableProperties = batchTableDraco.properties;
  176. }
  177. let hasPositions;
  178. let hasColors;
  179. let hasNormals;
  180. let hasBatchIds;
  181. let isTranslucent;
  182. if (defined(featureTableDraco)) {
  183. dracoFeatureTableProperties = featureTableDraco.properties;
  184. const dracoByteOffset = featureTableDraco.byteOffset;
  185. const dracoByteLength = featureTableDraco.byteLength;
  186. if (
  187. !defined(dracoFeatureTableProperties) ||
  188. !defined(dracoByteOffset) ||
  189. !defined(dracoByteLength)
  190. ) {
  191. throw new RuntimeError(
  192. "Draco properties, byteOffset, and byteLength must be defined"
  193. );
  194. }
  195. dracoBuffer = featureTable.buffer.slice(
  196. dracoByteOffset,
  197. dracoByteOffset + dracoByteLength
  198. );
  199. hasPositions = defined(dracoFeatureTableProperties.POSITION);
  200. hasColors =
  201. defined(dracoFeatureTableProperties.RGB) ||
  202. defined(dracoFeatureTableProperties.RGBA);
  203. hasNormals = defined(dracoFeatureTableProperties.NORMAL);
  204. hasBatchIds = defined(dracoFeatureTableProperties.BATCH_ID);
  205. isTranslucent = defined(dracoFeatureTableProperties.RGBA);
  206. }
  207. let draco;
  208. if (defined(dracoBuffer)) {
  209. draco = {
  210. buffer: dracoBuffer,
  211. featureTableProperties: dracoFeatureTableProperties,
  212. batchTableProperties: dracoBatchTableProperties,
  213. properties: combine(
  214. dracoFeatureTableProperties,
  215. dracoBatchTableProperties
  216. ),
  217. dequantizeInShader: true,
  218. };
  219. }
  220. return {
  221. draco: draco,
  222. hasPositions: hasPositions,
  223. hasColors: hasColors,
  224. isTranslucent: isTranslucent,
  225. hasNormals: hasNormals,
  226. hasBatchIds: hasBatchIds,
  227. };
  228. }
  229. function parsePositions(featureTable) {
  230. const featureTableJson = featureTable.json;
  231. let positions;
  232. if (defined(featureTableJson.POSITION)) {
  233. positions = featureTable.getPropertyArray(
  234. "POSITION",
  235. ComponentDatatype.FLOAT,
  236. 3
  237. );
  238. return {
  239. name: VertexAttributeSemantic.POSITION,
  240. semantic: VertexAttributeSemantic.POSITION,
  241. typedArray: positions,
  242. isQuantized: false,
  243. componentDatatype: ComponentDatatype.FLOAT,
  244. type: AttributeType.VEC3,
  245. };
  246. } else if (defined(featureTableJson.POSITION_QUANTIZED)) {
  247. positions = featureTable.getPropertyArray(
  248. "POSITION_QUANTIZED",
  249. ComponentDatatype.UNSIGNED_SHORT,
  250. 3
  251. );
  252. const quantizedVolumeScale = featureTable.getGlobalProperty(
  253. "QUANTIZED_VOLUME_SCALE",
  254. ComponentDatatype.FLOAT,
  255. 3
  256. );
  257. if (!defined(quantizedVolumeScale)) {
  258. throw new RuntimeError(
  259. "Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions."
  260. );
  261. }
  262. const quantizedRange = (1 << 16) - 1;
  263. const quantizedVolumeOffset = featureTable.getGlobalProperty(
  264. "QUANTIZED_VOLUME_OFFSET",
  265. ComponentDatatype.FLOAT,
  266. 3
  267. );
  268. if (!defined(quantizedVolumeOffset)) {
  269. throw new RuntimeError(
  270. "Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions."
  271. );
  272. }
  273. return {
  274. name: VertexAttributeSemantic.POSITION,
  275. semantic: VertexAttributeSemantic.POSITION,
  276. typedArray: positions,
  277. isQuantized: true,
  278. componentDatatype: ComponentDatatype.FLOAT,
  279. type: AttributeType.VEC3,
  280. quantizedRange: quantizedRange,
  281. quantizedVolumeOffset: Cartesian3.unpack(quantizedVolumeOffset),
  282. quantizedVolumeScale: Cartesian3.unpack(quantizedVolumeScale),
  283. quantizedComponentDatatype: ComponentDatatype.UNSIGNED_SHORT,
  284. quantizedType: AttributeType.VEC3,
  285. };
  286. }
  287. }
  288. function parseColors(featureTable) {
  289. const featureTableJson = featureTable.json;
  290. let colors;
  291. if (defined(featureTableJson.RGBA)) {
  292. colors = featureTable.getPropertyArray(
  293. "RGBA",
  294. ComponentDatatype.UNSIGNED_BYTE,
  295. 4
  296. );
  297. return {
  298. name: VertexAttributeSemantic.COLOR,
  299. semantic: VertexAttributeSemantic.COLOR,
  300. setIndex: 0,
  301. typedArray: colors,
  302. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  303. type: AttributeType.VEC4,
  304. normalized: true,
  305. isRGB565: false,
  306. isTranslucent: true,
  307. };
  308. } else if (defined(featureTableJson.RGB)) {
  309. colors = featureTable.getPropertyArray(
  310. "RGB",
  311. ComponentDatatype.UNSIGNED_BYTE,
  312. 3
  313. );
  314. return {
  315. name: "COLOR",
  316. semantic: VertexAttributeSemantic.COLOR,
  317. setIndex: 0,
  318. typedArray: colors,
  319. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  320. type: AttributeType.VEC3,
  321. normalized: true,
  322. isRGB565: false,
  323. isTranslucent: false,
  324. };
  325. } else if (defined(featureTableJson.RGB565)) {
  326. colors = featureTable.getPropertyArray(
  327. "RGB565",
  328. ComponentDatatype.UNSIGNED_SHORT,
  329. 1
  330. );
  331. return {
  332. name: "COLOR",
  333. semantic: VertexAttributeSemantic.COLOR,
  334. setIndex: 0,
  335. typedArray: colors,
  336. // These settings are for the Model implementation
  337. // which decodes on the CPU and uploads a VEC3 of float colors.
  338. // PointCloud does the decoding on the GPU so uploads a
  339. // UNSIGNED_SHORT instead.
  340. componentDatatype: ComponentDatatype.FLOAT,
  341. type: AttributeType.VEC3,
  342. normalized: false,
  343. isRGB565: true,
  344. isTranslucent: false,
  345. };
  346. } else if (defined(featureTableJson.CONSTANT_RGBA)) {
  347. const constantRGBA = featureTable.getGlobalProperty(
  348. "CONSTANT_RGBA",
  349. ComponentDatatype.UNSIGNED_BYTE,
  350. 4
  351. );
  352. const alpha = constantRGBA[3];
  353. const constantColor = Color.fromBytes(
  354. constantRGBA[0],
  355. constantRGBA[1],
  356. constantRGBA[2],
  357. alpha
  358. );
  359. const isTranslucent = alpha < 255;
  360. return {
  361. name: VertexAttributeSemantic.COLOR,
  362. semantic: VertexAttributeSemantic.COLOR,
  363. setIndex: 0,
  364. constantColor: constantColor,
  365. componentDatatype: ComponentDatatype.FLOAT,
  366. type: AttributeType.VEC4,
  367. isQuantized: false,
  368. isTranslucent: isTranslucent,
  369. };
  370. }
  371. return undefined;
  372. }
  373. function parseNormals(featureTable) {
  374. const featureTableJson = featureTable.json;
  375. let normals;
  376. if (defined(featureTableJson.NORMAL)) {
  377. normals = featureTable.getPropertyArray(
  378. "NORMAL",
  379. ComponentDatatype.FLOAT,
  380. 3
  381. );
  382. return {
  383. name: VertexAttributeSemantic.NORMAL,
  384. semantic: VertexAttributeSemantic.NORMAL,
  385. typedArray: normals,
  386. octEncoded: false,
  387. octEncodedZXY: false,
  388. componentDatatype: ComponentDatatype.FLOAT,
  389. type: AttributeType.VEC3,
  390. };
  391. } else if (defined(featureTableJson.NORMAL_OCT16P)) {
  392. normals = featureTable.getPropertyArray(
  393. "NORMAL_OCT16P",
  394. ComponentDatatype.UNSIGNED_BYTE,
  395. 2
  396. );
  397. const quantizationBits = 8;
  398. return {
  399. name: VertexAttributeSemantic.NORMAL,
  400. semantic: VertexAttributeSemantic.NORMAL,
  401. typedArray: normals,
  402. octEncoded: true,
  403. octEncodedZXY: false,
  404. quantizedRange: (1 << quantizationBits) - 1,
  405. quantizedType: AttributeType.VEC2,
  406. quantizedComponentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  407. componentDatatype: ComponentDatatype.FLOAT,
  408. type: AttributeType.VEC3,
  409. };
  410. }
  411. return undefined;
  412. }
  413. function parseBatchIds(featureTable) {
  414. const featureTableJson = featureTable.json;
  415. if (defined(featureTableJson.BATCH_ID)) {
  416. const batchIds = featureTable.getPropertyArray(
  417. "BATCH_ID",
  418. ComponentDatatype.UNSIGNED_SHORT,
  419. 1
  420. );
  421. return {
  422. name: VertexAttributeSemantic.FEATURE_ID,
  423. semantic: VertexAttributeSemantic.FEATURE_ID,
  424. setIndex: 0,
  425. typedArray: batchIds,
  426. componentDatatype: ComponentDatatype.fromTypedArray(batchIds),
  427. type: AttributeType.SCALAR,
  428. };
  429. }
  430. return undefined;
  431. }
  432. export default PntsParser;