Browse Source

3MFLoader: Add support for multiple textures.

Mugen87 5 years ago
parent
commit
2a39c1b9b4
3 changed files with 434 additions and 458 deletions
  1. 217 229
      examples/js/loaders/3MFLoader.js
  2. 217 229
      examples/jsm/loaders/3MFLoader.js
  3. BIN
      examples/models/3mf/multipletextures.3mf

+ 217 - 229
examples/js/loaders/3MFLoader.js

@@ -46,7 +46,6 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 		var scope = this;
 		var textureLoader = new THREE.TextureLoader( this.manager );
-		var textureMap = {};
 
 		function loadDocument( data ) {
 
@@ -358,10 +357,16 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 				var p3 = triangleNode.getAttribute( 'p3' );
 				var pid = triangleNode.getAttribute( 'pid' );
 
-				triangles.push( parseInt( v1, 10 ), parseInt( v2, 10 ), parseInt( v3, 10 ) );
-
 				var triangleProperty = {};
 
+				triangleProperty[ 'v1' ] = parseInt( v1, 10 );
+				triangleProperty[ 'v2' ] = parseInt( v2, 10 );
+				triangleProperty[ 'v3' ] = parseInt( v3, 10 );
+
+				triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] );
+
+				// optional
+
 				if ( p1 ) {
 
 					triangleProperty[ 'p1' ] = parseInt( p1, 10 );
@@ -535,11 +540,15 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 		function parseResourcesNode( resourcesNode ) {
 
 			var resourcesData = {};
-			var basematerialsNode = resourcesNode.querySelector( 'basematerials' );
 
-			if ( basematerialsNode ) {
+			resourcesData[ 'basematerials' ] = {};
+			var basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' );
+
+			for ( var i = 0; i < basematerialsNodes.length; i ++ ) {
 
-				resourcesData[ 'basematerials' ] = parseBasematerialsNode( basematerialsNode );
+				var basematerialsNode = basematerialsNodes[ i ];
+				var basematerialsData = parseBasematerialsNode( basematerialsNode );
+				resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData;
 
 			}
 
@@ -644,371 +653,350 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 		}
 
-		function buildGroups( geometry, modelData, meshData ) {
+		function buildTexture( texture2dgroup, objects, modelData, textureData ) {
 
-			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
-			var triangleProperties = meshData[ 'triangleProperties' ];
+			var texid = texture2dgroup.texid;
+			var texture2ds = modelData.resources.texture2d;
+			var texture2d = texture2ds[ texid ];
 
-			var start = 0;
-			var count = 0;
-			var currentMaterialIndex = - 1;
+			if ( texture2d ) {
 
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+				var data = textureData[ texture2d.path ];
+				var type = texture2d.contenttype;
 
-				var triangleProperty = triangleProperties[ i ];
-				var pid = triangleProperty.pid;
+				var blob = new Blob( [ data ], { type: type } );
+				var sourceURI = URL.createObjectURL( blob );
 
-				// only proceed if the triangle refers to a basematerials definition
+				var texture = textureLoader.load( sourceURI, function () {
 
-				if ( basematerialsData && ( basematerialsData.id === pid ) ) {
+					URL.revokeObjectURL( sourceURI );
 
-					if ( currentMaterialIndex === - 1 ) currentMaterialIndex = triangleProperty.p1;
+				} );
 
-					if ( currentMaterialIndex === triangleProperty.p1 ) {
+				texture.encoding = THREE.sRGBEncoding;
 
-						count += 3; // primitives per triangle
+				// texture parameters
 
-					} else {
+				switch ( texture2d.tilestyleu ) {
 
-						geometry.addGroup( start, count, currentMaterialIndex );
+					case 'wrap':
+						texture.wrapS = THREE.RepeatWrapping;
+						break;
 
-						start += count;
-						count = 3;
-						currentMaterialIndex = triangleProperty.p1;
+					case 'mirror':
+						texture.wrapS = THREE.MirroredRepeatWrapping;
+						break;
 
-					}
+					case 'none':
+					case 'clamp':
+						texture.wrapS = THREE.ClampToEdgeWrapping;
+						break;
 
-				}
+					default:
+						texture.wrapS = THREE.RepeatWrapping;
 
-			}
-
-			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
-
-		}
-
-		function buildGeometry( modelData, meshData, objectData ) {
-
-			var geometry = new THREE.BufferGeometry();
-			geometry.setIndex( new THREE.BufferAttribute( meshData[ 'triangles' ], 1 ) );
-			geometry.setAttribute( 'position', new THREE.BufferAttribute( meshData[ 'vertices' ], 3 ) );
-
-			//
+				}
 
-			var texture2dgroups = modelData.resources.texture2dgroup;
+				switch ( texture2d.tilestylev ) {
 
-			if ( texture2dgroups ) {
+					case 'wrap':
+						texture.wrapT = THREE.RepeatWrapping;
+						break;
 
-				var textureCoordinates = [];
+					case 'mirror':
+						texture.wrapT = THREE.MirroredRepeatWrapping;
+						break;
 
-				var triangleProperties = meshData[ 'triangleProperties' ];
-				var texture2dgroupObjectLevel;
+					case 'none':
+					case 'clamp':
+						texture.wrapT = THREE.ClampToEdgeWrapping;
+						break;
 
-				// check reference to texture coordinates on object level
+					default:
+						texture.wrapT = THREE.RepeatWrapping;
 
-				var texid;
-				var pid = objectData.pid;
+				}
 
-				if ( pid && texture2dgroups[ pid ] ) texture2dgroupObjectLevel = texture2dgroups[ pid ];
+				switch ( texture2d.filter ) {
 
-				// process all triangles
+					case 'auto':
+						texture.magFilter = THREE.LinearFilter;
+						texture.minFilter = THREE.LinearMipmapLinearFilter;
+						break;
 
-				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+					case 'linear':
+						texture.magFilter = THREE.LinearFilter;
+						texture.minFilter = THREE.LinearFilter;
+						break;
 
-					var texture2dgroup = texture2dgroupObjectLevel;
-					var triangleProperty = triangleProperties[ i ];
-					pid = triangleProperty.pid;
+					case 'nearest':
+						texture.magFilter = THREE.NearestFilter;
+						texture.minFilter = THREE.NearestFilter;
+						break;
 
-					// overwrite existing resource reference if necessary
+					default:
+						texture.magFilter = THREE.LinearFilter;
+						texture.minFilter = THREE.LinearMipmapLinearFilter;
 
-					if ( pid && texture2dgroups[ pid ] ) texture2dgroup = texture2dgroups[ pid ];
+				}
 
-					if ( texture2dgroup ) {
+				return texture;
 
-						texid = texture2dgroup.texid; // the loader only supports a single texture for a single geometry right now (and not per face)
-						var uvs = texture2dgroup.uvs;
+			} else {
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
+				return null;
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] );
+			}
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] );
+		}
 
-					}
+		function buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData ) {
 
-				}
+			var objectPindex = objectData.pindex;
 
-				if ( textureCoordinates.length > 0 ) {
+			var materialMap = {};
 
-					// uvs are defined on face level so the same vertex can have multiple uv coordinates
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-					geometry = geometry.toNonIndexed();
-					geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( textureCoordinates, 2 ) );
-					geometry.__texid = texid; // save the relationship between texture coordinates and texture
+				var triangleProperty = triangleProperties[ i ];
+				var pindex = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectPindex;
 
-					return geometry;
+				if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = [];
 
-				}
+				materialMap[ pindex ].push( triangleProperty );
 
 			}
 
-			return geometry;
-
-		}
-
-		function buildTexture( geometry, modelData, textureData ) {
-
-			var texid = geometry.__texid;
-
-			if ( texid !== undefined ) {
-
-				delete geometry.__texid;
+			//
 
-				if ( textureMap[ texid ] !== undefined ) {
+			var keys = Object.keys( materialMap );
+			var meshes = [];
 
-					return textureMap[ texid ];
+			for ( var i = 0, l = keys.length; i < l; i ++ ) {
 
-				} else {
+				var materialIndex = keys[ i ];
+				var trianglePropertiesProps = materialMap[ materialIndex ];
+				var basematerialData = basematerials.basematerials[ materialIndex ];
+				var material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
 
-					var texture2ds = modelData.resources.texture2d;
-					var texture2d = texture2ds[ texid ];
+				//
 
-					if ( texture2d ) {
+				var geometry = new THREE.BufferGeometry();
 
-						var data = textureData[ texture2d.path ];
-						var type = texture2d.contenttype;
+				var positionData = [];
 
-						var blob = new Blob( [ data ], { type: type } );
-						var sourceURI = URL.createObjectURL( blob );
+				var vertices = meshData.vertices;
 
-						var texture = textureLoader.load( sourceURI, function () {
+				for ( var j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) {
 
-							URL.revokeObjectURL( sourceURI );
+					var triangleProperty = trianglePropertiesProps[ j ];
 
-						} );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
 
-						texture.encoding = THREE.sRGBEncoding;
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
 
-						// texture parameters
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
 
-						switch ( texture2d.tilestyleu ) {
 
-							case 'wrap':
-								texture.wrapS = THREE.RepeatWrapping;
-								break;
+				}
 
-							case 'mirror':
-								texture.wrapS = THREE.MirroredRepeatWrapping;
-								break;
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
 
-							case 'none':
-							case 'clamp':
-								texture.wrapS = THREE.ClampToEdgeWrapping;
-								break;
+				//
 
-							default:
-								texture.wrapS = THREE.RepeatWrapping;
+				var mesh = new THREE.Mesh( geometry, material );
+				meshes.push( mesh );
 
-						}
+			}
 
-						switch ( texture2d.tilestylev ) {
+			return meshes;
 
-							case 'wrap':
-								texture.wrapT = THREE.RepeatWrapping;
-								break;
+		}
 
-							case 'mirror':
-								texture.wrapT = THREE.MirroredRepeatWrapping;
-								break;
+		function buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) {
 
-							case 'none':
-							case 'clamp':
-								texture.wrapT = THREE.ClampToEdgeWrapping;
-								break;
+			// geometry
 
-							default:
-								texture.wrapT = THREE.RepeatWrapping;
+			var geometry = new THREE.BufferGeometry();
 
-						}
+			var positionData = [];
+			var uvData = [];
 
-						switch ( texture2d.filter ) {
+			var vertices = meshData.vertices;
+			var uvs = texture2dgroup.uvs;
 
-							case 'auto':
-								texture.magFilter = THREE.LinearFilter;
-								texture.minFilter = THREE.LinearMipmapLinearFilter;
-								break;
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-							case 'linear':
-								texture.magFilter = THREE.LinearFilter;
-								texture.minFilter = THREE.LinearFilter;
-								break;
+				var triangleProperty = triangleProperties[ i ];
 
-							case 'nearest':
-								texture.magFilter = THREE.NearestFilter;
-								texture.minFilter = THREE.NearestFilter;
-								break;
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
 
-							default:
-								texture.magFilter = THREE.LinearFilter;
-								texture.minFilter = THREE.LinearMipmapLinearFilter;
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
 
-						}
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
 
-						textureMap[ texid ] = texture;
+				//
 
-						return texture;
+				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
 
-					}
+				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] );
 
-				}
+				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] );
 
 			}
 
-			return null;
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
+			geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvData, 2 ) );
 
-		}
+			// material
 
-		function buildMesh( meshData, objects, modelData, textureData, objectData ) {
+			var texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture );
 
-			var geometry = buildGeometry( modelData, meshData, objectData );
-			var texture = buildTexture( geometry, modelData, textureData );
+			var material = new THREE.MeshPhongMaterial( { map: texture, flatShading: true } );
 
-			// groups
+			// mesh
 
-			buildGroups( geometry, modelData, meshData );
+			var mesh = new THREE.Mesh( geometry, material );
 
-			// material
+			return mesh;
 
-			var material = null;
-
-			// add material if an object-level definition is present
+		}
 
-			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
+		function buildDefaultMesh( meshData ) {
 
-			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
+			var geometry = new THREE.BufferGeometry();
+			geometry.setIndex( new THREE.BufferAttribute( meshData[ 'triangles' ], 1 ) );
+			geometry.setAttribute( 'position', new THREE.BufferAttribute( meshData[ 'vertices' ], 3 ) );
 
-				var materialIndex = objectData.pindex;
-				var basematerialData = basematerialsData.basematerials[ materialIndex ];
+			var material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
 
-				material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
+			var mesh = new THREE.Mesh( geometry, material );
 
-			}
+			return mesh;
 
-			// add/overwrite material if definitions on triangles are present
+		}
 
-			if ( geometry.groups.length > 0 ) {
+		function buildMeshes( resourceMap, modelData, meshData, textureData, objectData ) {
 
-				var groups = geometry.groups;
-				material = [];
+			var keys = Object.keys( resourceMap );
+			var meshes = [];
 
-				for ( var i = 0, l = groups.length; i < l; i ++ ) {
+			for ( var i = 0; i < keys.length; i ++ ) {
 
-					var group = groups[ i ];
-					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
-					material.push( getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ) );
+				var resourceId = keys[ i ];
+				var triangleProperties = resourceMap[ resourceId ];
+				var resourceType = getResourceType( resourceId, modelData );
 
-				}
+				switch ( resourceType ) {
 
-			}
+					case 'material':
+						var basematerials = modelData.resources.basematerials[ resourceId ];
+						var newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData );
 
-			// default material
+						for ( var i = 0, l = newMeshes.length; i < l; i ++ ) {
 
-			if ( material === null ) {
+							meshes.push( newMeshes[ i ] );
 
-				if ( texture === null ) {
+						}
+						break;
 
-					material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+					case 'texture':
+						var texture2dgroup = modelData.resources.texture2dgroup[ resourceId ];
+						meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData ) );
+						break;
 
-				} else {
+					case 'default':
+						meshes.push( buildDefaultMesh( meshData ) );
+						break;
 
-					material = new THREE.MeshPhongMaterial( { map: texture, flatShading: true } );
+					default:
+						console.error( 'THREE.3MFLoader: Unsupported resource type.' );
 
 				}
 
 			}
 
-			return new THREE.Mesh( geometry, material );
+			return meshes;
 
 		}
 
-		function mergeGroups( geometry ) {
-
-			// sort by material index
-
-			var groups = geometry.groups.sort( function ( a, b ) {
+		function getResourceType( pid, modelData ) {
 
-				if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
-
-				return a.start - b.start;
-
-			} );
+			if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) {
 
-			// reorganize index buffer
+				return 'texture';
 
-			var index = geometry.index;
+			} else if ( modelData.resources.basematerials[ pid ] !== undefined ) {
 
-			var itemSize = index.itemSize;
-			var srcArray = index.array;
+				return 'material';
 
-			var targetOffset = 0;
+			} else if ( pid === 'default' ) {
 
-			var targetArray = new srcArray.constructor( srcArray.length );
+				return 'default';
 
-			for ( var i = 0; i < groups.length; i ++ ) {
+			} else {
 
-				var group = groups[ i ];
+				return undefined;
 
-				var groupLength = group.count * itemSize;
-				var groupStart = group.start * itemSize;
+			}
 
-				var sub = srcArray.subarray( groupStart, groupStart + groupLength );
+		}
 
-				targetArray.set( sub, targetOffset );
+		function analyzeObject( modelData, meshData, objectData ) {
 
-				targetOffset += groupLength;
+			var resourceMap = {};
 
-			}
+			var triangleProperties = meshData[ 'triangleProperties' ];
 
-			srcArray.set( targetArray );
+			var objectPid = objectData.pid;
 
-			// update groups
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-			var start = 0;
+				var triangleProperty = triangleProperties[ i ];
+				var pid = ( triangleProperty.pid !== undefined ) ? triangleProperty.pid : objectPid;
 
-			for ( i = 0; i < groups.length; i ++ ) {
+				if ( pid === undefined ) pid = 'default';
 
-				group = groups[ i ];
+				if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = [];
 
-				group.start = start;
-				start += group.count;
+				resourceMap[ pid ].push( triangleProperty );
 
 			}
 
-			// merge groups
-
-			var lastGroup = groups[ 0 ];
-
-			geometry.groups = [ lastGroup ];
-
-			for ( i = 1; i < groups.length; i ++ ) {
+			return resourceMap;
 
-				group = groups[ i ];
+		}
 
-				if ( lastGroup.materialIndex === group.materialIndex ) {
+		function buildGroup( meshData, objects, modelData, textureData, objectData ) {
 
-					lastGroup.count += group.count;
+			var group = new THREE.Group();
 
-				} else {
+			var resourceMap = analyzeObject( modelData, meshData, objectData );
+			var meshes = buildMeshes( resourceMap, modelData, meshData, textureData, objectData );
 
-					lastGroup = group;
-					geometry.groups.push( lastGroup );
+			for ( var i = 0, l = meshes.length; i < l; i ++ ) {
 
-				}
+				group.add( meshes[ i ] );
 
 			}
 
+			return group;
+
 		}
 
 		function applyExtensions( extensions, meshData, modelXml ) {
@@ -1134,7 +1122,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 				applyExtensions( extensions, meshData, modelXml );
 
-				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildMesh );
+				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup );
 
 			} else {
 

+ 217 - 229
examples/jsm/loaders/3MFLoader.js

@@ -67,7 +67,6 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		var scope = this;
 		var textureLoader = new TextureLoader( this.manager );
-		var textureMap = {};
 
 		function loadDocument( data ) {
 
@@ -379,10 +378,16 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 				var p3 = triangleNode.getAttribute( 'p3' );
 				var pid = triangleNode.getAttribute( 'pid' );
 
-				triangles.push( parseInt( v1, 10 ), parseInt( v2, 10 ), parseInt( v3, 10 ) );
-
 				var triangleProperty = {};
 
+				triangleProperty[ 'v1' ] = parseInt( v1, 10 );
+				triangleProperty[ 'v2' ] = parseInt( v2, 10 );
+				triangleProperty[ 'v3' ] = parseInt( v3, 10 );
+
+				triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] );
+
+				// optional
+
 				if ( p1 ) {
 
 					triangleProperty[ 'p1' ] = parseInt( p1, 10 );
@@ -556,11 +561,15 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 		function parseResourcesNode( resourcesNode ) {
 
 			var resourcesData = {};
-			var basematerialsNode = resourcesNode.querySelector( 'basematerials' );
 
-			if ( basematerialsNode ) {
+			resourcesData[ 'basematerials' ] = {};
+			var basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' );
+
+			for ( var i = 0; i < basematerialsNodes.length; i ++ ) {
 
-				resourcesData[ 'basematerials' ] = parseBasematerialsNode( basematerialsNode );
+				var basematerialsNode = basematerialsNodes[ i ];
+				var basematerialsData = parseBasematerialsNode( basematerialsNode );
+				resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData;
 
 			}
 
@@ -665,371 +674,350 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 		}
 
-		function buildGroups( geometry, modelData, meshData ) {
+		function buildTexture( texture2dgroup, objects, modelData, textureData ) {
 
-			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
-			var triangleProperties = meshData[ 'triangleProperties' ];
+			var texid = texture2dgroup.texid;
+			var texture2ds = modelData.resources.texture2d;
+			var texture2d = texture2ds[ texid ];
 
-			var start = 0;
-			var count = 0;
-			var currentMaterialIndex = - 1;
+			if ( texture2d ) {
 
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+				var data = textureData[ texture2d.path ];
+				var type = texture2d.contenttype;
 
-				var triangleProperty = triangleProperties[ i ];
-				var pid = triangleProperty.pid;
+				var blob = new Blob( [ data ], { type: type } );
+				var sourceURI = URL.createObjectURL( blob );
 
-				// only proceed if the triangle refers to a basematerials definition
+				var texture = textureLoader.load( sourceURI, function () {
 
-				if ( basematerialsData && ( basematerialsData.id === pid ) ) {
+					URL.revokeObjectURL( sourceURI );
 
-					if ( currentMaterialIndex === - 1 ) currentMaterialIndex = triangleProperty.p1;
+				} );
 
-					if ( currentMaterialIndex === triangleProperty.p1 ) {
+				texture.encoding = sRGBEncoding;
 
-						count += 3; // primitives per triangle
+				// texture parameters
 
-					} else {
+				switch ( texture2d.tilestyleu ) {
 
-						geometry.addGroup( start, count, currentMaterialIndex );
+					case 'wrap':
+						texture.wrapS = RepeatWrapping;
+						break;
 
-						start += count;
-						count = 3;
-						currentMaterialIndex = triangleProperty.p1;
+					case 'mirror':
+						texture.wrapS = MirroredRepeatWrapping;
+						break;
 
-					}
+					case 'none':
+					case 'clamp':
+						texture.wrapS = ClampToEdgeWrapping;
+						break;
 
-				}
+					default:
+						texture.wrapS = RepeatWrapping;
 
-			}
-
-			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
-
-		}
-
-		function buildGeometry( modelData, meshData, objectData ) {
-
-			var geometry = new BufferGeometry();
-			geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) );
-			geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) );
-
-			//
+				}
 
-			var texture2dgroups = modelData.resources.texture2dgroup;
+				switch ( texture2d.tilestylev ) {
 
-			if ( texture2dgroups ) {
+					case 'wrap':
+						texture.wrapT = RepeatWrapping;
+						break;
 
-				var textureCoordinates = [];
+					case 'mirror':
+						texture.wrapT = MirroredRepeatWrapping;
+						break;
 
-				var triangleProperties = meshData[ 'triangleProperties' ];
-				var texture2dgroupObjectLevel;
+					case 'none':
+					case 'clamp':
+						texture.wrapT = ClampToEdgeWrapping;
+						break;
 
-				// check reference to texture coordinates on object level
+					default:
+						texture.wrapT = RepeatWrapping;
 
-				var texid;
-				var pid = objectData.pid;
+				}
 
-				if ( pid && texture2dgroups[ pid ] ) texture2dgroupObjectLevel = texture2dgroups[ pid ];
+				switch ( texture2d.filter ) {
 
-				// process all triangles
+					case 'auto':
+						texture.magFilter = LinearFilter;
+						texture.minFilter = LinearMipmapLinearFilter;
+						break;
 
-				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+					case 'linear':
+						texture.magFilter = LinearFilter;
+						texture.minFilter = LinearFilter;
+						break;
 
-					var texture2dgroup = texture2dgroupObjectLevel;
-					var triangleProperty = triangleProperties[ i ];
-					pid = triangleProperty.pid;
+					case 'nearest':
+						texture.magFilter = NearestFilter;
+						texture.minFilter = NearestFilter;
+						break;
 
-					// overwrite existing resource reference if necessary
+					default:
+						texture.magFilter = LinearFilter;
+						texture.minFilter = LinearMipmapLinearFilter;
 
-					if ( pid && texture2dgroups[ pid ] ) texture2dgroup = texture2dgroups[ pid ];
+				}
 
-					if ( texture2dgroup ) {
+				return texture;
 
-						texid = texture2dgroup.texid; // the loader only supports a single texture for a single geometry right now (and not per face)
-						var uvs = texture2dgroup.uvs;
+			} else {
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
+				return null;
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] );
+			}
 
-						textureCoordinates.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] );
-						textureCoordinates.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] );
+		}
 
-					}
+		function buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData ) {
 
-				}
+			var objectPindex = objectData.pindex;
 
-				if ( textureCoordinates.length > 0 ) {
+			var materialMap = {};
 
-					// uvs are defined on face level so the same vertex can have multiple uv coordinates
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-					geometry = geometry.toNonIndexed();
-					geometry.setAttribute( 'uv', new Float32BufferAttribute( textureCoordinates, 2 ) );
-					geometry.__texid = texid; // save the relationship between texture coordinates and texture
+				var triangleProperty = triangleProperties[ i ];
+				var pindex = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectPindex;
 
-					return geometry;
+				if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = [];
 
-				}
+				materialMap[ pindex ].push( triangleProperty );
 
 			}
 
-			return geometry;
-
-		}
-
-		function buildTexture( geometry, modelData, textureData ) {
-
-			var texid = geometry.__texid;
-
-			if ( texid !== undefined ) {
-
-				delete geometry.__texid;
+			//
 
-				if ( textureMap[ texid ] !== undefined ) {
+			var keys = Object.keys( materialMap );
+			var meshes = [];
 
-					return textureMap[ texid ];
+			for ( var i = 0, l = keys.length; i < l; i ++ ) {
 
-				} else {
+				var materialIndex = keys[ i ];
+				var trianglePropertiesProps = materialMap[ materialIndex ];
+				var basematerialData = basematerials.basematerials[ materialIndex ];
+				var material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
 
-					var texture2ds = modelData.resources.texture2d;
-					var texture2d = texture2ds[ texid ];
+				//
 
-					if ( texture2d ) {
+				var geometry = new BufferGeometry();
 
-						var data = textureData[ texture2d.path ];
-						var type = texture2d.contenttype;
+				var positionData = [];
 
-						var blob = new Blob( [ data ], { type: type } );
-						var sourceURI = URL.createObjectURL( blob );
+				var vertices = meshData.vertices;
 
-						var texture = textureLoader.load( sourceURI, function () {
+				for ( var j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) {
 
-							URL.revokeObjectURL( sourceURI );
+					var triangleProperty = trianglePropertiesProps[ j ];
 
-						} );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
 
-						texture.encoding = sRGBEncoding;
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
 
-						// texture parameters
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
+					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
 
-						switch ( texture2d.tilestyleu ) {
 
-							case 'wrap':
-								texture.wrapS = RepeatWrapping;
-								break;
+				}
 
-							case 'mirror':
-								texture.wrapS = MirroredRepeatWrapping;
-								break;
+				geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) );
 
-							case 'none':
-							case 'clamp':
-								texture.wrapS = ClampToEdgeWrapping;
-								break;
+				//
 
-							default:
-								texture.wrapS = RepeatWrapping;
+				var mesh = new Mesh( geometry, material );
+				meshes.push( mesh );
 
-						}
+			}
 
-						switch ( texture2d.tilestylev ) {
+			return meshes;
 
-							case 'wrap':
-								texture.wrapT = RepeatWrapping;
-								break;
+		}
 
-							case 'mirror':
-								texture.wrapT = MirroredRepeatWrapping;
-								break;
+		function buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) {
 
-							case 'none':
-							case 'clamp':
-								texture.wrapT = ClampToEdgeWrapping;
-								break;
+			// geometry
 
-							default:
-								texture.wrapT = RepeatWrapping;
+			var geometry = new BufferGeometry();
 
-						}
+			var positionData = [];
+			var uvData = [];
 
-						switch ( texture2d.filter ) {
+			var vertices = meshData.vertices;
+			var uvs = texture2dgroup.uvs;
 
-							case 'auto':
-								texture.magFilter = LinearFilter;
-								texture.minFilter = LinearMipmapLinearFilter;
-								break;
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-							case 'linear':
-								texture.magFilter = LinearFilter;
-								texture.minFilter = LinearFilter;
-								break;
+				var triangleProperty = triangleProperties[ i ];
 
-							case 'nearest':
-								texture.magFilter = NearestFilter;
-								texture.minFilter = NearestFilter;
-								break;
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
 
-							default:
-								texture.magFilter = LinearFilter;
-								texture.minFilter = LinearMipmapLinearFilter;
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
 
-						}
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
+				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
 
-						textureMap[ texid ] = texture;
+				//
 
-						return texture;
+				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
 
-					}
+				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] );
 
-				}
+				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] );
+				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] );
 
 			}
 
-			return null;
+			geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) );
+			geometry.setAttribute( 'uv', new Float32BufferAttribute( uvData, 2 ) );
 
-		}
+			// material
 
-		function buildMesh( meshData, objects, modelData, textureData, objectData ) {
+			var texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture );
 
-			var geometry = buildGeometry( modelData, meshData, objectData );
-			var texture = buildTexture( geometry, modelData, textureData );
+			var material = new MeshPhongMaterial( { map: texture, flatShading: true } );
 
-			// groups
+			// mesh
 
-			buildGroups( geometry, modelData, meshData );
+			var mesh = new Mesh( geometry, material );
 
-			// material
+			return mesh;
 
-			var material = null;
-
-			// add material if an object-level definition is present
+		}
 
-			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
+		function buildDefaultMesh( meshData ) {
 
-			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
+			var geometry = new BufferGeometry();
+			geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) );
+			geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) );
 
-				var materialIndex = objectData.pindex;
-				var basematerialData = basematerialsData.basematerials[ materialIndex ];
+			var material = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
 
-				material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
+			var mesh = new Mesh( geometry, material );
 
-			}
+			return mesh;
 
-			// add/overwrite material if definitions on triangles are present
+		}
 
-			if ( geometry.groups.length > 0 ) {
+		function buildMeshes( resourceMap, modelData, meshData, textureData, objectData ) {
 
-				var groups = geometry.groups;
-				material = [];
+			var keys = Object.keys( resourceMap );
+			var meshes = [];
 
-				for ( var i = 0, l = groups.length; i < l; i ++ ) {
+			for ( var i = 0; i < keys.length; i ++ ) {
 
-					var group = groups[ i ];
-					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
-					material.push( getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ) );
+				var resourceId = keys[ i ];
+				var triangleProperties = resourceMap[ resourceId ];
+				var resourceType = getResourceType( resourceId, modelData );
 
-				}
+				switch ( resourceType ) {
 
-			}
+					case 'material':
+						var basematerials = modelData.resources.basematerials[ resourceId ];
+						var newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData );
 
-			// default material
+						for ( var i = 0, l = newMeshes.length; i < l; i ++ ) {
 
-			if ( material === null ) {
+							meshes.push( newMeshes[ i ] );
 
-				if ( texture === null ) {
+						}
+						break;
 
-					material = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+					case 'texture':
+						var texture2dgroup = modelData.resources.texture2dgroup[ resourceId ];
+						meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData ) );
+						break;
 
-				} else {
+					case 'default':
+						meshes.push( buildDefaultMesh( meshData ) );
+						break;
 
-					material = new MeshPhongMaterial( { map: texture, flatShading: true } );
+					default:
+						console.error( 'THREE.3MFLoader: Unsupported resource type.' );
 
 				}
 
 			}
 
-			return new Mesh( geometry, material );
+			return meshes;
 
 		}
 
-		function mergeGroups( geometry ) {
-
-			// sort by material index
-
-			var groups = geometry.groups.sort( function ( a, b ) {
+		function getResourceType( pid, modelData ) {
 
-				if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
-
-				return a.start - b.start;
-
-			} );
+			if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) {
 
-			// reorganize index buffer
+				return 'texture';
 
-			var index = geometry.index;
+			} else if ( modelData.resources.basematerials[ pid ] !== undefined ) {
 
-			var itemSize = index.itemSize;
-			var srcArray = index.array;
+				return 'material';
 
-			var targetOffset = 0;
+			} else if ( pid === 'default' ) {
 
-			var targetArray = new srcArray.constructor( srcArray.length );
+				return 'default';
 
-			for ( var i = 0; i < groups.length; i ++ ) {
+			} else {
 
-				var group = groups[ i ];
+				return undefined;
 
-				var groupLength = group.count * itemSize;
-				var groupStart = group.start * itemSize;
+			}
 
-				var sub = srcArray.subarray( groupStart, groupStart + groupLength );
+		}
 
-				targetArray.set( sub, targetOffset );
+		function analyzeObject( modelData, meshData, objectData ) {
 
-				targetOffset += groupLength;
+			var resourceMap = {};
 
-			}
+			var triangleProperties = meshData[ 'triangleProperties' ];
 
-			srcArray.set( targetArray );
+			var objectPid = objectData.pid;
 
-			// update groups
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-			var start = 0;
+				var triangleProperty = triangleProperties[ i ];
+				var pid = ( triangleProperty.pid !== undefined ) ? triangleProperty.pid : objectPid;
 
-			for ( i = 0; i < groups.length; i ++ ) {
+				if ( pid === undefined ) pid = 'default';
 
-				group = groups[ i ];
+				if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = [];
 
-				group.start = start;
-				start += group.count;
+				resourceMap[ pid ].push( triangleProperty );
 
 			}
 
-			// merge groups
-
-			var lastGroup = groups[ 0 ];
-
-			geometry.groups = [ lastGroup ];
-
-			for ( i = 1; i < groups.length; i ++ ) {
+			return resourceMap;
 
-				group = groups[ i ];
+		}
 
-				if ( lastGroup.materialIndex === group.materialIndex ) {
+		function buildGroup( meshData, objects, modelData, textureData, objectData ) {
 
-					lastGroup.count += group.count;
+			var group = new Group();
 
-				} else {
+			var resourceMap = analyzeObject( modelData, meshData, objectData );
+			var meshes = buildMeshes( resourceMap, modelData, meshData, textureData, objectData );
 
-					lastGroup = group;
-					geometry.groups.push( lastGroup );
+			for ( var i = 0, l = meshes.length; i < l; i ++ ) {
 
-				}
+				group.add( meshes[ i ] );
 
 			}
 
+			return group;
+
 		}
 
 		function applyExtensions( extensions, meshData, modelXml ) {
@@ -1155,7 +1143,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 				applyExtensions( extensions, meshData, modelXml );
 
-				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildMesh );
+				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup );
 
 			} else {
 

BIN
examples/models/3mf/multipletextures.3mf