2021-04-14 02:56:31 +08:00
import Check from "../Core/Check.js" ;
2022-07-15 21:04:34 +08:00
import ComponentDatatype from "../Core/ComponentDatatype.js" ;
2021-04-14 02:56:31 +08:00
import defined from "../Core/defined.js" ;
import deprecationWarning from "../Core/deprecationWarning.js" ;
2022-07-15 21:04:34 +08:00
import DeveloperError from "../Core/DeveloperError.js" ;
2021-04-16 01:03:39 +08:00
import RuntimeError from "../Core/RuntimeError.js" ;
2021-04-14 02:56:31 +08:00
import BatchTableHierarchy from "./BatchTableHierarchy.js" ;
2022-02-22 04:52:32 +08:00
import StructuralMetadata from "./StructuralMetadata.js" ;
2022-07-15 21:04:34 +08:00
import PropertyAttribute from "./PropertyAttribute.js" ;
2021-10-21 01:54:34 +08:00
import PropertyTable from "./PropertyTable.js" ;
2021-04-14 02:56:31 +08:00
import getBinaryAccessor from "./getBinaryAccessor.js" ;
import JsonMetadataTable from "./JsonMetadataTable.js" ;
2021-09-28 05:51:18 +08:00
import MetadataClass from "./MetadataClass.js" ;
2021-04-14 02:56:31 +08:00
import MetadataSchema from "./MetadataSchema.js" ;
2021-04-20 02:32:48 +08:00
import MetadataTable from "./MetadataTable.js" ;
2022-07-15 21:04:34 +08:00
import ModelComponents from "./ModelComponents.js" ;
2022-08-05 22:08:17 +08:00
import ModelUtility from "./Model/ModelUtility.js" ;
2022-12-07 13:47:57 +08:00
import oneTimeWarning from "../Core/oneTimeWarning.js" ;
2021-04-14 02:56:31 +08:00
/ * *
* An object that parses the the 3 D Tiles 1.0 batch table and transcodes it to
2022-02-22 04:52:32 +08:00
* be compatible with structural metadata from the < code > EXT _structural _metadata < / c o d e > g l T F e x t e n s i o n
2021-04-14 02:56:31 +08:00
* < p >
2022-02-22 04:52:32 +08:00
* See the { @ link https : //github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata|EXT_structural_metadata Extension} for glTF.
2021-04-14 02:56:31 +08:00
* < / p >
*
2023-02-10 16:35:41 +08:00
* @ param { object } options Object with the following properties :
* @ param { number } options . count The number of features in the batch table .
* @ param { object } options . batchTable The batch table JSON
2021-04-16 01:03:39 +08:00
* @ param { Uint8Array } [ options . binaryBody ] The batch table binary body
2023-02-10 16:35:41 +08:00
* @ param { boolean } [ options . parseAsPropertyAttributes = false ] If true , binary properties are parsed as property attributes instead of a property table . This is used for . pnts models for GPU styling .
2022-07-15 21:04:34 +08:00
* @ param { ModelComponents . Attribute [ ] } [ options . customAttributeOutput ] Pass in an empty array here and this method will populate it with a list of custom attributes that will store the values of the property attributes . The attributes will be created with typed arrays , the caller is responsible for uploading them to the GPU . This option is required when options . parseAsPropertyAttributes is true .
2022-02-22 23:04:51 +08:00
* @ return { StructuralMetadata } A transcoded structural metadata object
2021-04-14 02:56:31 +08:00
*
* @ private
2021-04-29 01:28:41 +08:00
* @ experimental This feature is using part of the 3 D Tiles spec that is not final and is subject to change without Cesium ' s standard deprecation policy .
2021-04-14 02:56:31 +08:00
* /
2022-08-11 06:22:27 +08:00
function parseBatchTable ( options ) {
2021-04-14 02:56:31 +08:00
//>>includeStart('debug', pragmas.debug);
Check . typeOf . number ( "options.count" , options . count ) ;
Check . typeOf . object ( "options.batchTable" , options . batchTable ) ;
//>>includeEnd('debug');
const featureCount = options . count ;
const batchTable = options . batchTable ;
const binaryBody = options . binaryBody ;
2025-03-04 03:03:53 +08:00
const parseAsPropertyAttributes = options . parseAsPropertyAttributes ? ? false ;
2022-07-15 21:04:34 +08:00
const customAttributeOutput = options . customAttributeOutput ;
//>>includeStart('debug', pragmas.debug);
if ( parseAsPropertyAttributes && ! defined ( customAttributeOutput ) ) {
throw new DeveloperError (
"customAttributeOutput is required when parsing batch table as property attributes" ,
) ;
}
//>>includeEnd('debug');
2021-04-14 02:56:31 +08:00
// divide properties into binary, json and hierarchy
const partitionResults = partitionProperties ( batchTable ) ;
2022-08-30 03:11:12 +08:00
let jsonMetadataTable ;
if ( defined ( partitionResults . jsonProperties ) ) {
jsonMetadataTable = new JsonMetadataTable ( {
count : featureCount ,
properties : partitionResults . jsonProperties ,
} ) ;
}
2021-04-14 02:56:31 +08:00
2022-08-30 03:11:12 +08:00
let hierarchy ;
if ( defined ( partitionResults . hierarchy ) ) {
hierarchy = new BatchTableHierarchy ( {
extension : partitionResults . hierarchy ,
binaryBody : binaryBody ,
} ) ;
}
2021-04-14 02:56:31 +08:00
2021-09-28 20:57:48 +08:00
const className = MetadataClass . BATCH _TABLE _CLASS _NAME ;
2022-08-31 01:14:57 +08:00
const binaryProperties = partitionResults . binaryProperties ;
2021-09-28 05:51:18 +08:00
2022-07-15 21:04:34 +08:00
let metadataTable ;
let propertyAttributes ;
let transcodedSchema ;
if ( parseAsPropertyAttributes ) {
const attributeResults = transcodeBinaryPropertiesAsPropertyAttributes (
featureCount ,
className ,
2022-08-31 01:14:57 +08:00
binaryProperties ,
2022-07-15 21:04:34 +08:00
binaryBody ,
customAttributeOutput ,
) ;
transcodedSchema = attributeResults . transcodedSchema ;
const propertyAttribute = new PropertyAttribute ( {
propertyAttribute : attributeResults . propertyAttributeJson ,
class : attributeResults . transcodedClass ,
} ) ;
2021-04-20 02:32:48 +08:00
2022-07-15 21:04:34 +08:00
propertyAttributes = [ propertyAttribute ] ;
} else {
const binaryResults = transcodeBinaryProperties (
featureCount ,
className ,
2022-08-31 01:14:57 +08:00
binaryProperties ,
2022-07-15 21:04:34 +08:00
binaryBody ,
) ;
transcodedSchema = binaryResults . transcodedSchema ;
const featureTableJson = binaryResults . featureTableJson ;
metadataTable = new MetadataTable ( {
count : featureTableJson . count ,
properties : featureTableJson . properties ,
class : binaryResults . transcodedClass ,
2022-07-15 22:22:02 +08:00
bufferViews : binaryResults . bufferViewsTypedArrays ,
2022-07-15 21:04:34 +08:00
} ) ;
2022-07-18 04:31:27 +08:00
propertyAttributes = [ ] ;
2022-07-15 21:04:34 +08:00
}
2021-04-20 02:32:48 +08:00
2022-08-30 03:11:12 +08:00
const propertyTables = [ ] ;
if (
defined ( metadataTable ) ||
defined ( jsonMetadataTable ) ||
defined ( hierarchy )
) {
const propertyTable = new PropertyTable ( {
id : 0 ,
name : "Batch Table" ,
count : featureCount ,
metadataTable : metadataTable ,
jsonMetadataTable : jsonMetadataTable ,
batchTableHierarchy : hierarchy ,
} ) ;
propertyTables . push ( propertyTable ) ;
}
2021-04-14 02:56:31 +08:00
2022-07-15 21:04:34 +08:00
const metadataOptions = {
schema : transcodedSchema ,
2022-08-30 03:11:12 +08:00
propertyTables : propertyTables ,
2022-07-15 21:04:34 +08:00
propertyAttributes : propertyAttributes ,
2021-04-14 03:38:21 +08:00
extensions : partitionResults . extensions ,
extras : partitionResults . extras ,
2022-07-15 21:04:34 +08:00
} ;
return new StructuralMetadata ( metadataOptions ) ;
2021-04-14 02:56:31 +08:00
}
/ * *
* Divide the batch table ' s properties into binary , JSON and hierarchy
* extension as each is handled separately
*
2023-02-10 16:35:41 +08:00
* @ param { object } batchTable The batch table JSON
* @ returns { object } The batch table divided into binary , JSON and hierarchy portions . Extras and extensions are also divided out for ease of processing .
2021-04-14 02:56:31 +08:00
*
* @ private
* /
function partitionProperties ( batchTable ) {
const legacyHierarchy = batchTable . HIERARCHY ;
const extras = batchTable . extras ;
const extensions = batchTable . extensions ;
2021-04-19 23:07:42 +08:00
let hierarchyExtension ;
2021-04-14 02:56:31 +08:00
if ( defined ( legacyHierarchy ) ) {
parseBatchTable . _deprecationWarning (
"batchTableHierarchyExtension" ,
"The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead." ,
) ;
2021-04-19 23:07:42 +08:00
hierarchyExtension = legacyHierarchy ;
} else if ( defined ( extensions ) ) {
hierarchyExtension = extensions [ "3DTILES_batch_table_hierarchy" ] ;
2021-04-14 02:56:31 +08:00
}
2022-08-31 01:14:57 +08:00
// A JsonMetadataTable is only allocated as needed.
2022-08-30 03:11:12 +08:00
let jsonProperties ;
2022-08-31 01:14:57 +08:00
// A MetadataTable or PropertyAttribute will always be created, even if
// there are no properties.
const binaryProperties = { } ;
2021-04-14 02:56:31 +08:00
for ( const propertyId in batchTable ) {
if (
! batchTable . hasOwnProperty ( propertyId ) ||
// these cases were handled above;
propertyId === "HIERARCHY" ||
propertyId === "extensions" ||
propertyId === "extras"
) {
continue ;
}
const property = batchTable [ propertyId ] ;
if ( Array . isArray ( property ) ) {
2022-08-30 03:11:12 +08:00
jsonProperties = defined ( jsonProperties ) ? jsonProperties : { } ;
2021-04-14 02:56:31 +08:00
jsonProperties [ propertyId ] = property ;
} else {
binaryProperties [ propertyId ] = property ;
}
}
return {
binaryProperties : binaryProperties ,
jsonProperties : jsonProperties ,
2021-04-19 23:07:42 +08:00
hierarchy : hierarchyExtension ,
2021-04-14 02:56:31 +08:00
extras : extras ,
extensions : extensions ,
} ;
}
/ * *
* Transcode the binary properties of the batch table to be compatible with
2022-02-22 04:52:32 +08:00
* < code > EXT _structural _metadata < / c o d e >
2021-04-14 02:56:31 +08:00
*
2023-02-10 16:35:41 +08:00
* @ param { number } featureCount The number of features in the batch table
* @ param { string } className The name of the metadata class to be created .
2023-02-17 10:22:50 +08:00
* @ param { Object < string , Object > } binaryProperties A dictionary of property ID to property definition
2021-04-16 01:03:39 +08:00
* @ param { Uint8Array } [ binaryBody ] The binary body of the batch table
2023-02-10 16:35:41 +08:00
* @ return { object } Transcoded data needed for constructing a { @ link StructuralMetadata } object .
2021-04-14 02:56:31 +08:00
*
* @ private
* /
2021-09-28 05:51:18 +08:00
function transcodeBinaryProperties (
featureCount ,
className ,
binaryProperties ,
binaryBody ,
) {
2021-04-14 02:56:31 +08:00
const classProperties = { } ;
const featureTableProperties = { } ;
2022-07-15 21:04:34 +08:00
const bufferViewsTypedArrays = { } ;
2021-04-14 02:56:31 +08:00
let bufferViewCount = 0 ;
for ( const propertyId in binaryProperties ) {
if ( ! binaryProperties . hasOwnProperty ( propertyId ) ) {
continue ;
}
2021-04-16 01:03:39 +08:00
if ( ! defined ( binaryBody ) ) {
throw new RuntimeError (
2022-02-03 04:40:21 +08:00
` Property ${ propertyId } requires a batch table binary. ` ,
2021-04-16 01:03:39 +08:00
) ;
}
2021-04-14 02:56:31 +08:00
const property = binaryProperties [ propertyId ] ;
const binaryAccessor = getBinaryAccessor ( property ) ;
featureTableProperties [ propertyId ] = {
bufferView : bufferViewCount ,
} ;
classProperties [ propertyId ] = transcodePropertyType ( property ) ;
2022-07-15 21:04:34 +08:00
bufferViewsTypedArrays [ bufferViewCount ] =
binaryAccessor . createArrayBufferView (
2021-04-14 02:56:31 +08:00
binaryBody . buffer ,
binaryBody . byteOffset + property . byteOffset ,
featureCount ,
) ;
bufferViewCount ++ ;
}
const schemaJson = {
2021-09-28 05:51:18 +08:00
classes : { } ,
} ;
schemaJson . classes [ className ] = {
properties : classProperties ,
2021-04-14 02:56:31 +08:00
} ;
2021-09-28 05:51:18 +08:00
2023-01-02 01:48:00 +08:00
const transcodedSchema = MetadataSchema . fromJson ( schemaJson ) ;
2021-04-14 02:56:31 +08:00
const featureTableJson = {
2021-09-28 05:51:18 +08:00
class : className ,
2021-04-14 02:56:31 +08:00
count : featureCount ,
properties : featureTableProperties ,
} ;
return {
featureTableJson : featureTableJson ,
2022-07-15 21:04:34 +08:00
bufferViewsTypedArrays : bufferViewsTypedArrays ,
transcodedSchema : transcodedSchema ,
transcodedClass : transcodedSchema . classes [ className ] ,
} ;
}
function transcodeBinaryPropertiesAsPropertyAttributes (
featureCount ,
className ,
binaryProperties ,
binaryBody ,
customAttributeOutput ,
) {
const classProperties = { } ;
const propertyAttributeProperties = { } ;
2022-07-18 04:31:27 +08:00
let nextPlaceholderId = 0 ;
2022-07-15 21:04:34 +08:00
for ( const propertyId in binaryProperties ) {
if ( ! binaryProperties . hasOwnProperty ( propertyId ) ) {
continue ;
}
2022-07-19 01:12:26 +08:00
// For draco-compressed attributes from .pnts files, the results will be
// stored in separate typed arrays. These will be used in place of the
// binary body
const property = binaryProperties [ propertyId ] ;
if ( ! defined ( binaryBody ) && ! defined ( property . typedArray ) ) {
2022-07-15 21:04:34 +08:00
throw new RuntimeError (
` Property ${ propertyId } requires a batch table binary. ` ,
) ;
}
2022-08-05 04:22:27 +08:00
let sanitizedPropertyId = ModelUtility . sanitizeGlslIdentifier ( propertyId ) ;
2022-07-18 04:31:27 +08:00
// If the sanitized string is empty or a duplicate, use a placeholder
// name instead. This will work for styling, but it may lead to undefined
2022-07-19 05:16:40 +08:00
// behavior in CustomShader, since
// - different tiles may pick a different placeholder ID due to the
// collection being unordered
// - different tiles may have different number of properties.
2022-07-18 04:31:27 +08:00
if (
2022-07-20 23:43:48 +08:00
sanitizedPropertyId === "" ||
classProperties . hasOwnProperty ( sanitizedPropertyId )
2022-07-18 04:31:27 +08:00
) {
2022-07-20 23:43:48 +08:00
sanitizedPropertyId = ` property_ ${ nextPlaceholderId } ` ;
2022-07-18 04:31:27 +08:00
nextPlaceholderId ++ ;
}
2022-07-15 21:04:34 +08:00
2022-07-18 04:31:27 +08:00
const classProperty = transcodePropertyType ( property ) ;
classProperty . name = propertyId ;
2022-07-20 23:43:48 +08:00
classProperties [ sanitizedPropertyId ] = classProperty ;
2022-07-15 21:04:34 +08:00
// Extract the typed array and create a custom attribute as a typed array.
// The caller must add the results to the ModelComponents, and upload the
2022-07-19 05:16:40 +08:00
// typed array to the GPU. The attribute name is converted to all capitals
// and underscores, like a glTF custom attribute.
//
// For example, if the original property ID was 'Temperature ℃', the result
// is _TEMPERATURE
2022-07-20 23:43:48 +08:00
let customAttributeName = sanitizedPropertyId . toUpperCase ( ) ;
2022-07-18 04:31:27 +08:00
if ( ! customAttributeName . startsWith ( "_" ) ) {
customAttributeName = ` _ ${ customAttributeName } ` ;
}
2022-07-19 01:12:26 +08:00
// for .pnts with draco compression, property.typedArray is used
// instead of the binary body.
let attributeTypedArray = property . typedArray ;
if ( ! defined ( attributeTypedArray ) ) {
const binaryAccessor = getBinaryAccessor ( property ) ;
attributeTypedArray = binaryAccessor . createArrayBufferView (
binaryBody . buffer ,
binaryBody . byteOffset + property . byteOffset ,
featureCount ,
) ;
}
2022-07-15 21:04:34 +08:00
const attribute = new ModelComponents . Attribute ( ) ;
attribute . name = customAttributeName ;
attribute . count = featureCount ;
attribute . type = property . type ;
2022-12-07 13:47:57 +08:00
const componentDatatype =
ComponentDatatype . fromTypedArray ( attributeTypedArray ) ;
if (
componentDatatype === ComponentDatatype . INT ||
componentDatatype === ComponentDatatype . UNSIGNED _INT ||
componentDatatype === ComponentDatatype . DOUBLE
) {
2022-12-08 12:23:49 +08:00
parseBatchTable . _oneTimeWarning (
2022-12-07 13:47:57 +08:00
"Cast pnts property to floats" ,
2022-12-08 12:23:49 +08:00
` Point cloud property " ${ customAttributeName } " will be cast to a float array because INT, UNSIGNED_INT, and DOUBLE are not valid WebGL vertex attribute types. Some precision may be lost. ` ,
2022-12-07 13:47:57 +08:00
) ;
attributeTypedArray = new Float32Array ( attributeTypedArray ) ;
}
2022-07-15 21:04:34 +08:00
attribute . componentDatatype =
ComponentDatatype . fromTypedArray ( attributeTypedArray ) ;
attribute . typedArray = attributeTypedArray ;
customAttributeOutput . push ( attribute ) ;
// Refer to the custom attribute name from the property attribute
2022-07-20 23:43:48 +08:00
propertyAttributeProperties [ sanitizedPropertyId ] = {
2022-07-15 21:04:34 +08:00
attribute : customAttributeName ,
} ;
}
const schemaJson = {
classes : { } ,
} ;
schemaJson . classes [ className ] = {
properties : classProperties ,
} ;
2023-01-02 01:48:00 +08:00
const transcodedSchema = MetadataSchema . fromJson ( schemaJson ) ;
2022-07-15 21:04:34 +08:00
const propertyAttributeJson = {
properties : propertyAttributeProperties ,
} ;
return {
class : className ,
propertyAttributeJson : propertyAttributeJson ,
2021-04-14 02:56:31 +08:00
transcodedSchema : transcodedSchema ,
2021-09-28 05:51:18 +08:00
transcodedClass : transcodedSchema . classes [ className ] ,
2021-04-14 02:56:31 +08:00
} ;
}
/ * *
* Given a property definition from the batch table , compute the equivalent
2022-02-22 04:52:32 +08:00
* < code > EXT _structural _metadata < / c o d e > t y p e d e f i n i t i o n
2021-04-14 02:56:31 +08:00
*
2023-02-10 16:35:41 +08:00
* @ param { object } property The batch table property definition
* @ return { object } The corresponding structural metadata property definition
2021-04-14 02:56:31 +08:00
* @ private
* /
function transcodePropertyType ( property ) {
const componentType = transcodeComponentType ( property . componentType ) ;
return {
2022-02-18 01:23:02 +08:00
type : property . type ,
2021-10-20 03:44:43 +08:00
componentType : componentType ,
2021-04-14 02:56:31 +08:00
} ;
}
/ * *
* Convert the component type of a batch table property to the corresponding
2022-02-22 23:04:51 +08:00
* type used with structural metadata
2021-04-14 02:56:31 +08:00
*
2023-02-10 16:35:41 +08:00
* @ property { string } componentType the batch table ' s component type
* @ return { string } The corresponding structural metadata data type
2021-04-14 02:56:31 +08:00
*
* @ private
* /
function transcodeComponentType ( componentType ) {
switch ( componentType ) {
case "BYTE" :
return "INT8" ;
case "UNSIGNED_BYTE" :
return "UINT8" ;
case "SHORT" :
return "INT16" ;
case "UNSIGNED_SHORT" :
return "UINT16" ;
case "INT" :
return "INT32" ;
case "UNSIGNED_INT" :
return "UINT32" ;
case "FLOAT" :
return "FLOAT32" ;
case "DOUBLE" :
return "FLOAT64" ;
}
}
// exposed for testing
parseBatchTable . _deprecationWarning = deprecationWarning ;
2022-12-08 12:23:49 +08:00
parseBatchTable . _oneTimeWarning = oneTimeWarning ;
2022-08-11 06:22:27 +08:00
export default parseBatchTable ;