Skip to content

Commit

Permalink
Merge pull request #465 from Squareys/remove-unused-elements
Browse files Browse the repository at this point in the history
Remove more unused elements
  • Loading branch information
lilleyse committed Jun 8, 2019
2 parents 179cfcd + 145640e commit ec0e875
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 28 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Change Log
==========

### HEAD

* Added removal of unused materials, nodes and meshes. [#465](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/465)
* Added `keepUnusedElements` flag to keep unused materials, nodes and meshes. [#465](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/465)

### 2.1.3 - 2019-03-21

* Fixed a crash when saving separate resources that would exceed the Node buffer size limit. [#468](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/468)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ processGltf(gltf, options)
|`--separate`, `-s`|Write separate buffers, shaders, and textures instead of embedding them in the glTF.|No, default `false`|
|`--separateTextures`, `-t`|Write out separate textures only.|No, default `false`|
|`--stats`|Print statistics to console for output glTF file.|No, default `false`|
|`--keepUnusedElements`|Keep unused materials, nodes and meshes.|No, default `false`|
|`--draco.compressMeshes`, `-d`|Compress the meshes using Draco. Adds the KHR_draco_mesh_compression extension.|No, default `false`|
|`--draco.compressionLevel`|Draco compression level [0-10], most is 10, least is 0. A value of 0 will apply sequential encoding and preserve face order.|No, default `7`|
|`--draco.quantizePositionBits`|Quantization bits for position attribute when using Draco compression.|No, default `14`|
Expand Down
5 changes: 5 additions & 0 deletions bin/gltf-pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ const argv = yargs
type: 'boolean',
default: defaults.stats
},
keepUnusedElements: {
describe: 'Keep unused materials, nodes and meshes.',
type: 'boolean',
default: defaults.keepUnusedElements
},
'draco.compressMeshes': {
alias: 'd',
describe: 'Compress the meshes using Draco. Adds the KHR_draco_mesh_compression extension.',
Expand Down
15 changes: 15 additions & 0 deletions lib/ForEach.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,21 @@ ForEach.skin = function(gltf, handler) {
return ForEach.topLevel(gltf, 'skins', handler);
};

ForEach.skinJoint = function(skin, handler) {
const joints = skin.joints;
if (defined(joints)) {
const jointsLength = joints.length;
for (let i = 0; i < jointsLength; i++) {
const joint = joints[i];
const value = handler(joint);

if (defined(value)) {
return value;
}
}
}
};

ForEach.techniqueAttribute = function(technique, handler) {
const attributes = technique.attributes;
for (const attributeName in attributes) {
Expand Down
2 changes: 1 addition & 1 deletion lib/compressDracoMeshes.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ function compressDracoMeshes(gltf, options) {
} else {
addExtensionsRequired(gltf, 'KHR_draco_mesh_compression');
}
removeUnusedElements(gltf);
removeUnusedElements(gltf, ['accessor', 'bufferView', 'buffer']);

if (uncompressedFallback) {
assignMergedBufferNames(gltf);
Expand Down
12 changes: 12 additions & 0 deletions lib/processGltf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const getStatistics = require('./getStatistics');
const readResources = require('./readResources');
const removeDefaults = require('./removeDefaults');
const removePipelineExtras = require('./removePipelineExtras');
const removeUnusedElements = require('./removeUnusedElements');
const updateVersion = require('./updateVersion');
const writeResources = require('./writeResources');
const compressDracoMeshes = require('./compressDracoMeshes');
Expand Down Expand Up @@ -82,6 +83,11 @@ function getStages(options) {
if (defined(options.dracoOptions)) {
stages.push(compressDracoMeshes);
}
if (!options.keepUnusedElements) {
stages.push(function(gltf, options) {
removeUnusedElements(gltf);
});
}
return stages;
}

Expand Down Expand Up @@ -113,6 +119,12 @@ processGltf.defaults = {
* @default false
*/
stats: false,
/**
* Keep unused 'node', 'mesh' and 'material' elements.
* @type Boolean
* @default false
*/
keepUnusedElements: false,
/**
* Gets or sets whether to compress the meshes using Draco. Adds the KHR_draco_mesh_compression extension.
* @type Boolean
Expand Down
185 changes: 177 additions & 8 deletions lib/removeUnusedElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,38 @@ const Cesium = require('cesium');
const ForEach = require('./ForEach');
const hasExtension = require('./hasExtension');

const defaultValue = Cesium.defaultValue;
const defined = Cesium.defined;

module.exports = removeUnusedElements;

const allElementTypes = ['mesh', 'node', 'material', 'accessor', 'bufferView', 'buffer'];

/**
* Removes unused elements from gltf.
* This function currently only works for accessors, buffers, and bufferViews.
*
* @param {Object} gltf A javascript object containing a glTF asset.
* @param {String[]} [elementTypes=['mesh', 'node', 'material', 'accessor', 'bufferView', 'buffer']] Element types to be removed. Needs to be a subset of ['mesh', 'node', 'material', 'accessor', 'bufferView', 'buffer'], other items will be ignored.
*
* @private
*/
function removeUnusedElements(gltf) {
removeUnusedElementsByType(gltf, 'accessor');
removeUnusedElementsByType(gltf, 'bufferView');
removeUnusedElementsByType(gltf, 'buffer');
function removeUnusedElements(gltf, elementTypes) {
elementTypes = defaultValue(elementTypes, allElementTypes);
allElementTypes.forEach(function(type) {
if (elementTypes.indexOf(type) > -1) {
removeUnusedElementsByType(gltf, type);
}
});
return gltf;
}

const TypeToGltfElementName = {
accessor: 'accessors',
buffer: 'buffers',
bufferView: 'bufferViews'
bufferView: 'bufferViews',
node: 'nodes',
material: 'materials',
mesh: 'meshes'
};

function removeUnusedElementsByType(gltf, type) {
Expand Down Expand Up @@ -145,8 +154,8 @@ Remove.bufferView = function(gltf, bufferViewId) {
});

if (hasExtension(gltf, 'KHR_draco_mesh_compression')) {
ForEach.mesh(gltf, function (mesh) {
ForEach.meshPrimitive(mesh, function (primitive) {
ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
if (defined(primitive.extensions) &&
defined(primitive.extensions.KHR_draco_mesh_compression)) {
if (primitive.extensions.KHR_draco_mesh_compression.bufferView > bufferViewId) {
Expand All @@ -158,6 +167,88 @@ Remove.bufferView = function(gltf, bufferViewId) {
}
};

Remove.mesh = function(gltf, meshId) {
const meshes = gltf.meshes;
meshes.splice(meshId, 1);

ForEach.node(gltf, function(node) {
if (defined(node.mesh)) {
if (node.mesh > meshId) {
node.mesh--;
} else if (node.mesh === meshId) {
// Remove reference to deleted mesh
delete node.mesh;
}
}
});
};

Remove.node = function(gltf, nodeId) {
const nodes = gltf.nodes;
nodes.splice(nodeId, 1);

// Shift all node references
ForEach.skin(gltf, function(skin) {
if (defined(skin.skeleton) && skin.skeleton > nodeId) {
skin.skeleton--;
}

skin.joints = skin.joints.map(function(x) {
return x > nodeId ? x - 1 : x;
});
});
ForEach.animation(gltf, function(animation) {
ForEach.animationChannel(animation, function(channel) {
if (defined(channel.target) && defined(channel.target.node) && (channel.target.node > nodeId)) {
channel.target.node--;
}
});
});
ForEach.technique(gltf, function(technique) {
ForEach.techniqueUniform(technique, function(uniform) {
if (defined(uniform.node) && uniform.node > nodeId) {
uniform.node--;
}
});
});
ForEach.node(gltf, function(node) {
if (!defined(node.children)) {
return;
}

node.children = node.children
.filter(function(x) {
return x !== nodeId; // Remove
})
.map(function(x) {
return x > nodeId ? x - 1 : x; // Shift indices
});
});
ForEach.scene(gltf, function(scene) {
scene.nodes = scene.nodes
.filter(function(x) {
return x !== nodeId; // Remove
})
.map(function(x) {
return x > nodeId ? x - 1 : x; // Shift indices
});
});
};

Remove.material = function(gltf, materialId) {
const materials = gltf.materials;
materials.splice(materialId, 1);

// Shift other material ids
ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
if (defined(primitive.material) && primitive.material > materialId) {
primitive.material--;
}
});
});
};

/**
* Contains functions for getting a list of element ids in use by the glTF asset.
* @constructor
Expand Down Expand Up @@ -260,3 +351,81 @@ getListOfElementsIdsInUse.bufferView = function(gltf) {

return usedBufferViewIds;
};

getListOfElementsIdsInUse.mesh = function(gltf) {
const usedMeshIds = {};
ForEach.node(gltf, function(node) {
if (defined(node.mesh && defined(gltf.meshes))) {
const mesh = gltf.meshes[node.mesh];
if (defined(mesh) && defined(mesh.primitives) && (mesh.primitives.length > 0)) {
usedMeshIds[node.mesh] = true;
}
}
});

return usedMeshIds;
};

// Check if node is empty. It is considered empty if neither referencing
// mesh, camera, extensions and has no children
function nodeIsEmpty(gltf, node) {
if (defined(node.mesh) || defined(node.camera) || defined(node.skin)
|| defined(node.weights) || defined(node.extras)
|| (defined(node.extensions) && node.extensions.length !== 0)) {
return false;
}

// Empty if no children or children are all empty nodes
return !defined(node.children)
|| node.children.filter(function(n) {
return !nodeIsEmpty(gltf, gltf.nodes[n]);
}).length === 0;
}

getListOfElementsIdsInUse.node = function(gltf) {
const usedNodeIds = {};
ForEach.node(gltf, function(node, nodeId) {
if (!nodeIsEmpty(gltf, node)) {
usedNodeIds[nodeId] = true;
}
});
ForEach.skin(gltf, function(skin) {
if (defined(skin.skeleton)) {
usedNodeIds[skin.skeleton] = true;
}

ForEach.skinJoint(skin, function(joint) {
usedNodeIds[joint] = true;
});
});
ForEach.animation(gltf, function(animation) {
ForEach.animationChannel(animation, function(channel) {
if (defined(channel.target) && defined(channel.target.node)) {
usedNodeIds[channel.target.node] = true;
}
});
});
ForEach.technique(gltf, function(technique) {
ForEach.techniqueUniform(technique, function(uniform) {
if (defined(uniform.node)) {
usedNodeIds[uniform.node] = true;
}
});
});

return usedNodeIds;
};

getListOfElementsIdsInUse.material = function(gltf) {
const usedMaterialIds = {};

ForEach.mesh(gltf, function(mesh) {
ForEach.meshPrimitive(mesh, function(primitive) {
if (defined(primitive.material)) {
usedMaterialIds[primitive.material] = true;
}
});
});

return usedMaterialIds;
};
2 changes: 1 addition & 1 deletion lib/splitPrimitives.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function splitPrimitives(gltf) {
}
}
}
removeUnusedElements(gltf);
removeUnusedElements(gltf, ['accessor', 'bufferView', 'buffer']);
}

return gltf;
Expand Down
2 changes: 1 addition & 1 deletion lib/updateVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ function moveByteStrideToBufferView(gltf) {
}

// Remove unused buffer views
removeUnusedElements(gltf);
removeUnusedElements(gltf, ['accessor', 'bufferView', 'buffer']);
}

function requirePositionAccessorMinMax(gltf) {
Expand Down
2 changes: 1 addition & 1 deletion lib/writeResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function writeResources(gltf, options) {
});

// Buffers need to be written last because images and shaders may write to new buffers
removeUnusedElements(gltf);
removeUnusedElements(gltf, ['acessor', 'bufferView', 'buffer']);
mergeBuffers(gltf, options.name);

ForEach.buffer(gltf, function(buffer, bufferId) {
Expand Down
Loading

0 comments on commit ec0e875

Please sign in to comment.