Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BufferGeometryUtils: Add mergeBufferGeometries() helper. #13241

Merged
merged 12 commits into from
Feb 27, 2018
Merged
5 changes: 3 additions & 2 deletions docs/api/core/BufferGeometry.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ <h3>[property:Array groups]</h3>

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>
Expand Down
51 changes: 51 additions & 0 deletions docs/examples/BufferGeometryUtils.html
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions docs/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ var list = {
},

"Utils": {
"BufferGeometryUtils": "examples/BufferGeometryUtils",
"SceneUtils": "examples/utils/SceneUtils"
}

Expand Down
188 changes: 188 additions & 0 deletions examples/js/BufferGeometryUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Collaborator

Choose a reason for hiding this comment

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

BufferGeometry doesn't have .userData property. Perhaps only GLTFLoader can set from glTF primitive.extras. (Any other loaders/modules can also set?) So, is this merging for GLTFLoader special?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, good point. Yes apparently it is glTF-specific. Probably deserves a comment, or maybe we can revisit whether GLTFLoader should do this..

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it's a good idea to pollute BufferGeometry with Three.js unofficial property. For example, it can be lost in serialization. I prefer officially adding .userData property to BufferGeometry.

@mrdoob What do you think of adding .userData property to BufferGeometry?


}

}

// 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 );

}

};
11 changes: 10 additions & 1 deletion src/core/BufferGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions test/three.example.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading