| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 | import arraySlice from "../Core/arraySlice.js";import Cartesian3 from "../Core/Cartesian3.js";import Check from "../Core/Check.js";import Color from "../Core/Color.js";import combine from "../Core/combine.js";import ComponentDatatype from "../Core/ComponentDatatype.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";import RuntimeError from "../Core/RuntimeError.js";import AttributeType from "./AttributeType.js";import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js";import VertexAttributeSemantic from "./VertexAttributeSemantic.js";/** * Handles parsing of a Point Cloud * * @namespace PntsParser * @private */const PntsParser = {};const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;/** * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/PointCloud|Point Cloud}. * * @private * * @param {*} arrayBuffer The array buffer containing the pnts * @param {*} [byteOffset=0] The byte offset of the beginning of the pnts in the array buffer * @returns {Object} An object containing a parsed representation of the point cloud */PntsParser.parse = function (arrayBuffer, byteOffset) {  byteOffset = defaultValue(byteOffset, 0);  //>>includeStart('debug', pragmas.debug);  Check.defined("arrayBuffer", arrayBuffer);  //>>includeEnd('debug');  const uint8Array = new Uint8Array(arrayBuffer);  const view = new DataView(arrayBuffer);  byteOffset += sizeOfUint32; // Skip magic  const version = view.getUint32(byteOffset, true);  if (version !== 1) {    throw new RuntimeError(      `Only Point Cloud tile version 1 is supported.  Version ${version} is not.`    );  }  byteOffset += sizeOfUint32;  // Skip byteLength  byteOffset += sizeOfUint32;  const featureTableJsonByteLength = view.getUint32(byteOffset, true);  if (featureTableJsonByteLength === 0) {    throw new RuntimeError(      "Feature table must have a byte length greater than zero"    );  }  byteOffset += sizeOfUint32;  const featureTableBinaryByteLength = view.getUint32(byteOffset, true);  byteOffset += sizeOfUint32;  const batchTableJsonByteLength = view.getUint32(byteOffset, true);  byteOffset += sizeOfUint32;  const batchTableBinaryByteLength = view.getUint32(byteOffset, true);  byteOffset += sizeOfUint32;  const featureTableJson = getJsonFromTypedArray(    uint8Array,    byteOffset,    featureTableJsonByteLength  );  byteOffset += featureTableJsonByteLength;  const featureTableBinary = new Uint8Array(    arrayBuffer,    byteOffset,    featureTableBinaryByteLength  );  byteOffset += featureTableBinaryByteLength;  // Get the batch table JSON and binary  let batchTableJson;  let batchTableBinary;  if (batchTableJsonByteLength > 0) {    // Has a batch table JSON    batchTableJson = getJsonFromTypedArray(      uint8Array,      byteOffset,      batchTableJsonByteLength    );    byteOffset += batchTableJsonByteLength;    if (batchTableBinaryByteLength > 0) {      // Has a batch table binary      batchTableBinary = new Uint8Array(        arrayBuffer,        byteOffset,        batchTableBinaryByteLength      );      byteOffset += batchTableBinaryByteLength;    }  }  const featureTable = new Cesium3DTileFeatureTable(    featureTableJson,    featureTableBinary  );  const pointsLength = featureTable.getGlobalProperty("POINTS_LENGTH");  featureTable.featuresLength = pointsLength;  if (!defined(pointsLength)) {    throw new RuntimeError(      "Feature table global property: POINTS_LENGTH must be defined"    );  }  let rtcCenter = featureTable.getGlobalProperty(    "RTC_CENTER",    ComponentDatatype.FLOAT,    3  );  if (defined(rtcCenter)) {    rtcCenter = Cartesian3.unpack(rtcCenter);  }  // Start with the draco compressed properties and add in uncompressed  // properties.  const parsedContent = parseDracoProperties(featureTable, batchTableJson);  parsedContent.rtcCenter = rtcCenter;  parsedContent.pointsLength = pointsLength;  if (!parsedContent.hasPositions) {    const positions = parsePositions(featureTable);    parsedContent.positions = positions;    parsedContent.hasPositions =      parsedContent.hasPositions || defined(positions);  }  if (!parsedContent.hasPositions) {    throw new RuntimeError(      "Either POSITION or POSITION_QUANTIZED must be defined."    );  }  if (!parsedContent.hasNormals) {    const normals = parseNormals(featureTable);    parsedContent.normals = normals;    parsedContent.hasNormals = parsedContent.hasNormals || defined(normals);  }  if (!parsedContent.hasColors) {    const colors = parseColors(featureTable);    parsedContent.colors = colors;    parsedContent.hasColors = parsedContent.hasColors || defined(colors);    parsedContent.hasConstantColor = defined(parsedContent.constantColor);    parsedContent.isTranslucent = defined(colors) && colors.isTranslucent;  }  if (!parsedContent.hasBatchIds) {    const batchIds = parseBatchIds(featureTable);    parsedContent.batchIds = batchIds;    parsedContent.hasBatchIds = parsedContent.hasBatchIds || defined(batchIds);  }  if (parsedContent.hasBatchIds) {    const batchLength = featureTable.getGlobalProperty("BATCH_LENGTH");    if (!defined(batchLength)) {      throw new RuntimeError(        "Global property: BATCH_LENGTH must be defined when BATCH_ID is defined."      );    }    parsedContent.batchLength = batchLength;  }  if (defined(batchTableBinary)) {    // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed    batchTableBinary = new Uint8Array(batchTableBinary);    parsedContent.batchTableJson = batchTableJson;    parsedContent.batchTableBinary = batchTableBinary;  }  return parsedContent;};function parseDracoProperties(featureTable, batchTableJson) {  const featureTableJson = featureTable.json;  let dracoBuffer;  let dracoFeatureTableProperties;  let dracoBatchTableProperties;  const featureTableDraco = defined(featureTableJson.extensions)    ? featureTableJson.extensions["3DTILES_draco_point_compression"]    : undefined;  const batchTableDraco =    defined(batchTableJson) && defined(batchTableJson.extensions)      ? batchTableJson.extensions["3DTILES_draco_point_compression"]      : undefined;  if (defined(batchTableDraco)) {    dracoBatchTableProperties = batchTableDraco.properties;  }  let hasPositions;  let hasColors;  let hasNormals;  let hasBatchIds;  let isTranslucent;  if (defined(featureTableDraco)) {    dracoFeatureTableProperties = featureTableDraco.properties;    const dracoByteOffset = featureTableDraco.byteOffset;    const dracoByteLength = featureTableDraco.byteLength;    if (      !defined(dracoFeatureTableProperties) ||      !defined(dracoByteOffset) ||      !defined(dracoByteLength)    ) {      throw new RuntimeError(        "Draco properties, byteOffset, and byteLength must be defined"      );    }    dracoBuffer = arraySlice(      featureTable.buffer,      dracoByteOffset,      dracoByteOffset + dracoByteLength    );    hasPositions = defined(dracoFeatureTableProperties.POSITION);    hasColors =      defined(dracoFeatureTableProperties.RGB) ||      defined(dracoFeatureTableProperties.RGBA);    hasNormals = defined(dracoFeatureTableProperties.NORMAL);    hasBatchIds = defined(dracoFeatureTableProperties.BATCH_ID);    isTranslucent = defined(dracoFeatureTableProperties.RGBA);  }  let draco;  if (defined(dracoBuffer)) {    draco = {      buffer: dracoBuffer,      featureTableProperties: dracoFeatureTableProperties,      batchTableProperties: dracoBatchTableProperties,      properties: combine(        dracoFeatureTableProperties,        dracoBatchTableProperties      ),      dequantizeInShader: true,    };  }  return {    draco: draco,    hasPositions: hasPositions,    hasColors: hasColors,    isTranslucent: isTranslucent,    hasNormals: hasNormals,    hasBatchIds: hasBatchIds,  };}function parsePositions(featureTable) {  const featureTableJson = featureTable.json;  let positions;  if (defined(featureTableJson.POSITION)) {    positions = featureTable.getPropertyArray(      "POSITION",      ComponentDatatype.FLOAT,      3    );    return {      name: VertexAttributeSemantic.POSITION,      semantic: VertexAttributeSemantic.POSITION,      typedArray: positions,      isQuantized: false,      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC3,    };  } else if (defined(featureTableJson.POSITION_QUANTIZED)) {    positions = featureTable.getPropertyArray(      "POSITION_QUANTIZED",      ComponentDatatype.UNSIGNED_SHORT,      3    );    const quantizedVolumeScale = featureTable.getGlobalProperty(      "QUANTIZED_VOLUME_SCALE",      ComponentDatatype.FLOAT,      3    );    if (!defined(quantizedVolumeScale)) {      throw new RuntimeError(        "Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions."      );    }    const quantizedRange = (1 << 16) - 1;    const quantizedVolumeOffset = featureTable.getGlobalProperty(      "QUANTIZED_VOLUME_OFFSET",      ComponentDatatype.FLOAT,      3    );    if (!defined(quantizedVolumeOffset)) {      throw new RuntimeError(        "Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions."      );    }    return {      name: VertexAttributeSemantic.POSITION,      semantic: VertexAttributeSemantic.POSITION,      typedArray: positions,      isQuantized: true,      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC3,      quantizedRange: quantizedRange,      quantizedVolumeOffset: Cartesian3.unpack(quantizedVolumeOffset),      quantizedVolumeScale: Cartesian3.unpack(quantizedVolumeScale),      quantizedComponentDatatype: ComponentDatatype.UNSIGNED_SHORT,      quantizedType: AttributeType.VEC3,    };  }}function parseColors(featureTable) {  const featureTableJson = featureTable.json;  let colors;  if (defined(featureTableJson.RGBA)) {    colors = featureTable.getPropertyArray(      "RGBA",      ComponentDatatype.UNSIGNED_BYTE,      4    );    return {      name: VertexAttributeSemantic.COLOR,      semantic: VertexAttributeSemantic.COLOR,      setIndex: 0,      typedArray: colors,      componentDatatype: ComponentDatatype.UNSIGNED_BYTE,      type: AttributeType.VEC4,      normalized: true,      isRGB565: false,      isTranslucent: true,    };  } else if (defined(featureTableJson.RGB)) {    colors = featureTable.getPropertyArray(      "RGB",      ComponentDatatype.UNSIGNED_BYTE,      3    );    return {      name: "COLOR",      semantic: VertexAttributeSemantic.COLOR,      setIndex: 0,      typedArray: colors,      componentDatatype: ComponentDatatype.UNSIGNED_BYTE,      type: AttributeType.VEC3,      normalized: true,      isRGB565: false,      isTranslucent: false,    };  } else if (defined(featureTableJson.RGB565)) {    colors = featureTable.getPropertyArray(      "RGB565",      ComponentDatatype.UNSIGNED_SHORT,      1    );    return {      name: "COLOR",      semantic: VertexAttributeSemantic.COLOR,      setIndex: 0,      typedArray: colors,      // These settings are for the ModelExperimental implementation      // which decodes on the CPU and uploads a VEC3 of float colors.      // PointCloud does the decoding on the GPU so uploads a      // UNSIGNED_SHORT instead.      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC3,      normalized: false,      isRGB565: true,      isTranslucent: false,    };  } else if (defined(featureTableJson.CONSTANT_RGBA)) {    const constantRGBA = featureTable.getGlobalProperty(      "CONSTANT_RGBA",      ComponentDatatype.UNSIGNED_BYTE,      4    );    const alpha = constantRGBA[3];    const constantColor = Color.fromBytes(      constantRGBA[0],      constantRGBA[1],      constantRGBA[2],      alpha    );    const isTranslucent = alpha < 255;    return {      name: VertexAttributeSemantic.COLOR,      semantic: VertexAttributeSemantic.COLOR,      setIndex: 0,      constantColor: constantColor,      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC4,      isQuantized: false,      isTranslucent: isTranslucent,    };  }  return undefined;}function parseNormals(featureTable) {  const featureTableJson = featureTable.json;  let normals;  if (defined(featureTableJson.NORMAL)) {    normals = featureTable.getPropertyArray(      "NORMAL",      ComponentDatatype.FLOAT,      3    );    return {      name: VertexAttributeSemantic.NORMAL,      semantic: VertexAttributeSemantic.NORMAL,      typedArray: normals,      octEncoded: false,      octEncodedZXY: false,      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC3,    };  } else if (defined(featureTableJson.NORMAL_OCT16P)) {    normals = featureTable.getPropertyArray(      "NORMAL_OCT16P",      ComponentDatatype.UNSIGNED_BYTE,      2    );    const quantizationBits = 8;    return {      name: VertexAttributeSemantic.NORMAL,      semantic: VertexAttributeSemantic.NORMAL,      typedArray: normals,      octEncoded: true,      octEncodedZXY: false,      quantizedRange: (1 << quantizationBits) - 1,      quantizedType: AttributeType.VEC2,      quantizedComponentDatatype: ComponentDatatype.UNSIGNED_BYTE,      componentDatatype: ComponentDatatype.FLOAT,      type: AttributeType.VEC3,    };  }  return undefined;}function parseBatchIds(featureTable) {  const featureTableJson = featureTable.json;  if (defined(featureTableJson.BATCH_ID)) {    const batchIds = featureTable.getPropertyArray(      "BATCH_ID",      ComponentDatatype.UNSIGNED_SHORT,      1    );    return {      name: VertexAttributeSemantic.FEATURE_ID,      semantic: VertexAttributeSemantic.FEATURE_ID,      setIndex: 0,      typedArray: batchIds,      componentDatatype: ComponentDatatype.fromTypedArray(batchIds),      type: AttributeType.SCALAR,    };  }  return undefined;}export default PntsParser;
 |