Fix incompatible byte strides

This commit is contained in:
Sean Lilley 2017-07-19 17:56:24 -04:00
parent ab6786e463
commit aaf44e75dc
7 changed files with 218 additions and 71 deletions

View File

@ -116,10 +116,14 @@ var options = {
console.time('Total');
obj2gltf(objPath, gltfPath, options)
.then(function() {
console.timeEnd('Total');
})
.catch(function(error) {
console.log(error);
});
try {
obj2gltf(objPath, gltfPath, options)
.then(function() {
console.timeEnd('Total');
})
.catch(function(error) {
console.log(error);
});
} catch(error) {
console.log(error);
}

View File

@ -2,6 +2,7 @@
var Cesium = require('cesium');
var path = require('path');
var PNG = require('pngjs').PNG;
var getBufferPadded = require('./getBufferPadded');
var Material = require('./Material');
var CesiumMath = Cesium.Math;
@ -57,12 +58,14 @@ function createGltf(objData, options) {
});
var bufferState = {
vertexBuffers : [],
vertexBufferByteOffset : 0,
vertexBufferViewIndex : 0,
positionBuffers : [],
normalBuffers : [],
uvBuffers : [],
indexBuffers : [],
indexBufferByteOffset : 0,
indexBufferViewIndex : 1
positionAccessors : [],
normalAccessors : [],
uvAccessors : [],
indexAccessors : []
};
var uint32Indices = requiresUint32Indices(nodes);
@ -98,49 +101,55 @@ function createGltf(objData, options) {
}
addBuffers(gltf, bufferState);
return gltf;
}
function addBuffers(gltf, bufferState) {
var bufferName = 'buffer';
var vertexBufferViewName = 'bufferView_vertex';
var indexBufferViewName = 'bufferView_index';
function addBufferView(gltf, buffers, accessors, byteStride, target) {
var length = buffers.length;
if (length === 0) {
return;
}
var bufferViewIndex = gltf.bufferViews.length;
var previousBufferView = gltf.bufferViews[bufferViewIndex - 1];
var byteOffset = defined(previousBufferView) ? previousBufferView.byteOffset + previousBufferView.byteLength : 0;
var byteLength = 0;
for (var i = 0; i < length; ++i) {
var accessor = gltf.accessors[accessors[i]];
accessor.bufferView = bufferViewIndex;
accessor.byteOffset = byteLength;
byteLength += buffers[i].length;
}
gltf.bufferViews.push({
name : 'bufferView_' + bufferViewIndex,
buffer : 0,
byteLength : byteLength,
byteOffset : byteOffset,
byteStride : byteStride,
target : target
});
}
var vertexBuffers = bufferState.vertexBuffers;
var indexBuffers = bufferState.indexBuffers;
var vertexBufferByteLength = bufferState.vertexBufferByteOffset;
var indexBufferByteLength = bufferState.indexBufferByteOffset;
function addBuffers(gltf, bufferState) {
// Positions and normals share the same byte stride so they can share the same bufferView
var positionsAndNormalsAccessors = bufferState.positionAccessors.concat(bufferState.normalAccessors);
var positionsAndNormalsBuffers = bufferState.positionBuffers.concat(bufferState.normalBuffers);
addBufferView(gltf, positionsAndNormalsBuffers, positionsAndNormalsAccessors, 12, WebGLConstants.ARRAY_BUFFER);
addBufferView(gltf, bufferState.uvBuffers, bufferState.uvAccessors, 8, WebGLConstants.ARRAY_BUFFER);
addBufferView(gltf, bufferState.indexBuffers, bufferState.indexAccessors, undefined, WebGLConstants.ELEMENT_ARRAY_BUFFER);
var buffers = [];
buffers = buffers.concat(vertexBuffers, indexBuffers);
var buffer = Buffer.concat(buffers);
buffers = buffers.concat(bufferState.positionBuffers, bufferState.normalBuffers, bufferState.uvBuffers, bufferState.indexBuffers);
var buffer = getBufferPadded(Buffer.concat(buffers));
gltf.buffers.push({
name : bufferName,
byteLength : buffer.byteLength,
name : 'buffer',
byteLength : buffer.length,
extras : {
_obj2gltf : {
source : buffer
}
}
});
gltf.bufferViews.push({
name : vertexBufferViewName,
buffer : 0,
byteLength : vertexBufferByteLength,
byteOffset : 0,
target : WebGLConstants.ARRAY_BUFFER
});
gltf.bufferViews.push({
name : indexBufferViewName,
buffer : 0,
byteLength : indexBufferByteLength,
byteOffset : vertexBufferByteLength,
target : WebGLConstants.ELEMENT_ARRAY_BUFFER
});
}
function getImage(images, imagePath) {
@ -304,7 +313,7 @@ function encodePng(pixels, width, height, inputChannels, outputChannels) {
// Constants defined by pngjs
var rgbColorType = 2;
var rgbaColorType = 4;
var rgbaColorType = 6;
var colorType = outputChannels === 4 ? rgbaColorType : rgbColorType;
var inputColorType = inputChannels === 4 ? rgbaColorType : rgbColorType;
@ -736,16 +745,13 @@ function getMaterial(gltf, materials, images, materialName, hasNormals, options)
return materialIndex;
}
function addVertexAttribute(gltf, bufferState, array, components, name) {
var buffer = array.toFloatBuffer();
function addVertexAttribute(gltf, array, components, name) {
var count = array.length / components;
var minMax = array.getMinMax(components);
var type = (components === 3 ? 'VEC3' : 'VEC2');
var accessor = {
name : name,
bufferView : bufferState.vertexBufferViewIndex,
byteOffset : bufferState.vertexBufferByteOffset,
componentType : WebGLConstants.FLOAT,
count : count,
min : minMax.min,
@ -753,24 +759,18 @@ function addVertexAttribute(gltf, bufferState, array, components, name) {
type : type
};
bufferState.vertexBufferByteOffset += buffer.length;
bufferState.vertexBuffers.push(buffer);
var accessorIndex = gltf.accessors.length;
gltf.accessors.push(accessor);
return accessorIndex;
}
function addIndexArray(gltf, bufferState, array, uint32Indices, name) {
var buffer = uint32Indices ? array.toUint32Buffer() : array.toUint16Buffer();
function addIndexArray(gltf, array, uint32Indices, name) {
var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT;
var count = array.length;
var minMax = array.getMinMax(1);
var accessor = {
name : name,
bufferView : bufferState.indexBufferViewIndex,
byteOffset : bufferState.indexBufferByteOffset,
componentType : componentType,
count : count,
min : minMax.min,
@ -778,9 +778,6 @@ function addIndexArray(gltf, bufferState, array, uint32Indices, name) {
type : 'SCALAR'
};
bufferState.indexBufferByteOffset += buffer.length;
bufferState.indexBuffers.push(buffer);
var accessorIndex = gltf.accessors.length;
gltf.accessors.push(accessor);
return accessorIndex;
@ -807,15 +804,25 @@ function addMesh(gltf, materials, images, bufferState, uint32Indices, mesh, opti
var hasNormals = mesh.normals.length > 0;
var hasUVs = mesh.uvs.length > 0;
var accessorIndex;
var attributes = {};
if (hasPositions) {
attributes.POSITION = addVertexAttribute(gltf, bufferState, mesh.positions, 3, mesh.name + '_positions');
accessorIndex = addVertexAttribute(gltf, mesh.positions, 3, mesh.name + '_positions');
attributes.POSITION = accessorIndex;
bufferState.positionBuffers.push(mesh.positions.toFloatBuffer());
bufferState.positionAccessors.push(accessorIndex);
}
if (hasNormals) {
attributes.NORMAL = addVertexAttribute(gltf, bufferState, mesh.normals, 3, mesh.name + '_normals');
accessorIndex = addVertexAttribute(gltf, mesh.normals, 3, mesh.name + '_normals');
attributes.NORMAL = accessorIndex;
bufferState.normalBuffers.push(mesh.normals.toFloatBuffer());
bufferState.normalAccessors.push(accessorIndex);
}
if (hasUVs) {
attributes.TEXCOORD_0 = addVertexAttribute(gltf, bufferState, mesh.uvs, 2, mesh.name + '_texcoords');
accessorIndex = addVertexAttribute(gltf, mesh.uvs, 2, mesh.name + '_texcoords');
attributes.TEXCOORD_0 = accessorIndex;
bufferState.uvBuffers.push(mesh.uvs.toFloatBuffer());
bufferState.uvAccessors.push(accessorIndex);
}
// Unload resources
@ -828,7 +835,11 @@ function addMesh(gltf, materials, images, bufferState, uint32Indices, mesh, opti
var primitivesLength = primitives.length;
for (var i = 0; i < primitivesLength; ++i) {
var primitive = primitives[i];
var indexAccessorIndex = addIndexArray(gltf, bufferState, primitive.indices, uint32Indices, mesh.name + '_' + i + '_indices');
var indexAccessorIndex = addIndexArray(gltf, primitive.indices, uint32Indices, mesh.name + '_' + i + '_indices');
var indexBuffer = uint32Indices ? primitive.indices.toUint32Buffer() : primitive.indices.toUint16Buffer();
bufferState.indexBuffers.push(indexBuffer);
bufferState.indexAccessors.push(indexAccessorIndex);
primitive.indices = undefined; // Unload resources
var materialIndex = getMaterial(gltf, materials, images, primitive.material, hasNormals, options);

19
lib/getBufferPadded.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
module.exports = getBufferPadded;
/**
* Pad the buffer to the next 4-byte boundary to ensure proper alignment for the section that follows.
*
* @param {Buffer} buffer The buffer.
* @returns {Buffer} The padded buffer.
*
* @private
*/
function getBufferPadded(buffer) {
var boundary = 4;
var byteLength = buffer.length;
var remainder = byteLength % boundary;
var padding = (remainder === 0) ? 0 : boundary - remainder;
var emptyBuffer = Buffer.alloc(padding);
return Buffer.concat([buffer, emptyBuffer]);
}

View File

@ -0,0 +1,34 @@
'use strict';
var Cesium = require('cesium');
var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
module.exports = getJsonBufferPadded;
/**
* Convert the JSON object to a padded buffer.
*
* Pad the JSON with extra whitespace to fit the next 4-byte boundary. This ensures proper alignment
* for the section that follows.
*
* @param {Object} [json] The JSON object.
* @returns {Buffer} The padded JSON buffer.
*
* @private
*/
function getJsonBufferPadded(json) {
var string = JSON.stringify(json);
var boundary = 8;
var byteLength = Buffer.byteLength(string);
var remainder = byteLength % boundary;
var padding = (remainder === 0) ? 0 : boundary - remainder;
var whitespace = '';
for (var i = 0; i < padding; ++i) {
whitespace += ' ';
}
string += whitespace;
return Buffer.from(string);
}

67
lib/gltfToGlb.js Normal file
View File

@ -0,0 +1,67 @@
'use strict';
var Cesium = require('cesium');
var getJsonBufferPadded = require('./getJsonBufferPadded');
var isDataUri = Cesium.isDataUri;
module.exports = gltfToGlb;
/**
* Convert a glTF to binary glTF.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @returns {Promise} A promise that resolves to a buffer containing the binary glTF.
*
* @private
*/
function gltfToGlb(gltf) {
var buffer = gltf.buffers[0];
var binaryBuffer;
if (isDataUri(buffer.uri)) {
binaryBuffer = dataUriToBuffer(buffer.uri);
delete buffer.uri;
} else {
binaryBuffer = Buffer.alloc(0);
}
// Create padded binary scene string
var jsonBuffer = getJsonBufferPadded(gltf);
// Allocate buffer (Global header) + (JSON chunk header) + (JSON chunk) + (Binary chunk header) + (Binary chunk)
var glbLength = 12 + 8 + jsonBuffer.length + 8 + binaryBuffer.length;
var glb = Buffer.alloc(glbLength);
// Write binary glTF header (magic, version, length)
var byteOffset = 0;
glb.writeUInt32LE(0x46546C67, byteOffset);
byteOffset += 4;
glb.writeUInt32LE(2, byteOffset);
byteOffset += 4;
glb.writeUInt32LE(glbLength, byteOffset);
byteOffset += 4;
// Write JSON Chunk header (length, type)
glb.writeUInt32LE(jsonBuffer.length, byteOffset);
byteOffset += 4;
glb.writeUInt32LE(0x4E4F534A, byteOffset); // JSON
byteOffset += 4;
// Write JSON Chunk
jsonBuffer.copy(glb, byteOffset);
byteOffset += jsonBuffer.length;
// Write Binary Chunk header (length, type)
glb.writeUInt32LE(binaryBuffer.length, byteOffset);
byteOffset += 4;
glb.writeUInt32LE(0x004E4942, byteOffset); // BIN
byteOffset += 4;
// Write Binary Chunk
binaryBuffer.copy(glb, byteOffset);
return glb;
}
function dataUriToBuffer(dataUri) {
var data = dataUri.slice(dataUri.indexOf(','));
return Buffer.from(data, 'base64');
}

View File

@ -72,21 +72,19 @@ function obj2gltf(objPath, gltfPath, options) {
throw new DeveloperError('gltfPath is required');
}
if (metallicRoughness + specularGlossiness + materialsCommon > 1) {
throw new DeveloperError('Only one material type may be set from [--metallicRoughness, --specularGlossiness, --materialsCommon].');
}
var extension = path.extname(gltfPath).toLowerCase();
var modelName = path.basename(gltfPath, path.extname(gltfPath));
if (extension === '.glb') {
if (binary || extension === '.glb') {
binary = true;
}
if (binary) {
extension = '.glb';
}
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
if (metallicRoughness + specularGlossiness + materialsCommon > 1) {
return Promise.reject(new RuntimeError('Only one material type may be set from [--metallicRoughness, --specularGlossiness, --materialsCommon].'));
}
var jsonOptions = {
spaces : 2
};

View File

@ -4,6 +4,7 @@ var fsExtra = require('fs-extra');
var mime = require('mime');
var path = require('path');
var Promise = require('bluebird');
var getBufferPadded = require('./getBufferPadded');
var defined = Cesium.defined;
var RuntimeError = Cesium.RuntimeError;
@ -125,19 +126,32 @@ function writeEmbeddedBuffer(gltf) {
}
function writeEmbeddedTextures(gltf) {
var bufferSource = gltf.buffers[0].extras._obj2gltf.source;
var buffer = gltf.buffers[0];
var bufferExtras = buffer.extras._obj2gltf;
var bufferSource = bufferExtras.source;
var images = gltf.images;
var imagesLength = images.length;
var sources = [bufferSource];
var byteOffset = bufferSource.length;
for (var i = 0; i < imagesLength; ++i) {
var image = images[i];
var extras = image.extras._obj2gltf;
var imageSource = extras.source;
var imageByteLength = imageSource.length;
image.mimeType = mime.lookup(extras.extension);
image.bufferView = gltf.bufferViews.length;
gltf.bufferViews.push({
buffer : 0,
byteOffset : bufferSource.length,
byteLength : imageSource.byteLength
byteOffset : byteOffset,
byteLength : imageByteLength
});
bufferSource = Buffer.concat([bufferSource, imageSource]);
byteOffset += imageByteLength;
sources.push(imageSource);
}
var source = getBufferPadded(Buffer.concat(sources));
bufferExtras.source = source;
buffer.byteLength = source.length;
}