UNPKG

gltf-pipeline

Version:

Content pipeline tools for optimizing glTF assets.

274 lines (241 loc) 8.24 kB
"use strict"; const Cesium = require("cesium"); const mime = require("mime"); const addBuffer = require("./addBuffer"); const ForEach = require("./ForEach"); const getImageExtension = require("./getImageExtension"); const mergeBuffers = require("./mergeBuffers"); const removeUnusedElements = require("./removeUnusedElements"); const defined = Cesium.defined; const WebGLConstants = Cesium.WebGLConstants; // .glsl shaders are text/plain type mime.define({ "text/plain": ["glsl"] }, true); // .basis is not a supported mime type, so add it mime.define({ "image/basis": ["basis"] }, true); // .ktx2 (KTX2) is not a supported mime type, so add it mime.define({ "image/ktx2": ["ktx2"] }, true); module.exports = writeResources; /** * Write glTF resources as data uris, buffer views, or files. * * @param {object} gltf A javascript object containing a glTF asset. * @param {object} [options] Object with the following properties: * @param {string} [options.name] The name of the glTF asset, for writing separate resources. * @param {boolean} [options.separateBuffers=false] Whether to save buffers as separate files. * @param {boolean} [options.separateShaders=false] Whether to save shaders as separate files. * @param {boolean} [options.separateTextures=false] Whether to save images as separate files. * @param {boolean} [options.forceMergeBuffers=false] Whether to force merging all buffers. * @param {boolean} [options.dataUris=false] Write embedded resources as data uris instead of buffer views. * @param {object} [options.bufferStorage] When defined, the glTF buffer's underlying Buffer object will be saved here instead of encoded as a data uri or saved as a separate resource. * @param {object} [options.separateResources] When defined, buffers of separate resources will be saved here. * @returns {object} The glTF asset. * * @private */ function writeResources(gltf, options) { options = options ?? {}; options.separateBuffers = options.separateBuffers ?? false; options.separateTextures = options.separateTextures ?? false; options.separateShaders = options.separateShaders ?? false; options.dataUris = options.dataUris ?? false; options.forceMergeBuffers = options.forceMergeBuffers ?? false; // Remember which of the resources have been written, so we can re-use them. const writtenResourceMap = {}; ForEach.image(gltf, function (image, i) { writeImage(gltf, image, i, writtenResourceMap, options); }); ForEach.shader(gltf, function (shader, i) { writeShader(gltf, shader, i, writtenResourceMap, options); }); // Buffers need to be written last because images and shaders may write to new buffers removeUnusedElements(gltf, ["accessor", "bufferView", "buffer"]); mergeBuffers(gltf, options.name, options.forceMergeBuffers); ForEach.buffer(gltf, function (buffer, bufferId) { writeBuffer(gltf, buffer, bufferId, writtenResourceMap, options); }); return gltf; } function writeBuffer(gltf, buffer, i, writtenResourceMap, options) { if (!defined(buffer.extras._pipeline.source)) { return; } if (defined(options.bufferStorage) && !options.separateBuffers) { writeBufferStorage(buffer, options); } else { writeResource( gltf, buffer, i, options.separateBuffers, true, ".bin", writtenResourceMap, options, ); } } function writeBufferStorage(buffer, options) { let combinedBuffer = options.bufferStorage.buffer; combinedBuffer = defined(combinedBuffer) ? combinedBuffer : Buffer.alloc(0); combinedBuffer = Buffer.concat([ combinedBuffer, buffer.extras._pipeline.source, ]); options.bufferStorage.buffer = combinedBuffer; } function writeImage(gltf, image, i, writtenResourceMap, options) { const extension = getImageExtension(image.extras._pipeline.source); writeResource( gltf, image, i, options.separateTextures, options.dataUris, extension, writtenResourceMap, options, ); if (defined(image.bufferView)) { // Preserve the image mime type when writing to a buffer view image.mimeType = mime.getType(extension); } } function writeShader(gltf, shader, i, writtenResourceMap, options) { writeResource( gltf, shader, i, options.separateShaders, options.dataUris, ".glsl", writtenResourceMap, options, ); } function writeResource( gltf, object, index, separate, dataUris, extension, writtenResourceMap, options, ) { if (separate) { writeFile(gltf, object, index, extension, writtenResourceMap, options); } else if (dataUris) { writeDataUri(object, extension); } else { writeBufferView(gltf, object, writtenResourceMap); } } function writeDataUri(object, extension) { delete object.bufferView; const source = object.extras._pipeline.source; const mimeType = mime.getType(extension); object.uri = `data:${mimeType};base64,${source.toString("base64")}`; } function writeBufferView(gltf, object, writtenResourceMap) { delete object.uri; // If we've written this resource before, re-use the bufferView const resourceId = object.extras._pipeline.resourceId; if (defined(resourceId) && defined(writtenResourceMap[resourceId])) { object.bufferView = writtenResourceMap[resourceId]; return; } let source = object.extras._pipeline.source; if (typeof source === "string") { source = Buffer.from(source); } object.bufferView = addBuffer(gltf, source); // Save the bufferView so we can re-use it later if (defined(resourceId)) { writtenResourceMap[resourceId] = object.bufferView; } } function getProgram(gltf, shaderIndex) { return ForEach.program(gltf, function (program, index) { if ( program.fragmentShader === shaderIndex || program.vertexShader === shaderIndex ) { return { program: program, index: index, }; } }); } function getName(gltf, object, index, extension, options) { const gltfName = options.name; const objectName = object.name; if (defined(objectName)) { return objectName; } else if (extension === ".bin") { if (defined(gltfName)) { return gltfName + index; } return `buffer${index}`; } else if (extension === ".glsl") { const programInfo = getProgram(gltf, index); const program = programInfo.program; const programIndex = programInfo.index; const programName = program.name; const shaderType = object.type === WebGLConstants.FRAGMENT_SHADER ? "FS" : "VS"; if (defined(programName)) { return programName + shaderType; } else if (defined(gltfName)) { return gltfName + shaderType + programIndex; } return shaderType.toLowerCase() + programIndex; } // Otherwise is an image if (defined(gltfName)) { return gltfName + index; } return `image${index}`; } function getRelativePath(gltf, object, index, extension, options) { const pipelineExtras = object.extras._pipeline; let relativePath = pipelineExtras.relativePath; if (defined(relativePath)) { return relativePath.replace(/\\/g, "/"); } const name = getName(gltf, object, index, extension, options); relativePath = name + extension; // Check if a file of the same name already exists, and if so, append a number let number = 1; while (defined(options.separateResources[relativePath])) { relativePath = `${name}_${number}${extension}`; number++; } return relativePath; } function writeFile( gltf, object, index, extension, writtenResourceMap, options, ) { delete object.bufferView; // If we've written this resource before, re-use the uri const resourceId = object.extras._pipeline.resourceId; if (defined(resourceId) && defined(writtenResourceMap[resourceId])) { object.uri = writtenResourceMap[resourceId]; return; } const source = object.extras._pipeline.source; const relativePath = getRelativePath(gltf, object, index, extension, options); object.uri = relativePath; if (defined(options.separateResources)) { options.separateResources[relativePath] = source; } // Save the uri so we can re-use it later if (defined(resourceId)) { writtenResourceMap[resourceId] = object.uri; } }