Przeglądaj źródła

Merge pull request #13241 from donmccurdy/feat-gltfloader-buffergeometry-groups

BufferGeometryUtils: Add mergeBufferGeometries() helper.
Mr.doob 7 lat temu
rodzic
commit
72338e1b13

+ 3 - 2
docs/api/core/BufferGeometry.html

@@ -134,8 +134,9 @@
 
 			Each group is an object of the form:
 			<code>{ start: Integer, count: Integer, materialIndex: Integer }</code>
-			where start specifies the index of the first vertex in this draw call, count specifies
-			how many vertices are included, and materialIndex specifies the material array index to use.<br /><br />
+			where start specifies the first element in this draw call – the first vertex for non-indexed geometry,
+			otherwise the first triangle index. Count specifies how many vertices (or indices) are included, and
+			materialIndex specifies the material array index to use.<br /><br />
 
 			Use [page:.addGroup] to add groups, rather than modifying this array directly.
 		</div>

+ 51 - 0
docs/examples/BufferGeometryUtils.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <base href="../" />
+    <script src="list.js"></script>
+    <script src="page.js"></script>
+    <link type="text/css" rel="stylesheet" href="page.css" />
+  </head>
+  <body>
+    <h1>[name]</h1>
+
+    <div class="desc">
+    A class containing utility functions for [page:BufferGeometry BufferGeometry] instances.<br /><br />
+    </div>
+
+
+    <h2>Methods</h2>
+
+    <h3>[method:null computeTangents]( [param:BufferGeometry geometry] )</h3>
+    <div>
+    geometry -- A [page:BufferGeometry BufferGeometry] instance, which must have index, position, normal, and uv attributes.<br /><br />
+
+    Calculates and adds tangent attribute to a geometry.<br /><br />
+
+    </div>
+
+    <h3>[method:BufferGeometry mergeBufferGeometries]( [param:Array geometries] )</h3>
+    <div>
+    geometries -- Array of [page:BufferGeometry BufferGeometry] instances.<br /><br />
+
+    Merges a set of geometries into a single instance. All geometries must have compatible attributes.
+    If merge does not succeed, the method returns null.<br /><br />
+
+    </div>
+
+    <h3>[method:BufferAttribute mergeBufferAttributes]( [param:Array attributes] )</h3>
+    <div>
+    attributes -- Array of [page:BufferAttribute BufferAttribute] instances.<br /><br />
+
+    Merges a set of attributes into a single instance. All attributes must have compatible properties
+    and types, and [page:InterleavedBufferAttribute InterleavedBufferAttributes] are not supported. If merge does not succeed, the method
+    returns null.
+
+    </div>
+
+    <h2>Source</h2>
+
+    [link:https://github.com/mrdoob/three.js/blob/master/examples/js/BufferGeometryUtils.js examples/js/BufferGeometryUtils.js]
+  </body>
+</html>

+ 1 - 0
docs/list.js

@@ -386,6 +386,7 @@ var list = {
 		},
 
 		"Utils": {
+			"BufferGeometryUtils": "examples/BufferGeometryUtils",
 			"SceneUtils": "examples/utils/SceneUtils"
 		}
 

+ 188 - 0
examples/js/BufferGeometryUtils.js

@@ -182,6 +182,194 @@ 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();
+
+		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 );
+
+			}
+
+		}
+
+		// 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}
+	 */
+	mergeBufferAttributes: function ( attributes ) {
+
+		var TypedArray;
+		var itemSize;
+		var normalized;
+		var arrayLength = 0;
+
+		for ( var i = 0; i < attributes.length; ++ i ) {
+
+			var attribute = attributes[ i ];
+
+			if ( attribute.isInterleavedBufferAttribute ) return null;
+
+			if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
+			if ( TypedArray !== attribute.array.constructor ) return null;
+
+			if ( itemSize === undefined ) itemSize = attribute.itemSize;
+			if ( itemSize !== attribute.itemSize ) return null;
+
+			if ( normalized === undefined ) normalized = attribute.normalized;
+			if ( normalized !== attribute.normalized ) return null;
+
+			arrayLength += attribute.array.length;
+
+		}
+
+		var array = new TypedArray( arrayLength );
+		var offset = 0;
+
+		for ( var j = 0; j < attributes.length; ++ j ) {
+
+			array.set( attributes[ j ].array, offset );
+
+			offset += attributes[ j ].array.length;
+
+		}
+
+		return new THREE.BufferAttribute( array, itemSize, normalized );
+
 	}
 
 };

+ 10 - 1
src/core/BufferGeometry.js

@@ -794,7 +794,16 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 		}
 
-		if ( offset === undefined ) offset = 0;
+		if ( offset === undefined ) {
+
+			offset = 0;
+
+			console.warn(
+				'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '
+				+ 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
+			);
+
+		}
 
 		var attributes = this.attributes;
 

+ 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';

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

@@ -0,0 +1,129 @@
+/**
+ * @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.notOk( THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] ) );
+
+    attr2.itemSize = 2;
+    attr2.normalized = true;
+
+    assert.notOk( THREE.BufferGeometryUtils.mergeBufferAttributes( [ attr1, attr2 ] ) );
+
+    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, 2, 1, 0 ] ), 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, 2, 1, 0, 3, 4, 5 ], 'merges indices' );
+    assert.equal( mergedGeometry.attributes.position.itemSize, 1, 'retains .itemSize' );
+
+  } );
+
+  QUnit.test( 'mergeBufferGeometries - morph targets', ( assert ) => {
+
+    var geometry1 = new THREE.BufferGeometry();
+    geometry1.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3 ] ), 1, false ) );
+    geometry1.morphAttributes.position = [
+      new THREE.BufferAttribute( new Float32Array( [ 10, 20, 30 ] ), 1, false ),
+      new THREE.BufferAttribute( new Float32Array( [ 100, 200, 300 ] ), 1, false )
+    ];
+
+    var geometry2 = new THREE.BufferGeometry();
+    geometry2.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [ 4, 5, 6 ] ), 1, false ) );
+    geometry2.morphAttributes.position = [
+      new THREE.BufferAttribute( new Float32Array( [ 40, 50, 60 ] ), 1, false ),
+      new THREE.BufferAttribute( new Float32Array( [ 400, 500, 600 ] ), 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.morphAttributes.position[ 0 ].array ), [ 10, 20, 30, 40, 50, 60 ], 'merges morph targets' );
+    assert.smartEqual( Array.from( mergedGeometry.morphAttributes.position[ 1 ].array ), [ 100, 200, 300, 400, 500, 600 ], 'merges morph targets' );
+    assert.equal( mergedGeometry.attributes.position.itemSize, 1, 'retains .itemSize' );
+
+  } );
+
+  QUnit.test( 'mergeBufferGeometries - invalid', ( 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 ) );
+
+    assert.notOk( THREE.BufferGeometryUtils.mergeBufferGeometries( [ geometry1, geometry2 ] ) );
+
+    geometry2.setIndex( new THREE.BufferAttribute( new Uint16Array( [ 0, 1, 2 ] ), 1, false ) );
+
+    assert.ok( THREE.BufferGeometryUtils.mergeBufferGeometries( [ geometry1, geometry2 ] ) );
+
+    geometry2.addAttribute( 'foo', new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3 ] ), 1, false ) );
+
+    assert.notOk( THREE.BufferGeometryUtils.mergeBufferGeometries( [ geometry1, geometry2 ] ) );
+
+  } );
+
+} );