Skip to content

Commit

Permalink
Merge pull request #243 from AnalyticalGraphicsInc/uint16-check
Browse files Browse the repository at this point in the history
Uint16 check
  • Loading branch information
lilleyse authored Mar 3, 2017
2 parents 4710acb + 616c88e commit ba06022
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Change Log
* Added `updateVersion` stage for patching glTF `0.8` -> `1.0` changes; `addDefaults` no longer calls `processModelMaterialsCommon`. [#223](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/223)
* Added `build-cesium-combined` command to gulp file for generating simple files for other projects. [#231](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/231).
* Change Cesium `Geometry`'s and `VertexFormat`'s `binormal` attribute to bitangent.
* Fixed a bug in `combinePrimitives` where combining primitives can overflow uint16 for the resulting indices. [#230](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/issues/230)
* Made `generateNormals` stage optional and added `smoothNormals` option for generating smooth normals if the model does not have normals. [#240](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/240)

### 0.1.0-alpha10 - 2017-01-10
Expand Down
159 changes: 125 additions & 34 deletions lib/combinePrimitives.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ function combinePrimitivesInGroup(gltf, allPrimitives, primitives, materialId, m
var mergeIndicesGroupLength;
var rootPrimitive;
var primitive;
var concatenatePrimitives = [];
var concatenatePrimitiveGroups = [[]];
var concatenatePrimitiveGroupsLength;
var preservePrimitives = [];
var attributes = {};
var attributeTypes = {};
Expand All @@ -80,8 +81,8 @@ function combinePrimitivesInGroup(gltf, allPrimitives, primitives, materialId, m
accessor = accessors[rootPrimitiveAttributes[semantic]];
attributes[semantic] = [];
attributeTypes[semantic] = {
type : accessor.type,
componentType : accessor.componentType
type: accessor.type,
componentType: accessor.componentType
};
}

Expand Down Expand Up @@ -136,11 +137,12 @@ function combinePrimitivesInGroup(gltf, allPrimitives, primitives, materialId, m
}
} else {
// No conflicts, just concatenate
concatenatePrimitives.push(primitive);
concatenatePrimitiveGroups[0].push(primitive);
}
}
primitives = [];

// merge primitives by combining indices
mergeIndicesPrimitiveGroupsLength = mergeIndicesPrimitiveGroups.length;
var indexOffset = 0;
var startIndices;
Expand Down Expand Up @@ -177,7 +179,7 @@ function combinePrimitivesInGroup(gltf, allPrimitives, primitives, materialId, m
}
indexOffset += accessor.count;
if (indices.length > 0) {
concatenatePrimitives.push(createPrimitive(gltf, attributes, attributeTypes, indices, materialId, mode, uint32Enabled));
concatenatePrimitiveGroups[0].push(createPrimitive(gltf, attributes, attributeTypes, indices, materialId, mode, uint32Enabled));
}
// Reset
indexOffset = 0;
Expand All @@ -191,42 +193,59 @@ function combinePrimitivesInGroup(gltf, allPrimitives, primitives, materialId, m
}
}
}
var concatenatePrimitivesLength = concatenatePrimitives.length;
if (concatenatePrimitivesLength > 1) {
for (i = 0; i < concatenatePrimitivesLength; i++) {
primitive = concatenatePrimitives[i];
startIndices = indices.length;
accessorCount = 0;

// Subdivide groups for concatenatePrimitives if the combined primitive will overflow the index type (uint16/32)
var indexMax = uint32Enabled ? 4294967295 : 65535;
concatenatePrimitiveGroups = subdividePrimitiveGroup(concatenatePrimitiveGroups[0], accessors, indexMax);

// concatenate primitives by combining attribute buffers and adding offset to indices
concatenatePrimitiveGroupsLength = concatenatePrimitiveGroups.length;
for (var h = 0; h < concatenatePrimitiveGroupsLength; h++) {
var concatenatePrimitives = concatenatePrimitiveGroups[h];
var concatenatePrimitivesLength = concatenatePrimitives.length;
if (concatenatePrimitivesLength > 1) {
// Reset
indexOffset = 0;
for (j = 0; j < semanticsLength; j++) {
semantic = semantics[j];
accessor = accessors[primitive.attributes[semantic]];
accessorCount = accessor.count;
readAccessor(gltf, accessor, attributes[semantic], false);
attributes[semantic] = [];
}
if (defined(primitive.indices)) {
readAccessor(gltf, accessors[primitive.indices], indices, false);
} else {
for (k = 0; k < accessorCount; k++) {
indices.push(k);
indices = [];
for (i = 0; i < concatenatePrimitivesLength; i++) {
primitive = concatenatePrimitives[i];
startIndices = indices.length;
accessorCount = 0;
for (j = 0; j < semanticsLength; j++) {
semantic = semantics[j];
accessor = accessors[primitive.attributes[semantic]];
accessorCount = accessor.count;
readAccessor(gltf, accessor, attributes[semantic], false);
}
}
if (indexOffset > 0) {
indicesLength = indices.length;
for (j = startIndices; j < indicesLength; j++) {
indices[j] += indexOffset;
if (defined(primitive.indices)) {
readAccessor(gltf, accessors[primitive.indices], indices, false);
} else {
for (k = 0; k < accessorCount; k++) {
indices.push(k);
}
}
if (indexOffset > 0) {
indicesLength = indices.length;
for (j = startIndices; j < indicesLength; j++) {
indices[j] += indexOffset;
}
}
indexOffset += accessor.count;
}
if (indices.length > 0) {
primitives.push(createPrimitive(gltf, attributes, attributeTypes, indices, materialId, mode, uint32Enabled));
}
indexOffset += accessor.count;
} else if (concatenatePrimitivesLength > 0) {
primitives.push(concatenatePrimitives[0]);
}
if (indices.length > 0) {
primitives.push(createPrimitive(gltf, attributes, attributeTypes, indices, materialId, mode, uint32Enabled));
var preservePrimitivesLength = preservePrimitives.length;
for (i = 0; i < preservePrimitivesLength; i++) {
primitives.push(preservePrimitives[i]);
}
} else if (concatenatePrimitivesLength > 0){
primitives.push(concatenatePrimitives[0]);
}
var preservePrimitivesLength = preservePrimitives.length;
for (i = 0; i < preservePrimitivesLength; i++) {
primitives.push(preservePrimitives[i]);
}
}
return primitives;
Expand Down Expand Up @@ -254,4 +273,76 @@ function createPrimitive(gltf, attributes, attributeTypes, indices, materialId,
}
}
return primitive;
}
}

function getVertexCount(primitive, accessors) {
var attributes = primitive.attributes;
var vertexCount = 0;
for (var attributeId in attributes) {
if (attributes.hasOwnProperty(attributeId)) {
var accessorId = attributes[attributeId];
vertexCount = Math.max(vertexCount, accessors[accessorId].count);
}
}
return vertexCount;
}

function SortablePrimitive(primitive, accessors) {
this.primitive = primitive;
this.vertexCount = getVertexCount(primitive, accessors);
}

function compareVertexCounts(primitiveA, primitiveB) {
return primitiveA.vertexCount - primitiveB.vertexCount;
}

function subdividePrimitiveGroup(primitiveGroup, accessors, indexMax) {
// Make a pass over all the primitives in the group and estimate what the biggest index result will be,
// assuming that the largest index in each primitive is vertex count - 1.
var indexOffset = 0;
var primitiveCount = primitiveGroup.length;
var needsSubdivision = false;
var i, primitive, vertexCount;
for (i = 0; i < primitiveCount; i++) {
primitive = primitiveGroup[i];
vertexCount = getVertexCount(primitive, accessors);
if (vertexCount - 1 + indexOffset > indexMax) {
needsSubdivision = true;
break;
}
indexOffset += vertexCount;
}

if (!needsSubdivision) {
return [primitiveGroup];
}

// If the estimate will overflow, sort the primitives by indices count.
// "bin" primitives together, creating a new "bin" when the estimated new index will overflow.
var sortablePrimitives = new Array(primitiveCount);
for (i = 0; i < primitiveCount; i++) {
sortablePrimitives[i] = new SortablePrimitive(primitiveGroup[i], accessors);
}
sortablePrimitives.sort(compareVertexCounts);

var currentBucket = [];
var buckets = [currentBucket];
indexOffset = 0;
for(i = 0; i < primitiveCount; i++) {
var sortablePrimitive = sortablePrimitives[i];
primitive = sortablePrimitive.primitive;
vertexCount = sortablePrimitive.vertexCount;
if (vertexCount - 1 + indexOffset > indexMax) {
// If adding this primitive to the bucket will cause overflow,
// put it in a new bucket and restart indexOffset.
currentBucket = [primitive];
buckets.push(currentBucket);
indexOffset = vertexCount;
}
else {
currentBucket.push(primitive);
indexOffset += vertexCount;
}
}
return buckets;
}
167 changes: 167 additions & 0 deletions specs/lib/combinePrimitivesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,4 +527,171 @@ describe('combinePrimitives', function() {
delete gltf.meshes.mesh.primitives[1].extras._pipeline.conflicts;
expect(gltf).toEqual(originalGltf);
});

it('doesn\'t combine primitives in a way that overflows uint16', function() {
var valueCount = 16385;
var smallerValueCount = 100;
var quarterOverflow = new Uint16Array(valueCount);
for (var i = 0; i < valueCount; i++) {
quarterOverflow[i] = i;
}
var quarterOverflowBuffer = new Buffer(quarterOverflow.buffer);
var quarterOverflowAccessor = {
bufferView : 'bufferView',
byteOffset : 0,
byteStride : 0,
componentType : WebGLConstants.UNSIGNED_SHORT,
count : valueCount,
type : 'SCALAR'
};

var gltf = {
accessors : {
dataA : quarterOverflowAccessor,
dataB : quarterOverflowAccessor,
dataC : quarterOverflowAccessor,
dataD : quarterOverflowAccessor,
dataE : quarterOverflowAccessor,
dataF : {
bufferView : 'bufferView',
byteOffset : 0,
byteStride : 0,
componentType : WebGLConstants.UNSIGNED_SHORT,
count : smallerValueCount,
type : 'SCALAR'
}
},
bufferViews : {
bufferView : {
buffer : 'buffer',
byteLength : quarterOverflowBuffer.length,
byteOffset : 0
}
},
buffers : {
buffer : {
byteLength : quarterOverflowBuffer.length,
extras : {
_pipeline : {
source : quarterOverflowBuffer
}
}
}
},
meshes : {
mesh : {
primitives : [{
attributes : {
A : 'dataA'
},
indices : 'dataA',
extras : {
_pipeline : {}
}
}, {
attributes : {
A : 'dataB'
},
indices : 'dataB',
extras : {
_pipeline : {}
}
}, {
attributes : {
A : 'dataC'
},
indices : 'dataC',
extras : {
_pipeline : {}
}
}, {
attributes : {
A : 'dataD'
},
indices : 'dataD',
extras : {
_pipeline : {}
}
}, {
attributes : {
A : 'dataE'
},
indices : 'dataE',
extras : {
_pipeline : {}
}
}, {
attributes : {
A : 'dataF'
},
indices : 'dataF',
extras : {
_pipeline : {}
}
}]
}
}
};
combinePrimitives(gltf);

var newPrimitives = gltf.meshes.mesh.primitives;
expect(newPrimitives.length).toEqual(2);

var primitive0 = newPrimitives[0];
var primitive1 = newPrimitives[1];
var newAccessors = gltf.accessors;

var expectedNewAccessorLength1 = smallerValueCount + valueCount * 3;
var expectedNewAccessorLength2 = valueCount * 2;
expect(newAccessors[primitive0.indices].count).toEqual(expectedNewAccessorLength1);
expect(newAccessors[primitive1.indices].count).toEqual(expectedNewAccessorLength2);

// Check indices. Since indices for each primitive were monotonically increasing,
// expect indices to match loop iteration indices.
var indices = [];
var indicesMatch = true;
readAccessor(gltf, newAccessors[primitive0.indices], indices);
for (i = 0; i < expectedNewAccessorLength1; i++) {
indicesMatch = indicesMatch && indices[i] === i;
}
expect(indicesMatch).toBe(true);

indices = [];
indicesMatch = true;
readAccessor(gltf, newAccessors[primitive1.indices], indices);
for (i = 0; i < expectedNewAccessorLength2; i++) {
indicesMatch = indicesMatch && indices[i] === i;
}
expect(indicesMatch).toBe(true);

// Check attributes. Since attributes for each primitive were monotonically increasing,
// this must be done in pieces.
var attributes = [];
var attributesMatch = true;
readAccessor(gltf, newAccessors[primitive0.attributes.A], attributes);
for (i = 0; i < smallerValueCount; i++) {
attributesMatch = attributesMatch && attributes[i] === i;
}
for (i = 0; i < valueCount; i++) {
attributesMatch = attributesMatch && attributes[i + smallerValueCount] === i;
}
for (i = 0; i < valueCount; i++) {
attributesMatch = attributesMatch && attributes[i + smallerValueCount + valueCount] === i;
}
for (i = 0; i < valueCount; i++) {
attributesMatch = attributesMatch && attributes[i + smallerValueCount + valueCount * 2] === i;
}
expect(attributesMatch).toBe(true);

attributes = [];
attributesMatch = true;
readAccessor(gltf, newAccessors[primitive1.attributes.A], attributes);
for (i = 0; i < valueCount; i++) {
attributesMatch = attributesMatch && attributes[i] === i;
}
for (i = 0; i < valueCount; i++) {
attributesMatch = attributesMatch && attributes[i + valueCount] === i;
}
expect(attributesMatch).toBe(true);
});
});

0 comments on commit ba06022

Please sign in to comment.