UNPKG

@softrobot/loaders.gl-draco

Version:

Framework-independent loader and writer for Draco compressed meshes and point clouds

348 lines (276 loc) 10.6 kB
import { getMeshBoundingBox } from '@loaders.gl/loader-utils'; const GEOMETRY_TYPE = { TRIANGULAR_MESH: 0, POINT_CLOUD: 1 }; const DRACO_TO_GLTF_ATTRIBUTE_NAME_MAP = { POSITION: 'POSITION', NORMAL: 'NORMAL', COLOR: 'COLOR_0', TEX_COORD: 'TEXCOORD_0' }; const DRACO_DATA_TYPE_TO_TYPED_ARRAY_MAP = { 1: Int8Array, 2: Uint8Array, 3: Int16Array, 4: Uint16Array, 5: Int32Array, 6: Uint32Array, 9: Float32Array }; const INDEX_ITEM_SIZE = 4; export default class DracoParser { constructor(draco) { this.draco = draco; this.drawMode = 'TRIANGLE'; this.metadataQuerier = {}; } destroy() {} parseSync(arrayBuffer, options = {}) { this.metadataQuerier = new this.draco.MetadataQuerier(); const buffer = new this.draco.DecoderBuffer(); buffer.Init(new Int8Array(arrayBuffer), arrayBuffer.byteLength); const decoder = new this.draco.Decoder(); const data = {}; let dracoStatus; let dracoGeometry; let header; try { const geometryType = decoder.GetEncodedGeometryType(buffer); switch (geometryType) { case this.draco.TRIANGULAR_MESH: dracoGeometry = new this.draco.Mesh(); dracoStatus = decoder.DecodeBufferToMesh(buffer, dracoGeometry); header = { type: GEOMETRY_TYPE.TRIANGULAR_MESH, faceCount: dracoGeometry.num_faces(), attributeCount: dracoGeometry.num_attributes(), vertexCount: dracoGeometry.num_points() }; break; case this.draco.POINT_CLOUD: dracoGeometry = new this.draco.PointCloud(); dracoStatus = decoder.DecodeBufferToPointCloud(buffer, dracoGeometry); header = { type: GEOMETRY_TYPE.POINT_CLOUD, attributeCount: dracoGeometry.num_attributes(), vertexCount: dracoGeometry.num_points() }; break; default: throw new Error('Unknown DRACO geometry type.'); } if (!dracoStatus.ok() || !dracoGeometry.ptr) { const message = "DRACO decompression failed: ".concat(dracoStatus.error_msg()); if (dracoGeometry) { this.draco.destroy(dracoGeometry); } throw new Error(message); } data.loaderData = { header }; this._extractDRACOGeometry(decoder, dracoGeometry, geometryType, data, options); const metadata = this._getGeometryMetadata(decoder, dracoGeometry); data.header = { vertexCount: header.vertexCount, boundingBox: getMeshBoundingBox(data.attributes), metadata }; } finally { this.draco.destroy(decoder); this.draco.destroy(buffer); this.draco.destroy(dracoGeometry); this.draco.destroy(this.metadataQuerier); } return data; } _extractDRACOGeometry(decoder, dracoGeometry, geometryType, geometry, options) { const attributes = this._getAttributes(decoder, dracoGeometry, options); const positionAttribute = attributes.POSITION; if (!positionAttribute) { throw new Error('DRACO decompressor: No position attribute found.'); } if (geometryType === this.draco.TRIANGULAR_MESH) { attributes.indices = this.drawMode === 'TRIANGLE_STRIP' ? this._getMeshStripIndices(decoder, dracoGeometry) : this._getMeshFaceIndices(decoder, dracoGeometry); geometry.mode = this.drawMode === 'TRIANGLE_STRIP' ? 5 : 4; } else { geometry.mode = 0; } if (attributes.indices) { geometry.indices = { value: attributes.indices, size: 1 }; delete attributes.indices; } geometry.attributes = attributes; return geometry; } getPositionAttributeMetadata(positionAttribute) { this.metadata = this.metadata || {}; this.metadata.attributes = this.metadata.attributes || {}; const posTransform = new this.draco.AttributeQuantizationTransform(); if (posTransform.InitFromAttribute(positionAttribute)) { this.metadata.attributes.position.isQuantized = true; this.metadata.attributes.position.maxRange = posTransform.range(); this.metadata.attributes.position.numQuantizationBits = posTransform.quantization_bits(); this.metadata.attributes.position.minValues = new Float32Array(3); for (let i = 0; i < 3; ++i) { this.metadata.attributes.position.minValues[i] = posTransform.min_value(i); } } this.draco.destroy(posTransform); } _getAttributes(decoder, dracoGeometry, options) { const attributes = {}; const numPoints = dracoGeometry.num_points(); for (let attributeId = 0; attributeId < dracoGeometry.num_attributes(); attributeId++) { const dracoAttribute = decoder.GetAttribute(dracoGeometry, attributeId); const attributeMetadata = this._getAttributeMetadata(decoder, dracoGeometry, attributeId); const attributeData = { uniqueId: dracoAttribute.unique_id(), attributeType: dracoAttribute.attribute_type(), dataType: DRACO_DATA_TYPE_TO_TYPED_ARRAY_MAP[dracoAttribute.data_type()], size: dracoAttribute.size(), numComponents: dracoAttribute.num_components(), byteOffset: dracoAttribute.byte_offset(), byteStride: dracoAttribute.byte_stride(), normalized: dracoAttribute.normalized(), metadata: attributeMetadata }; const attributeName = this._deduceAttributeName(attributeData, options); const { typedArray } = this._getAttributeTypedArray(decoder, dracoGeometry, dracoAttribute, attributeName); attributes[attributeName] = { value: typedArray, size: typedArray.length / numPoints, metadata: attributeMetadata }; } return attributes; } _getMeshFaceIndices(decoder, dracoGeometry) { const numFaces = dracoGeometry.num_faces(); const numIndices = numFaces * 3; const byteLength = numIndices * INDEX_ITEM_SIZE; const ptr = this.draco._malloc(byteLength); decoder.GetTrianglesUInt32Array(dracoGeometry, byteLength, ptr); const indices = new Uint32Array(this.draco.HEAPF32.buffer, ptr, numIndices).slice(); this.draco._free(ptr); return indices; } _getMeshStripIndices(decoder, dracoGeometry) { const dracoArray = new this.draco.DracoInt32Array(); decoder.GetTriangleStripsFromMesh(dracoGeometry, dracoArray); const indices = new Uint32Array(dracoArray.size()); for (let i = 0; i < dracoArray.size(); ++i) { indices[i] = dracoArray.GetValue(i); } this.draco.destroy(dracoArray); return indices; } _getAttributeTypedArray(decoder, dracoGeometry, dracoAttribute, attributeName) { if (dracoAttribute.ptr === 0) { const message = "DRACO decode bad attribute ".concat(attributeName); throw new Error(message); } const TypedArrayCtor = DRACO_DATA_TYPE_TO_TYPED_ARRAY_MAP[dracoAttribute.data_type()]; const numComponents = dracoAttribute.num_components(); const numPoints = dracoGeometry.num_points(); const numValues = numPoints * numComponents; const byteLength = numValues * TypedArrayCtor.BYTES_PER_ELEMENT; const dataType = this._getDracoDataType(TypedArrayCtor); const ptr = this.draco._malloc(byteLength); decoder.GetAttributeDataArrayForAllPoints(dracoGeometry, dracoAttribute, dataType, byteLength, ptr); const typedArray = new TypedArrayCtor(this.draco.HEAPF32.buffer, ptr, numValues).slice(); this.draco._free(ptr); return { typedArray, components: numComponents }; } _getDracoDataType(attributeType) { switch (attributeType) { case Float32Array: return this.draco.DT_FLOAT32; case Int8Array: return this.draco.DT_INT8; case Int16Array: return this.draco.DT_INT16; case Int32Array: return this.draco.DT_INT32; case Uint8Array: return this.draco.DT_UINT8; case Uint16Array: return this.draco.DT_UINT16; case Uint32Array: return this.draco.DT_UINT32; default: return this.draco.DT_INVALID; } } _deduceAttributeName(attributeData, options) { const { extraAttributes = {} } = options; if (extraAttributes && typeof extraAttributes === 'object') { for (const [attributeName, attributeUniqueId] of Object.entries(extraAttributes)) { if (attributeUniqueId === attributeData.uniqueId) { return attributeName; } } } for (const dracoAttributeConstant in DRACO_TO_GLTF_ATTRIBUTE_NAME_MAP) { const attributeType = this.draco[dracoAttributeConstant]; if (attributeData.attributeType === attributeType) { return DRACO_TO_GLTF_ATTRIBUTE_NAME_MAP[dracoAttributeConstant]; } } if (attributeData.metadata) { const entryName = options.attributeNameEntry || 'name'; if (attributeData.metadata[entryName]) { return attributeData.metadata[entryName].string; } } return "CUSTOM_ATTRIBUTE_".concat(attributeData.uniqueId); } _getGeometryMetadata(decoder, dracoGeometry) { const dracoMetadata = decoder.GetMetadata(dracoGeometry); return this._queryDracoMetadata(dracoMetadata); } _getAttributeMetadata(decoder, dracoGeometry, attributeId) { const dracoMetadata = decoder.GetAttributeMetadata(dracoGeometry, attributeId); return this._queryDracoMetadata(dracoMetadata); } _queryDracoMetadata(dracoMetadata) { if (!dracoMetadata || !dracoMetadata.ptr) { return {}; } const result = {}; const numEntries = this.metadataQuerier.NumEntries(dracoMetadata); const dracoArray = new this.draco.DracoInt32Array(); for (let entryIndex = 0; entryIndex < numEntries; entryIndex++) { const entryName = this.metadataQuerier.GetEntryName(dracoMetadata, entryIndex); this.metadataQuerier.GetIntEntryArray(dracoMetadata, entryName, dracoArray); const numValues = dracoArray.size(); const intArray = new Int32Array(numValues); for (let i = 0; i < numValues; i++) { intArray[i] = dracoArray.GetValue(i); } result[entryName] = { int: this.metadataQuerier.GetIntEntry(dracoMetadata, entryName), string: this.metadataQuerier.GetStringEntry(dracoMetadata, entryName), double: this.metadataQuerier.GetDoubleEntry(dracoMetadata, entryName), intArray }; } this.draco.destroy(dracoArray); return result; } decode(arrayBuffer, options) { return this.parseSync(arrayBuffer, options); } } //# sourceMappingURL=draco-parser.js.map