UNPKG

@loaders.gl/gltf

Version:

Framework-independent loader for the glTF format

132 lines 6.16 kB
// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression // Only TRIANGLES: 0x0004 and TRIANGLE_STRIP: 0x0005 are supported /* eslint-disable camelcase */ import { sliceArrayBuffer, parseFromContext } from '@loaders.gl/loader-utils'; import { DracoLoader } from '@loaders.gl/draco'; import { GLTFScenegraph } from "../api/gltf-scenegraph.js"; import { getGLTFAccessors, getGLTFAccessor } from "../gltf-utils/gltf-attribute-utils.js"; const KHR_DRACO_MESH_COMPRESSION = 'KHR_draco_mesh_compression'; /** Extension name */ export const name = KHR_DRACO_MESH_COMPRESSION; export function preprocess(gltfData, options, context) { const scenegraph = new GLTFScenegraph(gltfData); for (const primitive of makeMeshPrimitiveIterator(scenegraph)) { if (scenegraph.getObjectExtension(primitive, KHR_DRACO_MESH_COMPRESSION)) { // TODO - Remove fallback accessors to make sure we don't load unnecessary buffers } } } export async function decode(gltfData, options, context) { if (!options?.gltf?.decompressMeshes) { return; } const scenegraph = new GLTFScenegraph(gltfData); const promises = []; for (const primitive of makeMeshPrimitiveIterator(scenegraph)) { if (scenegraph.getObjectExtension(primitive, KHR_DRACO_MESH_COMPRESSION)) { promises.push(decompressPrimitive(scenegraph, primitive, options, context)); } } // Decompress meshes in parallel await Promise.all(promises); // We have now decompressed all primitives, so remove the top-level extension scenegraph.removeExtension(KHR_DRACO_MESH_COMPRESSION); } export function encode(gltfData, options = {}) { const scenegraph = new GLTFScenegraph(gltfData); for (const mesh of scenegraph.json.meshes || []) { // eslint-disable-next-line camelcase // @ts-ignore compressMesh(mesh, options); // NOTE: Only add the extension if something was actually compressed scenegraph.addRequiredExtension(KHR_DRACO_MESH_COMPRESSION); } } // DECODE // Unpacks one mesh primitive and removes the extension from the primitive // DracoDecoder needs to be imported and registered by app // Returns: Promise that resolves when all pending draco decoder jobs for this mesh complete // TODO - Implement fallback behavior per KHR_DRACO_MESH_COMPRESSION spec async function decompressPrimitive(scenegraph, primitive, options, context) { const dracoExtension = scenegraph.getObjectExtension(primitive, KHR_DRACO_MESH_COMPRESSION); if (!dracoExtension) { return; } const buffer = scenegraph.getTypedArrayForBufferView(dracoExtension.bufferView); // TODO - parse does not yet deal well with byte offsets embedded in typed arrays. Copy buffer // TODO - remove when `parse` is fixed to handle `byteOffset`s const bufferCopy = sliceArrayBuffer(buffer.buffer, buffer.byteOffset); // , buffer.byteLength); const dracoOptions = { ...options }; // TODO - remove hack: The entire tileset might be included, too expensive to serialize delete dracoOptions['3d-tiles']; const decodedData = await parseFromContext(bufferCopy, DracoLoader, dracoOptions, context); const decodedAttributes = getGLTFAccessors(decodedData.attributes); // Restore min/max values for (const [attributeName, decodedAttribute] of Object.entries(decodedAttributes)) { if (attributeName in primitive.attributes) { const accessorIndex = primitive.attributes[attributeName]; const accessor = scenegraph.getAccessor(accessorIndex); if (accessor?.min && accessor?.max) { decodedAttribute.min = accessor.min; decodedAttribute.max = accessor.max; } } } // @ts-ignore primitive.attributes = decodedAttributes; if (decodedData.indices) { // @ts-ignore primitive.indices = getGLTFAccessor(decodedData.indices); } // Extension has been processed, delete it scenegraph.removeObjectExtension(primitive, KHR_DRACO_MESH_COMPRESSION); checkPrimitive(primitive); } // ENCODE // eslint-disable-next-line max-len // Only TRIANGLES: 0x0004 and TRIANGLE_STRIP: 0x0005 are supported function compressMesh(attributes, indices, mode = 4, options, context) { if (!options.DracoWriter) { throw new Error('options.gltf.DracoWriter not provided'); } // TODO - use DracoWriter using encode w/ registered DracoWriter... const compressedData = options.DracoWriter.encodeSync({ attributes }); // Draco compression may change the order and number of vertices in a mesh. // To satisfy the requirement that accessors properties be correct for both // compressed and uncompressed data, generators should create uncompressed // attributes and indices using data that has been decompressed from the Draco buffer, // rather than the original source data. // @ts-ignore TODO this needs to be fixed const decodedData = context?.parseSync?.({ attributes }); const fauxAccessors = options._addFauxAttributes(decodedData.attributes); const bufferViewIndex = options.addBufferView(compressedData); const glTFMesh = { primitives: [ { attributes: fauxAccessors, // TODO - verify with spec mode, // GL.POINTS extensions: { [KHR_DRACO_MESH_COMPRESSION]: { bufferView: bufferViewIndex, attributes: fauxAccessors // TODO - verify with spec } } } ] }; return glTFMesh; } // UTILS function checkPrimitive(primitive) { if (!primitive.attributes && Object.keys(primitive.attributes).length > 0) { throw new Error('glTF: Empty primitive detected: Draco decompression failure?'); } } function* makeMeshPrimitiveIterator(scenegraph) { for (const mesh of scenegraph.json.meshes || []) { for (const primitive of mesh.primitives) { yield primitive; } } } //# sourceMappingURL=KHR_draco_mesh_compression.js.map