obj2gltf/lib/writeGltf.js
Matthew Amato b87c761073 Monthly Maintenance
* Upgrade prettier to 3.0.3 and format files.
* Upgrade `eslint-config-cesium` and `eslint-config-prettier`.
* Switch from the defunct `eslint-plugin-node` to `eslint-plugin-n`
Since that is what `eslint-config-cesium` now uses.
* Switch from pretty-quick to lint-staged
2023-10-03 13:28:47 -04:00

207 lines
5.4 KiB
JavaScript

"use strict";
const Cesium = require("cesium");
const mime = require("mime");
const PNG = require("pngjs").PNG;
const Promise = require("bluebird");
const getBufferPadded = require("./getBufferPadded");
const gltfToGlb = require("./gltfToGlb");
const defined = Cesium.defined;
const RuntimeError = Cesium.RuntimeError;
module.exports = writeGltf;
/**
* Write glTF resources as embedded data uris or external files.
*
* @param {Object} gltf The glTF asset.
* @param {Object} options The options object passed along from lib/obj2gltf.js
* @returns {Promise} A promise that resolves to the glTF JSON or glb buffer.
*
* @private
*/
function writeGltf(gltf, options) {
return encodeTextures(gltf).then(function () {
const binary = options.binary;
const separate = options.separate;
const separateTextures = options.separateTextures;
const promises = [];
if (separateTextures) {
promises.push(writeSeparateTextures(gltf, options));
} else {
writeEmbeddedTextures(gltf);
}
if (separate) {
promises.push(writeSeparateBuffers(gltf, options));
} else if (!binary) {
writeEmbeddedBuffer(gltf);
}
const binaryBuffer = gltf.buffers[0].extras._obj2gltf.source;
return Promise.all(promises).then(function () {
deleteExtras(gltf);
removeEmpty(gltf);
if (binary) {
return gltfToGlb(gltf, binaryBuffer);
}
return gltf;
});
});
}
function encodePng(texture) {
// Constants defined by pngjs
const rgbColorType = 2;
const rgbaColorType = 6;
const png = new PNG({
width: texture.width,
height: texture.height,
colorType: texture.transparent ? rgbaColorType : rgbColorType,
inputColorType: rgbaColorType,
inputHasAlpha: true,
});
png.data = texture.pixels;
return new Promise(function (resolve, reject) {
const chunks = [];
const stream = png.pack();
stream.on("data", function (chunk) {
chunks.push(chunk);
});
stream.on("end", function () {
resolve(Buffer.concat(chunks));
});
stream.on("error", reject);
});
}
function encodeTexture(texture) {
if (
!defined(texture.source) &&
defined(texture.pixels) &&
texture.extension === ".png"
) {
return encodePng(texture).then(function (encoded) {
texture.source = encoded;
});
}
}
function encodeTextures(gltf) {
// Dynamically generated PBR textures need to be encoded to png prior to being saved
const encodePromises = [];
const images = gltf.images;
const length = images.length;
for (let i = 0; i < length; ++i) {
encodePromises.push(encodeTexture(images[i].extras._obj2gltf));
}
return Promise.all(encodePromises);
}
function deleteExtras(gltf) {
const buffers = gltf.buffers;
const buffersLength = buffers.length;
for (let i = 0; i < buffersLength; ++i) {
delete buffers[i].extras;
}
const images = gltf.images;
const imagesLength = images.length;
for (let i = 0; i < imagesLength; ++i) {
delete images[i].extras;
}
}
function removeEmpty(json) {
Object.keys(json).forEach(function (key) {
if (
!defined(json[key]) ||
(Array.isArray(json[key]) && json[key].length === 0)
) {
delete json[key]; // Delete values that are undefined or []
} else if (typeof json[key] === "object") {
removeEmpty(json[key]);
}
});
}
function writeSeparateBuffers(gltf, options) {
const buffers = gltf.buffers;
return Promise.map(
buffers,
function (buffer) {
const source = buffer.extras._obj2gltf.source;
const bufferUri = `${buffer.name}.bin`;
buffer.uri = bufferUri;
return options.writer(bufferUri, source);
},
{ concurrency: 10 },
);
}
function writeSeparateTextures(gltf, options) {
const images = gltf.images;
return Promise.map(
images,
function (image) {
const texture = image.extras._obj2gltf;
const imageUri = image.name + texture.extension;
image.uri = imageUri;
return options.writer(imageUri, texture.source);
},
{ concurrency: 10 },
);
}
function writeEmbeddedBuffer(gltf) {
const buffer = gltf.buffers[0];
const source = buffer.extras._obj2gltf.source;
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
if (source.length > 201326580) {
throw new RuntimeError(
"Buffer is too large to embed in the glTF. Use the --separate flag instead.",
);
}
buffer.uri = `data:application/octet-stream;base64,${source.toString(
"base64",
)}`;
}
function writeEmbeddedTextures(gltf) {
const buffer = gltf.buffers[0];
const bufferExtras = buffer.extras._obj2gltf;
const bufferSource = bufferExtras.source;
const images = gltf.images;
const imagesLength = images.length;
const sources = [bufferSource];
let byteOffset = bufferSource.length;
for (let i = 0; i < imagesLength; ++i) {
const image = images[i];
const texture = image.extras._obj2gltf;
const textureSource = texture.source;
const textureByteLength = textureSource.length;
image.mimeType = mime.getType(texture.extension);
image.bufferView = gltf.bufferViews.length;
gltf.bufferViews.push({
buffer: 0,
byteOffset: byteOffset,
byteLength: textureByteLength,
});
byteOffset += textureByteLength;
sources.push(textureSource);
}
const source = getBufferPadded(Buffer.concat(sources));
bufferExtras.source = source;
buffer.byteLength = source.length;
}