PntsParser.js 14 KB

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