mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-27 02:24:04 -05:00
207 lines
5.4 KiB
JavaScript
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;
|
|
}
|