B3dmParser.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import deprecationWarning from "../Core/deprecationWarning.js";
  4. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  5. import RuntimeError from "../Core/RuntimeError.js";
  6. /**
  7. * Handles parsing of a Batched 3D Model.
  8. *
  9. * @namespace B3dmParser
  10. * @private
  11. */
  12. const B3dmParser = {};
  13. B3dmParser._deprecationWarning = deprecationWarning;
  14. const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  15. /**
  16. * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}.
  17. *
  18. * @private
  19. *
  20. * @param {ArrayBuffer} arrayBuffer The array buffer containing the b3dm.
  21. * @param {Number} [byteOffset=0] The byte offset of the beginning of the b3dm in the array buffer.
  22. * @returns {Object} Returns an object with the batch length, feature table (binary and json), batch table (binary and json) and glTF parts of the b3dm.
  23. */
  24. B3dmParser.parse = function (arrayBuffer, byteOffset) {
  25. const byteStart = defaultValue(byteOffset, 0);
  26. //>>includeStart('debug', pragmas.debug);
  27. Check.defined("arrayBuffer", arrayBuffer);
  28. //>>includeEnd('debug');
  29. byteOffset = byteStart;
  30. const uint8Array = new Uint8Array(arrayBuffer);
  31. const view = new DataView(arrayBuffer);
  32. byteOffset += sizeOfUint32; // Skip magic
  33. const version = view.getUint32(byteOffset, true);
  34. if (version !== 1) {
  35. throw new RuntimeError(
  36. `Only Batched 3D Model version 1 is supported. Version ${version} is not.`
  37. );
  38. }
  39. byteOffset += sizeOfUint32;
  40. const byteLength = view.getUint32(byteOffset, true);
  41. byteOffset += sizeOfUint32;
  42. let featureTableJsonByteLength = view.getUint32(byteOffset, true);
  43. byteOffset += sizeOfUint32;
  44. let featureTableBinaryByteLength = view.getUint32(byteOffset, true);
  45. byteOffset += sizeOfUint32;
  46. let batchTableJsonByteLength = view.getUint32(byteOffset, true);
  47. byteOffset += sizeOfUint32;
  48. let batchTableBinaryByteLength = view.getUint32(byteOffset, true);
  49. byteOffset += sizeOfUint32;
  50. let batchLength;
  51. // Legacy header #1: [batchLength] [batchTableByteLength]
  52. // Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]
  53. // Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength]
  54. // If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic.
  55. // Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length.
  56. // The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead
  57. if (batchTableJsonByteLength >= 570425344) {
  58. // First legacy check
  59. byteOffset -= sizeOfUint32 * 2;
  60. batchLength = featureTableJsonByteLength;
  61. batchTableJsonByteLength = featureTableBinaryByteLength;
  62. batchTableBinaryByteLength = 0;
  63. featureTableJsonByteLength = 0;
  64. featureTableBinaryByteLength = 0;
  65. B3dmParser._deprecationWarning(
  66. "b3dm-legacy-header",
  67. "This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel."
  68. );
  69. } else if (batchTableBinaryByteLength >= 570425344) {
  70. // Second legacy check
  71. byteOffset -= sizeOfUint32;
  72. batchLength = batchTableJsonByteLength;
  73. batchTableJsonByteLength = featureTableJsonByteLength;
  74. batchTableBinaryByteLength = featureTableBinaryByteLength;
  75. featureTableJsonByteLength = 0;
  76. featureTableBinaryByteLength = 0;
  77. B3dmParser._deprecationWarning(
  78. "b3dm-legacy-header",
  79. "This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel."
  80. );
  81. }
  82. let featureTableJson;
  83. if (featureTableJsonByteLength === 0) {
  84. featureTableJson = {
  85. BATCH_LENGTH: defaultValue(batchLength, 0),
  86. };
  87. } else {
  88. featureTableJson = getJsonFromTypedArray(
  89. uint8Array,
  90. byteOffset,
  91. featureTableJsonByteLength
  92. );
  93. byteOffset += featureTableJsonByteLength;
  94. }
  95. const featureTableBinary = new Uint8Array(
  96. arrayBuffer,
  97. byteOffset,
  98. featureTableBinaryByteLength
  99. );
  100. byteOffset += featureTableBinaryByteLength;
  101. let batchTableJson;
  102. let batchTableBinary;
  103. if (batchTableJsonByteLength > 0) {
  104. // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the
  105. // arraybuffer/string compressed in memory and then decompress it when it is first accessed.
  106. //
  107. // We could also make another request for it, but that would make the property set/get
  108. // API async, and would double the number of numbers in some cases.
  109. batchTableJson = getJsonFromTypedArray(
  110. uint8Array,
  111. byteOffset,
  112. batchTableJsonByteLength
  113. );
  114. byteOffset += batchTableJsonByteLength;
  115. if (batchTableBinaryByteLength > 0) {
  116. // Has a batch table binary
  117. batchTableBinary = new Uint8Array(
  118. arrayBuffer,
  119. byteOffset,
  120. batchTableBinaryByteLength
  121. );
  122. // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
  123. batchTableBinary = new Uint8Array(batchTableBinary);
  124. byteOffset += batchTableBinaryByteLength;
  125. }
  126. }
  127. const gltfByteLength = byteStart + byteLength - byteOffset;
  128. if (gltfByteLength === 0) {
  129. throw new RuntimeError("glTF byte length must be greater than 0.");
  130. }
  131. let gltfView;
  132. if (byteOffset % 4 === 0) {
  133. gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength);
  134. } else {
  135. // Create a copy of the glb so that it is 4-byte aligned
  136. B3dmParser._deprecationWarning(
  137. "b3dm-glb-unaligned",
  138. "The embedded glb is not aligned to a 4-byte boundary."
  139. );
  140. gltfView = new Uint8Array(
  141. uint8Array.subarray(byteOffset, byteOffset + gltfByteLength)
  142. );
  143. }
  144. return {
  145. batchLength: batchLength,
  146. featureTableJson: featureTableJson,
  147. featureTableBinary: featureTableBinary,
  148. batchTableJson: batchTableJson,
  149. batchTableBinary: batchTableBinary,
  150. gltf: gltfView,
  151. };
  152. };
  153. export default B3dmParser;