Skip to content

Commit

Permalink
Merge pull request #162 from AnalyticalGraphicsInc/combine-primitives
Browse files Browse the repository at this point in the history
Combine primitives
  • Loading branch information
lilleyse authored Nov 11, 2016
2 parents 707ca06 + 30648a4 commit 6ca0f8c
Show file tree
Hide file tree
Showing 23 changed files with 1,049 additions and 1,709 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Change Log
==========

### Next Release
* Fixed `combinePrimitives` stage and re-added it to the pipeline. [#108](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/issues/108)

### 0.1.0-alpha5 - 2016-11-02

* Added `MergeDuplicateProperties` for stages merging duplicate glTF properties, like materials and shaders. [#152](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/152)
Expand Down
26 changes: 0 additions & 26 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,32 +204,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

### object-values

https://www.npmjs.com/package/object-values

> The MIT License (MIT)
>
> Copyright (c) Sindre Sorhus <[email protected]> (sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

### promise

https://www.npmjs.com/package/promise
Expand Down
8 changes: 4 additions & 4 deletions lib/AccessorReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ AccessorReader.prototype.write = function(data, componentType, dataOffset) {
};

/**
* Get if the AccessorReader is at the end of the accessor data.
* Get if the AccessorReader is past the end of the accessor data.
*
* @returns {Boolean} True if there is more data to read, false if not.
* @returns {Boolean} True if the current index does not correspond to valid data, False if there is data to read.
*/
AccessorReader.prototype.hasNext = function() {
return this.index < this.count;
AccessorReader.prototype.pastEnd = function() {
return this.index >= this.count;
};

/**
Expand Down
18 changes: 6 additions & 12 deletions lib/Pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var combineMeshes = require('./combineMeshes');
var combineNodes = require('./combineNodes');
var compressIntegerAccessors = require('./compressIntegerAccessors');
var compressTextureCoordinates = require('./compressTextureCoordinates');
// var combinePrimitives = require('./combinePrimitives');
var combinePrimitives = require('./combinePrimitives');
var convertDagToTree = require('./convertDagToTree');
var encodeImages = require('./encodeImages');
var generateNormals = require('./generateNormals');
Expand Down Expand Up @@ -85,21 +85,19 @@ Pipeline.processJSONWithExtras = function(gltfWithExtras, options) {
}
addDefaults(gltfWithExtras, options);
RemoveUnusedProperties.removeAll(gltfWithExtras);
// It is generally better to merge the duplicate vertices before merging accessors.
// Once accessors merge, there is more likely to be overlap in accessor usage between primitives
// which limits the effectiveness of merging duplicate vertices.
generateNormals(gltfWithExtras, options);
mergeDuplicateVertices(gltfWithExtras);
MergeDuplicateProperties.mergeAll(gltfWithExtras);
RemoveUnusedProperties.removeAll(gltfWithExtras);
removeDuplicatePrimitives(gltfWithExtras);
combinePrimitives(gltfWithExtras);
convertDagToTree(gltfWithExtras);
combineNodes(gltfWithExtras);
combineMeshes(gltfWithExtras);
// TODO: Combine primitives can be uncommented and added back into the pipeline once it is fixed, but right now there are too many issues with it to allow in the main pipeline.
// combinePrimitives(gltfWithExtras);
// Merging duplicate vertices again to prevent repeat data in newly combined primitives
// mergeDuplicateVertices(gltfWithExtras);
combinePrimitives(gltfWithExtras);
MergeDuplicateProperties.mergeAll(gltfWithExtras);
removeDuplicatePrimitives(gltfWithExtras);
RemoveUnusedProperties.removeAll(gltfWithExtras);
optimizeForVertexCache(gltfWithExtras);

// run AO after optimizeForVertexCache since AO adds new attributes.
Expand All @@ -108,8 +106,6 @@ Pipeline.processJSONWithExtras = function(gltfWithExtras, options) {
bakeAmbientOcclusion(gltfWithExtras, aoOptions);
}

// Run removeAll stage again after all pipeline stages have been run to remove objects that become unused
RemoveUnusedProperties.removeAll(gltfWithExtras);
var waitForStages = [new Promise(function(resolve) {resolve();})];
if (options.encodeNormals) {
waitForStages.push(octEncodeNormals(gltfWithExtras));
Expand All @@ -135,8 +131,6 @@ Pipeline.processJSONWithExtras = function(gltfWithExtras, options) {
}
quantizeAttributes(gltfWithExtras, quantizedOptions);
}
// Remove duplicates again after all stages to minimize the buffer size
mergeDuplicateVertices(gltfWithExtras);
return encodeImages(gltfWithExtras);
});
};
Expand Down
194 changes: 194 additions & 0 deletions lib/PrimitiveHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use strict';
var Cesium = require('cesium');
var deepEqual = require('deep-equal');
var AccessorReader = require('./AccessorReader');
var getPrimitiveAttributeSemantics = require('./getPrimitiveAttributeSemantics');
var readAccessor = require('./readAccessor');

var Cartesian3 = Cesium.Cartesian3;
var Matrix4 = Cesium.Matrix4;
var defined = Cesium.defined;

module.exports = {
getAllPrimitives : getAllPrimitives,
getPrimitivesByMaterialMode : getPrimitivesByMaterialMode,
getPrimitiveConflicts : getPrimitiveConflicts,
primitiveEquals : primitiveEquals,
primitivesShareAttributeAccessor : primitivesShareAttributeAccessor,
primitivesHaveOverlappingIndexAccessors : primitivesHaveOverlappingIndexAccessors,
transformPrimitives : transformPrimitives
};

function primitivesShareAttributeAccessor(primitive, comparePrimitive) {
var attributes = primitive.attributes;
var compareAttributes = comparePrimitive.attributes;
for (var attribute in attributes) {
if (attributes.hasOwnProperty(attribute)) {
if (compareAttributes.hasOwnProperty(attribute)) {
if (attributes[attribute] === compareAttributes[attribute]) {
return true;
}
}
}
}
return false;
}

function primitivesHaveOverlappingIndexAccessors(gltf, primitive, comparePrimitive) {
var accessors = gltf.accessors;
var indexAccessorId = primitive.indices;
var compareIndexAccessorId = comparePrimitive.indices;
if (!defined(indexAccessorId) || !defined(compareIndexAccessorId)) {
return false;
}
if (indexAccessorId === compareIndexAccessorId) {
return true;
}
var indexAccessor = accessors[indexAccessorId];
var compareIndexAccessor = accessors[compareIndexAccessorId];
var indices = [];
readAccessor(gltf, indexAccessor, indices);
var accessorReader = new AccessorReader(gltf, compareIndexAccessor);
var value = [];

while (!accessorReader.pastEnd()) {
var index = accessorReader.read(value)[0];
if (indices.indexOf(index) >= 0) {
return true;
}
accessorReader.next();
}
return false;
}

function transformPrimitives(gltf, primitives, transform) {
var inverseTranspose = new Matrix4();
if (Matrix4.equals(transform, Matrix4.IDENTITY)) {
return;
}
var accessors = gltf.accessors;
Matrix4.inverseTransformation(transform, inverseTranspose);
Matrix4.transpose(inverseTranspose, inverseTranspose);

var scratchIndexArray = [];
var scratchCartesianArray = [];
var scratchCartesian = new Cartesian3();
var doneIndicesByAccessor = {};

var primitivesLength = primitives.length;
for (var i = 0; i < primitivesLength; i++) {
var primitive = primitives[i];
var attributes = primitive.attributes;
var indexAccessorReader;
var index = 0;
if (defined(primitive.indices)) {
indexAccessorReader = new AccessorReader(gltf, accessors[primitive.indices]);
indexAccessorReader.read(scratchIndexArray);
index = scratchIndexArray[0];
}
var positionAccessorReader;
var positionSemantics = getPrimitiveAttributeSemantics(primitive, 'POSITION');
var positionAccessorId = attributes[positionSemantics[0]];
if (positionSemantics.length > 0) {
doneIndicesByAccessor[positionAccessorId] = {};
positionAccessorReader = new AccessorReader(gltf, accessors[positionAccessorId]);
}
var normalAccessorReader;
var normalSemantics = getPrimitiveAttributeSemantics(primitive, 'NORMAL');
var normalAccessorId = attributes[normalSemantics[0]];
if (normalSemantics.length > 0) {
doneIndicesByAccessor[normalAccessorId] = {};
normalAccessorReader = new AccessorReader(gltf, accessors[normalAccessorId]);
}
var keepReading = true;
while (keepReading) {
if (defined(positionAccessorReader) && !doneIndicesByAccessor[positionAccessorId][index]) {
positionAccessorReader.index = index;
positionAccessorReader.read(scratchCartesianArray);
Cartesian3.unpack(scratchCartesianArray, 0, scratchCartesian);
Matrix4.multiplyByPoint(transform, scratchCartesian, scratchCartesian);
Cartesian3.pack(scratchCartesian, scratchCartesianArray);
positionAccessorReader.write(scratchCartesianArray);
doneIndicesByAccessor[positionAccessorId][index] = true;
}
if (defined(normalAccessorReader) && !doneIndicesByAccessor[normalAccessorId][index]) {
normalAccessorReader.index = index;
normalAccessorReader.read(scratchCartesianArray);
Cartesian3.unpack(scratchCartesianArray, 0, scratchCartesian);
Matrix4.multiplyByPointAsVector(inverseTranspose, scratchCartesian, scratchCartesian);
Cartesian3.normalize(scratchCartesian, scratchCartesian);
Cartesian3.pack(scratchCartesian, scratchCartesianArray);
normalAccessorReader.write(scratchCartesianArray);
doneIndicesByAccessor[normalAccessorId][index] = true;
}
if (defined(indexAccessorReader)) {
if (!indexAccessorReader.pastEnd()) {
indexAccessorReader.next();
indexAccessorReader.read(scratchIndexArray);
index = scratchIndexArray[0];
} else {
keepReading = false;
}
} else {
if (!positionAccessorReader.pastEnd() && !normalAccessorReader.pastEnd()) {
index++;
} else {
keepReading = false;
}
}
}
}
}

function getPrimitivesByMaterialMode(primitives) {
var primitivesLength = primitives.length;
var primitivesByMaterialMode = {};
for (var i = 0; i < primitivesLength; i++) {
var primitive = primitives[i];
var materialId = primitive.material;
var primitivesByMode = primitivesByMaterialMode[materialId];
if (!defined(primitivesByMode)) {
primitivesByMode = {};
primitivesByMaterialMode[materialId] = primitivesByMode;
}
var mode = primitive.mode;
var primitivesArray = primitivesByMode[mode];
if (!defined(primitivesArray)) {
primitivesArray = [];
primitivesByMode[mode] = primitivesArray;
}
primitivesArray.push(primitive);
}
return primitivesByMaterialMode;
}

function getPrimitiveConflicts(primitives, primitive) {
var primitivesLength = primitives.length;
var conflicts = [];
for (var i = 0; i < primitivesLength; i++) {
var otherPrimitive = primitives[i];
if (primitive !== otherPrimitive && primitivesShareAttributeAccessor(primitive, otherPrimitive)) {
conflicts.push(i);
}
}
return conflicts;
}

function getAllPrimitives(gltf) {
var primitives = [];
var meshes = gltf.meshes;
for (var meshId in meshes) {
if (meshes.hasOwnProperty(meshId)) {
var mesh = meshes[meshId];
primitives = primitives.concat(mesh.primitives);
}
}
return primitives;
}

function primitiveEquals(primitiveOne, primitiveTwo) {
return primitiveOne.mode === primitiveTwo.mode &&
primitiveOne.material === primitiveTwo.material &&
primitiveOne.indices === primitiveTwo.indices &&
deepEqual(primitiveOne.attributes, primitiveTwo.attributes);
}
11 changes: 10 additions & 1 deletion lib/RemoveUnusedProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ RemoveUnusedProperties.removeCameras = function(gltf) {
*/
RemoveUnusedProperties.removeMeshes = function(gltf) {
var usedMeshIds = {};
var meshes = gltf.meshes;
var nodes = gltf.nodes;

// Build hash of used meshes by iterating through nodes
Expand All @@ -124,7 +125,15 @@ RemoveUnusedProperties.removeMeshes = function(gltf) {
var length = nodeMeshes.length;
for (var i = 0; i < length; i++) {
var id = nodeMeshes[i];
usedMeshIds[id] = true;
var mesh = meshes[id];
if (!defined(mesh.primitives) || mesh.primitives.length === 0) {
// This is an empty mesh, remove it
nodeMeshes.splice(i, 1);
i--;
length--;
} else {
usedMeshIds[id] = true;
}
}
}
}
Expand Down
Loading

0 comments on commit 6ca0f8c

Please sign in to comment.