Browse Source

BufferGeometryUtils: Add .mergeBufferGeometries().

Don McCurdy 7 năm trước cách đây
mục cha
commit
5c825e3e6f

+ 149 - 0
examples/js/BufferGeometryUtils.js

@@ -184,6 +184,155 @@ THREE.BufferGeometryUtils = {
 
 	},
 
+	/**
+	 * @param  {Array<THREE.BufferGeometry>} geometries
+	 * @return {THREE.BufferGeometry}
+	 */
+	mergeBufferGeometries: function ( geometries ) {
+
+		var isIndexed = geometries[ 0 ].index !== null;
+
+		var attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
+		var morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
+
+		var attributes = {};
+		var morphAttributes = {};
+
+		var mergedGeometry = new THREE.BufferGeometry();
+		var offset = 0;
+
+		for ( var i = 0; i < geometries.length; ++ i ) {
+
+			var geometry = geometries[ i ];
+
+			// ensure that all geometries are indexed, or none
+
+			if ( isIndexed !== ( geometry.index !== null ) ) return null;
+
+			// gather attributes, exit early if they're different
+
+			for ( var name in geometry.attributes ) {
+
+				if ( !attributesUsed.has( name ) ) return null;
+
+				if ( attributes[ name ] === undefined ) attributes[ name ] = [];
+
+				attributes[ name ].push( geometry.attributes[ name ] );
+
+			}
+
+			// gather morph attributes, exit early if they're different
+
+			for ( var name in geometry.morphAttributes ) {
+
+				if ( !morphAttributesUsed.has( name ) ) return null;
+
+				if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
+
+				morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
+
+			}
+
+			// gather .userData
+
+			if ( geometry.userData !== undefined ) {
+
+				mergedGeometry.userData = mergedGeometry.userData || {};
+				mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];
+				mergedGeometry.userData.mergedUserData.push( geometry.userData );
+
+			}
+
+			// create new group for this geometry
+
+			mergedGeometry.addGroup( offset, geometry.attributes.position.count, i );
+			offset += geometry.attributes.position.count;
+
+		}
+
+		// merge indices
+
+		if ( isIndexed ) {
+
+			var indexOffset = 0;
+			var indexList = [];
+
+			for ( var i = 0; i < geometries.length; ++ i ) {
+
+				var index = geometries[ i ].index;
+
+				if ( indexOffset > 0 ) {
+
+					index = index.clone();
+
+					for ( var j = 0; j < index.count; ++ j ) {
+
+						index.setX( j, index.getX( j ) + indexOffset );
+
+					}
+
+				}
+
+				indexList.push( index );
+				indexOffset += geometries[ i ].attributes.position.count;
+
+			}
+
+			var mergedIndex = this.mergeBufferAttributes( indexList );
+
+			if ( !mergedIndex ) return null;
+
+			mergedGeometry.index = mergedIndex;
+
+		}
+
+		// merge attributes
+
+		for ( var name in attributes ) {
+
+			var mergedAttribute = this.mergeBufferAttributes( attributes[ name ] );
+
+			if ( ! mergedAttribute ) return null;
+
+			mergedGeometry.addAttribute( name, mergedAttribute );
+
+		}
+
+		// merge morph attributes
+
+		for ( var name in morphAttributes ) {
+
+			var numMorphTargets = morphAttributes[ name ][ 0 ].length;
+
+			if ( numMorphTargets === 0 ) break;
+
+			mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
+			mergedGeometry.morphAttributes[ name ] = [];
+
+			for ( var i = 0; i < numMorphTargets; ++ i ) {
+
+				var morphAttributesToMerge = [];
+
+				for ( var j = 0; j < morphAttributes[ name ].length; ++ j ) {
+
+					morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
+
+				}
+
+				var mergedMorphAttribute = this.mergeBufferAttributes( morphAttributesToMerge );
+
+				if ( !mergedMorphAttribute ) return null;
+
+				mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
+
+			}
+
+		}
+
+		return mergedGeometry;
+
+	},
+
 	/**
 	 * @param {Array<THREE.BufferAttribute>} attributes
 	 * @return {THREE.BufferAttribute}

+ 23 - 44
examples/js/loaders/GLTFLoader.js

@@ -2154,6 +2154,9 @@ THREE.GLTFLoader = ( function () {
 
 			return scope.loadGeometries( primitives ).then( function ( geometries ) {
 
+				var useSkinning = false;
+				var useMorphTargets = false;
+
 				for ( var i = 0, il = primitives.length; i < il; i ++ ) {
 
 					var primitive = primitives[ i ];
@@ -2173,10 +2176,10 @@ THREE.GLTFLoader = ( function () {
 					}
 
 					// If the material will be modified later on, clone it now.
+					useSkinning = meshDef.isSkinnedMesh === true;
+					useMorphTargets = primitive.targets !== undefined;
 					var useVertexColors = geometry.attributes.color !== undefined;
 					var useFlatShading = geometry.attributes.normal === undefined;
-					var useSkinning = meshDef.isSkinnedMesh === true;
-					var useMorphTargets = primitive.targets !== undefined;
 
 					if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
 
@@ -2325,64 +2328,40 @@ THREE.GLTFLoader = ( function () {
 
 				}
 
-				// TODO(donmccurdy): Does this work if it's not a THREE.Mesh?
+				// Attempt to merge primitives into a single mesh with multiple
+				// buffer geometry groups, returning the group if that fails.
+
 				if ( ! group.children[ 0 ].isMesh ) return group;
 				if ( Object.keys( modesUsed ).length > 1 ) return group;
 				if ( THREE.BufferGeometryUtils === undefined ) return group;
 
-				var mergedGeometry = new THREE.BufferGeometry();
-				var materials = [];
-				var attributes = {};
-				var attributesUsed = Object.keys( group.children[ 0 ].geometry.attributes ).sort().join( '|' );
-				var offset = 0;
-
-				for ( var j = 0, jl = group.children.length; j < jl; ++ j ) {
-
-					var childGeometry = group.children[ j ].geometry;
+				var groupGeometries = [];
+				var groupMaterials = [];
 
-					if ( childGeometry.index ) childGeometry = childGeometry.toNonIndexed();
+				for ( var i = 0; i < group.children.length; ++i ) {
 
-					var childAttributesUsed = Object.keys( childGeometry.attributes ).sort().join( '|' );
+					var geometry = group.children[ i ].geometry;
+					var material = group.children[ i ].material;
 
-					if ( attributesUsed !== childAttributesUsed ) {
-						console.log('bailing');
-						return group;
-					}
-
-					for ( var name in childGeometry.attributes ) {
+					// Can't update spec/gloss uniforms on a multi-material mesh,
+					// so skip this case for now.
+					if ( material.isGLTFSpecularGlossinessMaterial ) return group;
 
-						if ( attributes[ name ] === undefined ) attributes[ name ] = [];
-
-						attributes[ name ].push( childGeometry.attributes[ name ] );
-
-					}
-
-					// TODO(donmccurdy): Keep track of primitive .extras?
-
-					// TODO(donmccurdy): Reuse materials.
-					materials.push( group.children[ j ].material );
-					mergedGeometry.addGroup( offset, childGeometry.attributes.position.count, j );
-					offset += childGeometry.attributes.position.count;
+					groupGeometries.push( geometry );
+					groupMaterials.push( material );
 
 				}
 
-				for ( var name in attributes ) {
-
-					var mergedAttribute = THREE.BufferGeometryUtils.mergeBufferAttributes( attributes[ name ] );
+				var mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries( groupGeometries );
 
-					if ( ! mergedAttribute ) return group;
-
-					mergedGeometry.addAttribute( name, mergedAttribute );
-
-				}
+				if ( !mergedGeometry ) return group;
 
-				var mergedMesh = group.children[ 0 ].isSkinnedMesh
-					? new THREE.SkinnedMesh( mergedGeometry, materials )
-					: new THREE.Mesh( mergedGeometry, materials );
+				var mergedMesh = useSkinning
+					? new THREE.SkinnedMesh( mergedGeometry, groupMaterials )
+					: new THREE.Mesh( mergedGeometry, groupMaterials );
 
 				if ( meshDef.name !== undefined ) mergedMesh.name = meshDef.name;
 				if ( meshDef.extras !== undefined ) mergedMesh.userData = meshDef.extras;
-				// TODO(donmccurdy): Spec gloss... morph targets ... siiighh.
 
 				return mergedMesh;
 

+ 1 - 0
test/three.example.unit.js

@@ -2,6 +2,7 @@
  * @author TristanVALCKE / https://github.com/Itee
  */
 
+import './unit/example/BufferGeometryUtils.tests';
 import './unit/example/exporters/GLTFExporter.tests';
 import './unit/example/loaders/GLTFLoader.tests';
 import './unit/example/objects/Lensflare.tests';

+ 94 - 0
test/unit/example/BufferGeometryUtils.tests.js

@@ -0,0 +1,94 @@
+/**
+ * @author Don McCurdy / https://www.donmccurdy.com
+ */
+/* global QUnit */
+
+import * as BufferGeometryUtils from '../../../examples/js/BufferGeometryUtils';
+
+export default QUnit.module( 'BufferGeometryUtils', () => {
+
+  QUnit.test( 'mergeBufferAttributes - basic', ( assert ) => {
+
+    var array1 = new Float32Array( [ 1, 2, 3, 4 ] );
+    var attr1 = new THREE.BufferAttribute( array1, 2, false );
+
+    var array2 = new Float32Array( [ 5, 6, 7, 8 ] );
+    var attr2 = new THREE.BufferAttribute( array2, 2, false );
+
+    var mergedAttr = THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] );
+
+    assert.smartEqual( Array.from( mergedAttr.array ), [ 1, 2, 3, 4, 5, 6, 7, 8 ], 'merges elements' );
+    assert.equal( mergedAttr.itemSize, 2, 'retains .itemSize' );
+    assert.equal( mergedAttr.normalized, false, 'retains .normalized' );
+
+  } );
+
+  QUnit.test( 'mergeBufferAttributes - invalid', ( assert ) => {
+
+    var array1 = new Float32Array( [ 1, 2, 3, 4 ] );
+    var attr1 = new THREE.BufferAttribute( array1, 2, false );
+
+    var array2 = new Float32Array( [ 5, 6, 7, 8 ] );
+    var attr2 = new THREE.BufferAttribute( array2, 4, false );
+
+    assert.equal( THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] ), null );
+
+    attr2.itemSize = 2;
+    attr2.normalized = true;
+
+    assert.equal( THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] ), null );
+
+    attr2.normalized = false;
+
+    assert.ok( THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] ) );
+
+  } );
+
+  QUnit.test( 'mergeBufferGeometries - basic', ( assert ) => {
+
+    var geometry1 = new THREE.BufferGeometry();
+    geometry1.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3 ] ), 1, false ) );
+
+    var geometry2 = new THREE.BufferGeometry();
+    geometry2.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 4, 5, 6 ] ), 1, false ) );
+
+    var mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries( [ geometry1, geometry2 ] );
+
+    assert.ok( mergedGeometry, 'merge succeeds' );
+    assert.smartEqual( Array.from( mergedGeometry.attributes.position.array ), [ 1, 2, 3, 4, 5, 6 ], 'merges elements' );
+    assert.equal( mergedGeometry.attributes.position.itemSize, 1, 'retains .itemSize' );
+
+  } );
+
+  QUnit.test( 'mergeBufferGeometries - indexed', ( assert ) => {
+
+    var geometry1 = new THREE.BufferGeometry();
+    geometry1.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3 ] ), 1, false ) );
+    geometry1.setIndex( new THREE.BufferAttribute( new Uint16Array( [ 0, 1, 2 ] ), 1, false ) );
+
+    var geometry2 = new THREE.BufferGeometry();
+    geometry2.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 4, 5, 6 ] ), 1, false ) );
+    geometry2.setIndex( new THREE.BufferAttribute( new Uint16Array( [ 0, 1, 2 ] ), 1, false ) );
+
+    var mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries( [ geometry1, geometry2 ] );
+
+    assert.ok( mergedGeometry, 'merge succeeds' );
+    assert.smartEqual( Array.from( mergedGeometry.attributes.position.array ), [ 1, 2, 3, 4, 5, 6 ], 'merges elements' );
+    assert.smartEqual( Array.from( mergedGeometry.index.array ), [ 0, 1, 2, 3, 4, 5 ], 'merges indices' );
+    assert.equal( mergedGeometry.attributes.position.itemSize, 1, 'retains .itemSize' );
+
+  } );
+
+  QUnit.todo( 'mergeBufferGeometries - morph targets', ( assert ) => {
+
+
+
+  } );
+
+  QUnit.todo( 'mergeBufferGeometries - invalid', ( assert ) => {
+
+
+
+  } );
+
+} );