Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified bin/gltf-pipeline.js
100644 → 100755
Empty file.
82 changes: 82 additions & 0 deletions lib/addAccessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';
const addToArray = require('./addToArray');

module.exports = addAccessor;

/**
* Get type of an element given its number of components
*
* @param {Number} number of components
* @returns {Number} type of element
*
* @private
*/
function getTypeFromSize(size){
if (size===1) {
return 'SCALAR';
} else if (size===2) {
return 'VEC2';
} else if (size===3) {
return 'VEC3';
} else if (size===4) {
return 'VEC4';
}
}

/**
* Get ID of the component type
*
* @param {String} component type as a string
* @returns {Number} ID of the component type
*
* @private
*/
function getIDComponent(componentType){
if (componentType === 'FLOAT') {
return 5126;
} else if (componentType === 'UNSIGNED_INT') {
return 5125;
} else if (componentType === 'UNSIGNED_SHORT') {
return 5123;
} else if (componentType === 'SHORT') {
return 5122;
} else if (componentType === 'UNSIGNED_BYTE') {
return 5121;
} else if (componentType === 'BYTE') {
return 5120;
}
return -1;
}

/**
* Adds accessor to gltf.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @param {Number} buffer view associated to the accessor.
* @param {Number} number of elements to be accessed in the bufferView
* @param {String} component type as a string matching glTF 2.0 specification
* @param {Number} number of component type per element
* @param {Object} min & max values of the accessor elements
* @returns {Number} The bufferView id of the newly added bufferView.
*
* @private
*/
function addAccessor(gltf, bufferView, count, componentType, size, minmax){

const type = getTypeFromSize(size);

const accessor = {
bufferView : bufferView,
byteOffset : 0,
componentType : getIDComponent(componentType),
count : count,
type: type
};

if (minmax) {
accessor.min = minmax.min;
accessor.max = minmax.max;
}

return addToArray(gltf.accessors, accessor);
}
183 changes: 183 additions & 0 deletions lib/decompressDracoMeshes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
'use strict';
const Cesium = require('cesium');
const draco3d = require('draco3d');
const Promise = require('bluebird');
const ForEach = require('./ForEach');
const addAccessor = require('./addAccessor');
const addBuffer = require('./addBuffer');
const arrayFill = Cesium.arrayFill;

const defined = Cesium.defined;

let decoderModulePromise;

module.exports = decompressDracoMeshes;

/**
* Decompresses meshes using Draco compression from the glTF model.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @param {Object} options The same options object as {@link processGltf}
* @returns {Promise} A promise that resolves to the glTF asset with compressed meshes.
*
* @private
*/
function decompressDracoMeshes(gltf, options) {
if (!defined(decoderModulePromise)) {
// Prepare encoder for compressing meshes.
decoderModulePromise = Promise.resolve(draco3d.createDecoderModule({}));
}

return decoderModulePromise.then(function(decoderModule) {
return decompress(gltf, options, decoderModule);
});
}

// TODO store those buffers somewhere to prevent doing the conversion everytime.
function getBuffer(gltf, bufferId){
const buffer = gltf.buffers[bufferId];
if (defined(buffer.url)){
// TODO check if URL is a file or not !!!
const data = buffer.url.split(',')[1];
return Buffer.from(data).toString('base64');
} else if (defined(buffer.extras) && defined(buffer.extras._pipeline)) {
return buffer.extras._pipeline.source;
}
}

function retrieveDracoBuffer(gltf, bufferViewId) {

const bufferView = gltf.bufferViews[bufferViewId];

const buffer = getBuffer(gltf, bufferView.buffer);

return Buffer.from(buffer.buffer, bufferView.byteOffset, bufferView.byteLength);

}

function decodeDracoData(rawBuffer, decoderModule, decoder) {

const buffer = new decoderModule.DecoderBuffer();
buffer.Init(new Int8Array(rawBuffer), rawBuffer.byteLength);
const geometryType = decoder.GetEncodedGeometryType(buffer);

let dracoGeometry;
let status;
if (geometryType === decoderModule.TRIANGULAR_MESH) {
dracoGeometry = new decoderModule.Mesh();
status = decoder.DecodeBufferToMesh(buffer, dracoGeometry);
if (!status.ok()) {
console.error(`Could not decode Draco mesh: ${status.error_msg()}`);
}
} else {
const errorMsg = 'Error: Unknown geometry type.';
console.error(errorMsg);
}
decoderModule.destroy(buffer);

return dracoGeometry;
}

function decompress(gltf, options, decoderModule) {

gltf._work = {
gltf: {
buffers:[],
bufferViews:[],
accessors:[]
}
};

ForEach.mesh(gltf, (mesh) => {
ForEach.meshPrimitive(mesh, (primitive) => {

if (defined(primitive.extensions) && defined(primitive.extensions.KHR_draco_mesh_compression) ) {

const draco_extension = primitive.extensions.KHR_draco_mesh_compression;

// retrieve the DracoBuffer from the bufferview
const buffer = retrieveDracoBuffer(gltf, draco_extension.bufferView);

// Decode draco data
const decoder = new decoderModule.Decoder();
const mesh = decodeDracoData(buffer, decoderModule, decoder);
decoderModule.destroy(decoder);

const numFaces = mesh.num_faces();
const numIndices = numFaces * 3;
const numPoints = mesh.num_points();
const indices = new Uint32Array(numIndices);

// retrieve indices
const ia = new decoderModule.DracoInt32Array();
for (let i = 0; i < numFaces; ++i) {
decoder.GetFaceFromMesh(mesh, i, ia);
const index = i * 3;
indices[index] = ia.GetValue(0);
indices[index + 1] = ia.GetValue(1);
indices[index + 2] = ia.GetValue(2);
}
decoderModule.destroy(ia);

const bufferView_indices = addBuffer(gltf._work.gltf, new Uint8Array(indices.buffer) );
const indices_accessor = addAccessor(gltf._work.gltf, bufferView_indices, numIndices, 'UNSIGNED_INT', 1);

primitive.indices = indices_accessor;

// Retrieve accessors data
const attrs = {POSITION: 3, NORMAL: 3, COLOR: 3, TEX_COORD: 2};
const attributes_accessors = {};
Object.keys(attrs).forEach((attr) => {

const stride = attrs[attr];
const numValues = numPoints * stride;
const decoderAttr = decoderModule[attr];
const attrId = decoder.GetAttributeId(mesh, decoderAttr);

if (attrId < 0) {
return;
}

const attribute = decoder.GetAttribute(mesh, attrId);
const attributeData = new decoderModule.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(mesh, attribute, attributeData);

const min = arrayFill(new Array(stride), Number.POSITIVE_INFINITY);
const max = arrayFill(new Array(stride), Number.NEGATIVE_INFINITY);

const attributeDataArray = new Float32Array(numValues);
for (let i = 0; i < numValues; ++i) {
attributeDataArray[i] = attributeData.GetValue(i);

const index = i % stride;
min[index] = Math.min(min[index], attributeDataArray[i]);
max[index] = Math.max(max[index], attributeDataArray[i]);
}

const bufferViewId = addBuffer(gltf._work.gltf, new Uint8Array(attributeDataArray.buffer));

attributes_accessors[attr] = addAccessor(gltf._work.gltf, bufferViewId, numPoints, 'FLOAT', stride, { min: min, max: max});

decoderModule.destroy(attributeData);

});

primitive.attributes = attributes_accessors;

delete primitive.extensions.KHR_draco_mesh_compression;

}

});
});

// Translating this into the decoded gltf
gltf.buffers = gltf._work.gltf.buffers;
gltf.bufferViews = gltf._work.gltf.bufferViews;
gltf.accessors = gltf._work.gltf.accessors;

delete gltf._work;

gltf.extensionsUsed = gltf.extensionsUsed.filter(extension => extension !== 'KHR_draco_mesh_compression');

}
7 changes: 6 additions & 1 deletion lib/processGltf.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const removeUnusedElements = require('./removeUnusedElements');
const updateVersion = require('./updateVersion');
const writeResources = require('./writeResources');
const compressDracoMeshes = require('./compressDracoMeshes');
const decompressDracoMeshes = require('./decompressDracoMeshes');

const clone = Cesium.clone;
const defaultValue = Cesium.defaultValue;
Expand Down Expand Up @@ -82,7 +83,11 @@ function printStats(gltf, options, processed) {
function getStages(options) {
const stages = [];
if (defined(options.dracoOptions)) {
stages.push(compressDracoMeshes);
if (defined(options.dracoOptions.decompress) && options.dracoOptions.decompress) {
stages.push(decompressDracoMeshes);
} else {
stages.push(compressDracoMeshes);
}
}
if (!options.keepUnusedElements) {
stages.push(function(gltf, options) {
Expand Down
19 changes: 19 additions & 0 deletions specs/lib/processGltfSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ describe('processGltf', () => {
expect(hasExtension(results.gltf, 'KHR_draco_mesh_compression')).toBe(true);
});

it('uses draco decompression', async () => {
const gltf = fsExtra.readJsonSync(gltfPath);
let options = {
dracoOptions: {
compressionLevel: 7
}
};
let results = await processGltf(gltf, options);
expect(hasExtension(results.gltf, 'KHR_draco_mesh_compression')).toBe(true);

options = {
dracoOptions: {
decompress : true
}
};
results = await processGltf(results.gltf, options);
expect(hasExtension(results.gltf, 'KHR_draco_mesh_compression')).toBe(false);
});

it('runs custom stages', async () => {
spyOn(console, 'log');
const gltf = fsExtra.readJsonSync(gltfPath);
Expand Down