|
@@ -469,6 +469,177 @@ THREE.BufferGeometryUtils = {
|
|
|
mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
|
|
|
return mem;
|
|
|
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param {THREE.BufferGeometry} geometry
|
|
|
+ * @param {number} tolerance
|
|
|
+ * @return {THREE.BufferGeometry>}
|
|
|
+ */
|
|
|
+ mergeVertices: function ( geometry, tolerance = 1e-4 ) {
|
|
|
+
|
|
|
+ tolerance = Math.max( tolerance, Number.EPSILON );
|
|
|
+
|
|
|
+ // Generate an index buffer if the geometry doesn't have one, or optimize it
|
|
|
+ // if it's already available.
|
|
|
+ var hashToIndex = {};
|
|
|
+ var indices = geometry.getIndex();
|
|
|
+ var positions = geometry.getAttribute( 'position' );
|
|
|
+ var vertexCount = indices ? indices.count : positions.count;
|
|
|
+
|
|
|
+ // next value for triangle indices
|
|
|
+ var nextIndex = 0;
|
|
|
+
|
|
|
+ // attributes and new attribute arrays
|
|
|
+ var attributeNames = Object.keys( geometry.attributes );
|
|
|
+ var attrArrays = {};
|
|
|
+ var morphAttrsArrays = {};
|
|
|
+ var newIndices = [];
|
|
|
+ var getters = [ 'getX', 'getY', 'getZ', 'getW' ];
|
|
|
+
|
|
|
+ // initialize the arrays
|
|
|
+ for ( var name of attributeNames ) {
|
|
|
+
|
|
|
+ attrArrays[ name ] = [];
|
|
|
+
|
|
|
+ var morphAttr = geometry.morphAttributes[ name ];
|
|
|
+ if ( morphAttr ) {
|
|
|
+
|
|
|
+ morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // convert the error tolerance to an amount of decimal places to truncate to
|
|
|
+ var decimalShift = Math.log10( 1 / tolerance );
|
|
|
+ var shiftMultiplier = Math.pow( 10, decimalShift );
|
|
|
+ for ( var i = 0; i < vertexCount; i ++ ) {
|
|
|
+
|
|
|
+ var index = indices ? indices.getX( i ) : i;
|
|
|
+
|
|
|
+ // Generate a hash for the vertex attributes at the current index 'i'
|
|
|
+ var hash = '';
|
|
|
+ for ( var j = 0, l = attributeNames.length; j < l; j ++ ) {
|
|
|
+
|
|
|
+ var name = attributeNames[ j ];
|
|
|
+ var attribute = geometry.getAttribute( name );
|
|
|
+ var itemSize = attribute.itemSize;
|
|
|
+
|
|
|
+ for ( var k = 0; k < itemSize; k ++ ) {
|
|
|
+
|
|
|
+ // double tilde truncates the decimal value
|
|
|
+ hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add another reference to the vertex if it's already
|
|
|
+ // used by another index
|
|
|
+ if ( hash in hashToIndex ) {
|
|
|
+
|
|
|
+ newIndices.push( hashToIndex[ hash ] );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // copy data to the new index in the attribute arrays
|
|
|
+ for ( var j = 0, l = attributeNames.length; j < l; j ++ ) {
|
|
|
+
|
|
|
+ var name = attributeNames[ j ];
|
|
|
+ var attribute = geometry.getAttribute( name );
|
|
|
+ var morphAttr = geometry.morphAttributes[ name ];
|
|
|
+ var itemSize = attribute.itemSize;
|
|
|
+ var newarray = attrArrays[ name ];
|
|
|
+ var newMorphArrays = morphAttrsArrays[ name ];
|
|
|
+
|
|
|
+ for ( var k = 0; k < itemSize; k ++ ) {
|
|
|
+
|
|
|
+ var getterFunc = getters[ k ];
|
|
|
+ newarray.push( attribute[ getterFunc ]( index ) );
|
|
|
+
|
|
|
+ if ( morphAttr ) {
|
|
|
+
|
|
|
+ for ( var m = 0, ml = morphAttr.length; m < ml; m ++ ) {
|
|
|
+
|
|
|
+ newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ hashToIndex[ hash ] = nextIndex;
|
|
|
+ newIndices.push( nextIndex );
|
|
|
+ nextIndex ++;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate typed arrays from new attribute arrays and update
|
|
|
+ // the attributeBuffers
|
|
|
+ const result = geometry.clone();
|
|
|
+ for ( var i = 0, l = attributeNames.length; i < l; i ++ ) {
|
|
|
+
|
|
|
+ var name = attributeNames[ i ];
|
|
|
+ var oldAttribute = geometry.getAttribute( name );
|
|
|
+ var attribute;
|
|
|
+
|
|
|
+ var buffer = new oldAttribute.array.constructor( attrArrays[ name ] );
|
|
|
+ if ( oldAttribute.isInterleavedBufferAttribute ) {
|
|
|
+
|
|
|
+ attribute = new THREE.BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.itemSize );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ attribute = geometry.getAttribute( name ).clone();
|
|
|
+ attribute.setArray( buffer );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ result.addAttribute( name, attribute );
|
|
|
+
|
|
|
+ // Update the attribute arrays
|
|
|
+ if ( name in morphAttrsArrays ) {
|
|
|
+
|
|
|
+ for ( var j = 0; j < morphAttrsArrays[ name ].length; j ++ ) {
|
|
|
+
|
|
|
+ var morphAttribute = geometry.morphAttributes[ name ][ j ].clone();
|
|
|
+ morphAttribute.setArray( new morphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] ) );
|
|
|
+ result.morphAttributes[ name ][ j ] = morphAttribute;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate an index buffer typed array
|
|
|
+ var cons = Uint8Array;
|
|
|
+ if ( newIndices.length >= Math.pow( 2, 8 ) ) cons = Uint16Array;
|
|
|
+ if ( newIndices.length >= Math.pow( 2, 16 ) ) cons = Uint32Array;
|
|
|
+
|
|
|
+ var newIndexBuffer = new cons( newIndices );
|
|
|
+ var newIndices = null;
|
|
|
+ if ( indices === null ) {
|
|
|
+
|
|
|
+ newIndices = new THREE.BufferAttribute( newIndexBuffer, 1 );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ newIndices = geometry.getIndex().clone();
|
|
|
+ newIndices.setArray( newIndexBuffer );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ result.setIndex( newIndices );
|
|
|
+
|
|
|
+ return result;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
};
|