Add existing to tracked
This commit is contained in:
+547
@@ -0,0 +1,547 @@
|
||||
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 defined from "../Core/defined.js";
|
||||
import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
|
||||
import oneTimeWarning from "../Core/oneTimeWarning.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 = 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,
|
||||
);
|
||||
if (Object.keys(batchTableJson).length === 0) {
|
||||
batchTableJson = undefined;
|
||||
}
|
||||
byteOffset += batchTableJsonByteLength;
|
||||
|
||||
if (batchTableBinaryByteLength > 0) {
|
||||
// Has a batch table binary
|
||||
batchTableBinary = new Uint8Array(
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
batchTableBinaryByteLength,
|
||||
);
|
||||
// Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
|
||||
batchTableBinary = new Uint8Array(batchTableBinary);
|
||||
}
|
||||
}
|
||||
|
||||
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(batchTableJson) || defined(batchTableBinary)) {
|
||||
parsedContent.batchTableJson = batchTableJson;
|
||||
parsedContent.batchTableBinary = batchTableBinary;
|
||||
}
|
||||
|
||||
// Handle the case that binary body references are contained in the
|
||||
// batch table without a batch table binary being present
|
||||
removeInvalidBinaryBodyReferences(parsedContent);
|
||||
|
||||
return parsedContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all invalid binary body references from the batch table
|
||||
* JSON of the given parsed content.
|
||||
*
|
||||
* This is a workaround for gracefully handling the invalid PNTS
|
||||
* files that may have been created by the point cloud tiler.
|
||||
* See https://github.com/CesiumGS/cesium/issues/12872
|
||||
*
|
||||
* When the batch table JSON is undefined, nothing will be done.
|
||||
* When the batch table binary is defined, nothing will be done
|
||||
* (assuming that any binary body references are valid - this is
|
||||
* not checked here).
|
||||
*
|
||||
* Otherwise, this will remove all binary body references from the
|
||||
* batch table JSON that are not resolved from draco via the
|
||||
* `parsedContent.draco.batchTableProperties`.
|
||||
*
|
||||
* If any (invalid) binary body reference is found (and removed),
|
||||
* a one-time warning will be printed.
|
||||
*
|
||||
* @param {object} parsedContent The parsed content
|
||||
* @ignore
|
||||
*/
|
||||
function removeInvalidBinaryBodyReferences(parsedContent) {
|
||||
const batchTableJson = parsedContent.batchTableJson;
|
||||
if (!defined(batchTableJson)) {
|
||||
return;
|
||||
}
|
||||
const batchTableBinary = parsedContent.batchTableBinary;
|
||||
if (defined(batchTableBinary)) {
|
||||
return;
|
||||
}
|
||||
const dracoBatchTablePropertyNames = Object.keys(
|
||||
parsedContent.draco?.batchTableProperties ?? {},
|
||||
);
|
||||
|
||||
// Collect the names of all binary body references (identified
|
||||
// by the property having a `byteOffset`) that have not been
|
||||
// resolved via the parsedContent.draco.batchTableProperties
|
||||
const invalidBinaryBodyReferenceNames = [];
|
||||
for (const name of Object.keys(batchTableJson)) {
|
||||
const property = batchTableJson[name];
|
||||
const byteOffset = property.byteOffset;
|
||||
if (defined(byteOffset)) {
|
||||
if (!dracoBatchTablePropertyNames.includes(name)) {
|
||||
invalidBinaryBodyReferenceNames.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there have been invalid binary body references, print
|
||||
// a one-time warning for each of them, and delete them.
|
||||
for (const name of invalidBinaryBodyReferenceNames) {
|
||||
oneTimeWarning(
|
||||
`PntsParser-invalidBinaryBodyReference`,
|
||||
`The point cloud data contained a binary property ${name} that could not be resolved - skipping`,
|
||||
);
|
||||
delete batchTableJson[name];
|
||||
}
|
||||
}
|
||||
|
||||
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 = featureTable.buffer.slice(
|
||||
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 Model 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;
|
||||
Reference in New Issue
Block a user