Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
36aa5f4
Add optimization and memory functions
gkjohnson May 23, 2018
3c46c2f
Merge remote-tracking branch 'mrdoob-origin/dev' into optimize-buffer…
gkjohnson May 24, 2018
b9951cc
Rename function to 'mergeVertices', create new BufferAttribute object…
gkjohnson May 24, 2018
908fa73
Add some utility functions to BufferGeometryUtils
gkjohnson May 24, 2018
523c340
Add missing clone and copy functions to InterleavedBufferAttribute
gkjohnson May 24, 2018
7842eb7
Include InterleavedBufferAttribute cloning in a test
gkjohnson May 24, 2018
99438ae
Merge pull request #2 from mrdoob/dev
gkjohnson Jun 8, 2018
73dba53
Merge remote-tracking branch 'origin/dev' into optimize-buffer-geomet…
gkjohnson Jun 8, 2018
814e782
Change precision to tolerance, update memory function name
gkjohnson Jun 22, 2018
516cf70
Linting update
gkjohnson Jun 22, 2018
ee7c54a
Make mergeVertices work if an index array already exists
gkjohnson Jul 25, 2018
64c4e2e
Merge remote-tracking branch 'mrdoob-origin/dev' into optimize-buffer…
gkjohnson Aug 20, 2018
86f1c64
Support merging morphAttributes
gkjohnson Aug 21, 2018
c20dff3
Fix incorrect variable reference. "attribute" -> "oldAttribute"
gkjohnson Oct 30, 2018
d51c345
Merge branch 'dev' into optimize-buffer-geometry-indices
gkjohnson Jan 4, 2019
7f521d4
Move `mergeVertices` to `BufferGeometryUtils`
gkjohnson Jan 9, 2019
b93c7c7
Remove the interleavedBufferAttribute and copy, clone functions
gkjohnson Jan 9, 2019
ae49679
Remove interleavedGeometryAttribute test
gkjohnson Jan 9, 2019
bd989c1
Use mergeVertices in physics volume example
gkjohnson Jan 9, 2019
ae9bc8c
add comments to example
gkjohnson Jan 9, 2019
176163a
Fix tabs
gkjohnson Jan 10, 2019
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
259 changes: 259 additions & 0 deletions examples/js/utils/BufferGeometryUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,265 @@ THREE.BufferGeometryUtils = {

return new THREE.BufferAttribute( array, itemSize, normalized );

},

/**
* @param {Array<THREE.BufferAttribute>} attributes
* @return {Array<THREE.InterleavedBufferAttribute>}
*/
interleaveAttributes: function ( attributes ) {

// Interleaves the provided attributes into an InterleavedBuffer and returns
// a set of InterleavedBufferAttributes for each attribute
var TypedArray;
var arrayLength = 0;
var stride = 0;

// calculate the the length and type of the interleavedBuffer
for ( var i = 0, l = attributes.length; i < l; ++ i ) {

var attribute = attributes[ i ];

if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
if ( TypedArray !== attribute.array.constructor ) {

console.warn( 'AttributeBuffers of different types cannot be interleaved' );
return null;

}

arrayLength += attribute.array.length;
stride += attribute.itemSize;

}

// Create the set of buffer attributes
var interleavedBuffer = new THREE.InterleavedBuffer( new TypedArray( arrayLength ), stride );
var offset = 0;
var res = [];
var getters = [ 'getX', 'getY', 'getZ', 'getW' ];
var setters = [ 'setX', 'setY', 'setZ', 'setW' ];

for ( var j = 0, l = attributes.length; j < l; j ++ ) {

var attribute = attributes[ j ];
var itemSize = attribute.itemSize;
var count = attribute.count;
var iba = new THREE.InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
res.push( iba );

offset += itemSize;

// Move the data for each attribute into the new interleavedBuffer
// at the appropriate offset
for ( var c = 0; c < count; c ++ ) {

for ( var k = 0; k < itemSize; k ++ ) {

iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );

}

}

}

return res;

},

/**
* @param {Array<THREE.BufferGeometry>} geometry
* @return {number}
*/
estimateBytesUsed: function ( geometry ) {

// Return the estimated memory used by this geometry in bytes
// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
// for InterleavedBufferAttributes.
var mem = 0;
for ( var name in geometry.attributes ) {

var attr = geometry.getAttribute( name );
mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;

}

var indices = geometry.getIndex();
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;

}

};
56 changes: 8 additions & 48 deletions examples/webgl_physics_volume.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<script src="../build/three.js"></script>
<script src="js/libs/ammo.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script src="js/utils/BufferGeometryUtils.js"></script>
<script src="js/WebGL.js"></script>
<script src="js/libs/stats.min.js"></script>

Expand Down Expand Up @@ -190,60 +191,19 @@

function processGeometry( bufGeometry ) {

// Obtain a Geometry
var geometry = new THREE.Geometry().fromBufferGeometry( bufGeometry );
// Ony consider the position values when merging the vertices
var posOnlyBufGeometry = new THREE.BufferGeometry();
posOnlyBufGeometry.addAttribute( 'position', bufGeometry.getAttribute( 'position' ) );
posOnlyBufGeometry.setIndex( bufGeometry.getIndex() );

// Merge the vertices so the triangle soup is converted to indexed triangles
geometry.mergeVertices();
// Merge the vertices so the triangle soup is converted to indexed triangles
var indexedBufferGeom = THREE.BufferGeometryUtils.mergeVertices( posOnlyBufGeometry );

// Convert again to BufferGeometry, indexed
var indexedBufferGeom = createIndexedBufferGeometryFromGeometry( geometry );

// Create index arrays mapping the indexed vertices to bufGeometry vertices
// Create index arrays mapping the indexed vertices to bufGeometry vertices
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i think this got converted from tabs to spaces

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Line 199 too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I'll get these when I get home today

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Let me know if there's anything else!

mapIndices( bufGeometry, indexedBufferGeom );

}

function createIndexedBufferGeometryFromGeometry( geometry ) {

var numVertices = geometry.vertices.length;
var numFaces = geometry.faces.length;

var bufferGeom = new THREE.BufferGeometry();
var vertices = new Float32Array( numVertices * 3 );
var indices = new ( numFaces * 3 > 65535 ? Uint32Array : Uint16Array )( numFaces * 3 );

for ( var i = 0; i < numVertices; i ++ ) {

var p = geometry.vertices[ i ];

var i3 = i * 3;

vertices[ i3 ] = p.x;
vertices[ i3 + 1 ] = p.y;
vertices[ i3 + 2 ] = p.z;

}

for ( var i = 0; i < numFaces; i ++ ) {

var f = geometry.faces[ i ];

var i3 = i * 3;

indices[ i3 ] = f.a;
indices[ i3 + 1 ] = f.b;
indices[ i3 + 2 ] = f.c;

}

bufferGeom.setIndex( new THREE.BufferAttribute( indices, 1 ) );
bufferGeom.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );

return bufferGeom;

}

function isEqual( x1, y1, z1, x2, y2, z2 ) {

var delta = 0.000001;
Expand Down