Browse Source

3MFLoader: Add basic support for textures.

Mugen87 5 years ago
parent
commit
8626f95308

+ 343 - 41
examples/js/loaders/3MFLoader.js

@@ -10,8 +10,10 @@
  * - Object Resources (Meshes and Components)
  * - Object Resources (Meshes and Components)
  * - Material Resources (Base Materials)
  * - Material Resources (Base Materials)
  *
  *
- * 3MF Materials and Properties Extension (e.g. textures) are not yet supported.
+ * 3MF Materials and Properties Extension are only partially supported.
  *
  *
+ * - Texture 2D
+ * - Texture 2D Groups
  */
  */
 
 
 THREE.ThreeMFLoader = function ( manager ) {
 THREE.ThreeMFLoader = function ( manager ) {
@@ -43,6 +45,8 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 	parse: function ( data ) {
 	parse: function ( data ) {
 
 
 		var scope = this;
 		var scope = this;
+		var textureLoader = new THREE.TextureLoader( this.manager );
+		var textureMap = {};
 
 
 		function loadDocument( data ) {
 		function loadDocument( data ) {
 
 
@@ -50,12 +54,14 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 			var file = null;
 			var file = null;
 
 
 			var relsName;
 			var relsName;
+			var modelRelsName;
 			var modelPartNames = [];
 			var modelPartNames = [];
 			var printTicketPartNames = [];
 			var printTicketPartNames = [];
 			var texturesPartNames = [];
 			var texturesPartNames = [];
 			var otherPartNames = [];
 			var otherPartNames = [];
 
 
 			var rels;
 			var rels;
+			var modelRels;
 			var modelParts = {};
 			var modelParts = {};
 			var printTicketParts = {};
 			var printTicketParts = {};
 			var texturesParts = {};
 			var texturesParts = {};
@@ -82,6 +88,10 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 					relsName = file;
 					relsName = file;
 
 
+				} else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) {
+
+					modelRelsName = file;
+
 				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
 				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
 
 
 					modelPartNames.push( file );
 					modelPartNames.push( file );
@@ -90,7 +100,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 					printTicketPartNames.push( file );
 					printTicketPartNames.push( file );
 
 
-				} else if ( file.match( /^3D\/Textures\/.*/ ) ) {
+				} else if ( file.match( /^3D\/Texture\/.*/ ) ) {
 
 
 					texturesPartNames.push( file );
 					texturesPartNames.push( file );
 
 
@@ -102,10 +112,24 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
+			//
+
 			var relsView = new Uint8Array( zip.file( relsName ).asArrayBuffer() );
 			var relsView = new Uint8Array( zip.file( relsName ).asArrayBuffer() );
 			var relsFileText = THREE.LoaderUtils.decodeText( relsView );
 			var relsFileText = THREE.LoaderUtils.decodeText( relsView );
 			rels = parseRelsXml( relsFileText );
 			rels = parseRelsXml( relsFileText );
 
 
+			//
+
+			if ( modelRelsName ) {
+
+				var relsView = new Uint8Array( zip.file( modelRelsName ).asArrayBuffer() );
+				var relsFileText = THREE.LoaderUtils.decodeText( relsView );
+				modelRels = parseRelsXml( relsFileText );
+
+			}
+
+			//
+
 			for ( var i = 0; i < modelPartNames.length; i ++ ) {
 			for ( var i = 0; i < modelPartNames.length; i ++ ) {
 
 
 				var modelPart = modelPartNames[ i ];
 				var modelPart = modelPartNames[ i ];
@@ -147,15 +171,18 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
+			//
+
 			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
 			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
 
 
 				var texturesPartName = texturesPartNames[ i ];
 				var texturesPartName = texturesPartNames[ i ];
-				texturesParts[ texturesPartName ] = zip.file( texturesPartName ).asBinary();
+				texturesParts[ texturesPartName ] = zip.file( texturesPartName ).asArrayBuffer();
 
 
 			}
 			}
 
 
 			return {
 			return {
 				rels: rels,
 				rels: rels,
+				modelRels: modelRels,
 				model: modelParts,
 				model: modelParts,
 				printTicket: printTicketParts,
 				printTicket: printTicketParts,
 				texture: texturesParts,
 				texture: texturesParts,
@@ -232,6 +259,49 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 		}
 		}
 
 
+		function parseTexture2DNode( texture2DNode ) {
+
+			var texture2dData = {
+				id: texture2DNode.getAttribute( 'id' ), // required
+				path: texture2DNode.getAttribute( 'path' ), // required
+				contenttype: texture2DNode.getAttribute( 'contenttype' ), // required
+				tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ),
+				tilestylev: texture2DNode.getAttribute( 'tilestylev' ),
+				filter: texture2DNode.getAttribute( 'filter' ),
+			};
+
+			return texture2dData;
+
+		}
+
+		function parseTextures2DGroupNodes( texture2DGroupNode ) {
+
+			var texture2DGroupData = {
+				id: texture2DGroupNode.getAttribute( 'id' ), // required
+				texid: texture2DGroupNode.getAttribute( 'texid' ), // required
+				displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' )
+			};
+
+			var tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' );
+
+			var uvs = [];
+
+			for ( var i = 0; i < tex2coordNodes.length; i ++ ) {
+
+				var tex2coordNode = tex2coordNodes[ i ];
+				var u = tex2coordNode.getAttribute( 'u' );
+				var v = tex2coordNode.getAttribute( 'v' );
+
+				uvs.push( parseFloat( u ), parseFloat( v ) );
+
+			}
+
+			texture2DGroupData[ 'uvs' ] = new Float32Array( uvs );
+
+			return texture2DGroupData;
+
+		}
+
 		function parseBasematerialNode( basematerialNode ) {
 		function parseBasematerialNode( basematerialNode ) {
 
 
 			var basematerialData = {};
 			var basematerialData = {};
@@ -261,13 +331,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
-			meshData[ 'vertices' ] = new Float32Array( vertices.length );
-
-			for ( var i = 0; i < vertices.length; i ++ ) {
-
-				meshData[ 'vertices' ][ i ] = vertices[ i ];
-
-			}
+			meshData[ 'vertices' ] = new Float32Array( vertices );
 
 
 			var triangleProperties = [];
 			var triangleProperties = [];
 			var triangles = [];
 			var triangles = [];
@@ -321,13 +385,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 			}
 			}
 
 
 			meshData[ 'triangleProperties' ] = triangleProperties;
 			meshData[ 'triangleProperties' ] = triangleProperties;
-			meshData[ 'triangles' ] = new Uint32Array( triangles.length );
-
-			for ( var i = 0; i < triangles.length; i ++ ) {
-
-				meshData[ 'triangles' ][ i ] = triangles[ i ];
-
-			}
+			meshData[ 'triangles' ] = new Uint32Array( triangles );
 
 
 			return meshData;
 			return meshData;
 
 
@@ -475,6 +533,34 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
+			//
+
+			resourcesData[ 'texture2d' ] = {};
+			var textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' );
+
+			for ( var i = 0; i < textures2DNodes.length; i ++ ) {
+
+				var textures2DNode = textures2DNodes[ i ];
+				var texture2DData = parseTexture2DNode( textures2DNode );
+				resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData;
+
+			}
+
+			//
+
+			resourcesData[ 'texture2dgroup' ] = {};
+			var textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' );
+
+			for ( var i = 0; i < textures2DGroupNodes.length; i ++ ) {
+
+				var textures2DGroupNode = textures2DGroupNodes[ i ];
+				var textures2DGroupData = parseTextures2DGroupNodes( textures2DGroupNode );
+				resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData;
+
+			}
+
+			//
+
 			resourcesData[ 'object' ] = {};
 			resourcesData[ 'object' ] = {};
 			var objectNodes = resourcesNode.querySelectorAll( 'object' );
 			var objectNodes = resourcesNode.querySelectorAll( 'object' );
 
 
@@ -548,15 +634,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 		}
 		}
 
 
-		function buildMesh( meshData, objects, modelData, objectData ) {
-
-			// geometry
-
-			var geometry = new THREE.BufferGeometry();
-			geometry.setIndex( new THREE.BufferAttribute( meshData[ 'triangles' ], 1 ) );
-			geometry.setAttribute( 'position', new THREE.BufferAttribute( meshData[ 'vertices' ], 3 ) );
-
-			// groups
+		function buildGroups( geometry, modelData, meshData ) {
 
 
 			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
 			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
 			var triangleProperties = meshData[ 'triangleProperties' ];
 			var triangleProperties = meshData[ 'triangleProperties' ];
@@ -578,7 +656,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 					if ( currentMaterialIndex === triangleProperty.p1 ) {
 					if ( currentMaterialIndex === triangleProperty.p1 ) {
 
 
-						count += 3; // primitves per triangle
+						count += 3; // primitives per triangle
 
 
 					} else {
 					} else {
 
 
@@ -596,20 +674,215 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
 			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
 
 
-			geometry.computeBoundingSphere();
+		}
+
+		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;
+
+			if ( texture2dgroups ) {
+
+				var textureCoordinates = [];
+
+				var triangleProperties = meshData[ 'triangleProperties' ];
+				var texture2dgroupObjectLevel;
+
+				// check reference to texture coordinates on object level
+
+				var texid;
+				var pid = objectData.pid;
+
+				if ( pid && texture2dgroups[ pid ] ) texture2dgroupObjectLevel = texture2dgroups[ pid ];
+
+				// process all triangles
+
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+
+					var texture2dgroup = texture2dgroupObjectLevel;
+					var triangleProperty = triangleProperties[ i ];
+					pid = triangleProperty.pid;
+
+					// overwrite existing resource reference if necessary
+
+					if ( pid && texture2dgroups[ pid ] ) texture2dgroup = texture2dgroups[ pid ];
+
+					if ( texture2dgroup ) {
+
+						texid = texture2dgroup.texid; // the loader only supports a single texture for a single geometry right now (and not per face)
+						var uvs = texture2dgroup.uvs;
+
+						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
+						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
+
+						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 ] );
+
+					}
+
+				}
+
+				if ( textureCoordinates.length > 0 ) {
+
+					// uvs are defined on face level so the same vertex can have multiple uv coordinates
+
+					geometry = geometry.toNonIndexed();
+					geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( textureCoordinates, 2 ) );
+					geometry.__texid = texid; // save the relationship between texture coordinates and texture
+
+					return geometry;
+
+				}
+
+			}
+
+			return geometry;
+
+		}
+
+		function buildTexture( geometry, modelData, textureData ) {
+
+			var texid = geometry.__texid;
+
+			if ( texid !== undefined ) {
+
+				delete geometry.__texid;
+
+				if ( textureMap[ texid ] !== undefined ) {
+
+					return textureMap[ texid ];
+
+				} else {
+
+					var texture2ds = modelData.resources.texture2d;
+					var texture2d = texture2ds[ texid ];
+
+					if ( texture2d ) {
+
+						var data = textureData[ texture2d.path ];
+						var type = texture2d.contenttype;
+
+						var blob = new Blob( [ data ], { type: type } );
+						var sourceURI = URL.createObjectURL( blob );
+
+						var texture = textureLoader.load( sourceURI, function () {
+
+							URL.revokeObjectURL( sourceURI );
+
+						} );
+
+						texture.encoding = THREE.sRGBEncoding;
+
+						// texture parameters
+
+						switch ( texture2d.tilestyleu ) {
+
+							case 'wrap':
+								texture.wrapS = THREE.RepeatWrapping;
+								break;
+
+							case 'mirror':
+								texture.wrapS = THREE.MirroredRepeatWrapping;
+								break;
+
+							case 'none':
+							case 'clamp':
+								texture.wrapS = THREE.ClampToEdgeWrapping;
+								break;
+
+							default:
+								texture.wrapS = THREE.RepeatWrapping;
+
+						}
+
+						switch ( texture2d.tilestylev ) {
+
+							case 'wrap':
+								texture.wrapT = THREE.RepeatWrapping;
+								break;
+
+							case 'mirror':
+								texture.wrapT = THREE.MirroredRepeatWrapping;
+								break;
+
+							case 'none':
+							case 'clamp':
+								texture.wrapT = THREE.ClampToEdgeWrapping;
+								break;
+
+							default:
+								texture.wrapT = THREE.RepeatWrapping;
+
+						}
+
+						switch ( texture2d.filter ) {
+
+							case 'auto':
+								texture.magFilter = THREE.LinearFilter;
+								texture.minFilter = THREE.LinearMipmapLinearFilter;
+								break;
+
+							case 'linear':
+								texture.magFilter = THREE.LinearFilter;
+								texture.minFilter = THREE.LinearFilter;
+								break;
+
+							case 'nearest':
+								texture.magFilter = THREE.NearestFilter;
+								texture.minFilter = THREE.NearestFilter;
+								break;
+
+							default:
+								texture.magFilter = THREE.LinearFilter;
+								texture.minFilter = THREE.LinearMipmapLinearFilter;
+
+						}
+
+						textureMap[ texid ] = texture;
+
+						return texture;
+
+					}
+
+				}
+
+			}
+
+			return null;
+
+		}
+
+		function buildMesh( meshData, objects, modelData, textureData, objectData ) {
+
+			var geometry = buildGeometry( modelData, meshData, objectData );
+			var texture = buildTexture( geometry, modelData, textureData );
+
+			// groups
+
+			buildGroups( geometry, modelData, meshData );
 
 
 			// material
 			// material
 
 
-			var material;
+			var material = null;
 
 
 			// add material if an object-level definition is present
 			// add material if an object-level definition is present
 
 
+			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
+
 			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
 			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
 
 
 				var materialIndex = objectData.pindex;
 				var materialIndex = objectData.pindex;
 				var basematerialData = basematerialsData.basematerials[ materialIndex ];
 				var basematerialData = basematerialsData.basematerials[ materialIndex ];
 
 
-				material = getBuild( basematerialData, objects, modelData, objectData, buildBasematerial );
+				material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
 
 
 			}
 			}
 
 
@@ -624,8 +897,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 					var group = groups[ i ];
 					var group = groups[ i ];
 					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
 					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
-
-					material.push( getBuild( basematerialData, objects, modelData, objectData, buildBasematerial ) );
+					material.push( getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ) );
 
 
 				}
 				}
 
 
@@ -633,7 +905,19 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			// default material
 			// default material
 
 
-			if ( material === undefined ) material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+			if ( material === null ) {
+
+				if ( texture === null ) {
+
+					material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+
+				} else {
+
+					material = new THREE.MeshPhongMaterial( { map: texture, flatShading: true } );
+
+				}
+
+			}
 
 
 			return new THREE.Mesh( geometry, material );
 			return new THREE.Mesh( geometry, material );
 
 
@@ -755,11 +1039,11 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 		}
 		}
 
 
-		function getBuild( data, objects, modelData, objectData, builder ) {
+		function getBuild( data, objects, modelData, textureData, objectData, builder ) {
 
 
 			if ( data.build !== undefined ) return data.build;
 			if ( data.build !== undefined ) return data.build;
 
 
-			data.build = builder( data, objects, modelData, objectData );
+			data.build = builder( data, objects, modelData, textureData, objectData );
 
 
 			return data.build;
 			return data.build;
 
 
@@ -791,7 +1075,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 		}
 		}
 
 
-		function buildComposite( compositeData, objects, modelData ) {
+		function buildComposite( compositeData, objects, modelData, textureData ) {
 
 
 			var composite = new THREE.Group();
 			var composite = new THREE.Group();
 
 
@@ -802,7 +1086,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 				if ( build === undefined ) {
 				if ( build === undefined ) {
 
 
-					buildObject( component.objectId, objects, modelData );
+					buildObject( component.objectId, objects, modelData, textureData );
 					build = objects[ component.objectId ];
 					build = objects[ component.objectId ];
 
 
 				}
 				}
@@ -827,7 +1111,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 		}
 		}
 
 
-		function buildObject( objectId, objects, modelData ) {
+		function buildObject( objectId, objects, modelData, textureData ) {
 
 
 			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
 			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
 
 
@@ -840,13 +1124,13 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 				applyExtensions( extensions, meshData, modelXml );
 				applyExtensions( extensions, meshData, modelXml );
 
 
-				objects[ objectData.id ] = getBuild( meshData, objects, modelData, objectData, buildMesh );
+				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildMesh );
 
 
 			} else {
 			} else {
 
 
 				var compositeData = objectData[ 'components' ];
 				var compositeData = objectData[ 'components' ];
 
 
-				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, objectData, buildComposite );
+				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite );
 
 
 			}
 			}
 
 
@@ -855,8 +1139,26 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 		function buildObjects( data3mf ) {
 		function buildObjects( data3mf ) {
 
 
 			var modelsData = data3mf.model;
 			var modelsData = data3mf.model;
+			var modelRels = data3mf.modelRels;
 			var objects = {};
 			var objects = {};
 			var modelsKeys = Object.keys( modelsData );
 			var modelsKeys = Object.keys( modelsData );
+			var textureData = {};
+
+			// evaluate model relationship to a texture
+
+			if ( modelRels ) {
+
+				var textureKey = modelRels.target.substring( 1 );
+
+				if ( data3mf.texture[ textureKey ] ) {
+
+					textureData[ modelRels.target ] = data3mf.texture[ textureKey ];
+
+				}
+
+			}
+
+			// start build
 
 
 			for ( var i = 0; i < modelsKeys.length; i ++ ) {
 			for ( var i = 0; i < modelsKeys.length; i ++ ) {
 
 
@@ -869,7 +1171,7 @@ THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 					var objectId = objectIds[ j ];
 					var objectId = objectIds[ j ];
 
 
-					buildObject( objectId, objects, modelData );
+					buildObject( objectId, objects, modelData, textureData );
 
 
 				}
 				}
 
 

+ 353 - 42
examples/jsm/loaders/3MFLoader.js

@@ -10,20 +10,31 @@
  * - Object Resources (Meshes and Components)
  * - Object Resources (Meshes and Components)
  * - Material Resources (Base Materials)
  * - Material Resources (Base Materials)
  *
  *
- * 3MF Materials and Properties Extension (e.g. textures) are not yet supported.
+ * 3MF Materials and Properties Extension are only partially supported.
  *
  *
+ * - Texture 2D
+ * - Texture 2D Groups
  */
  */
 
 
 import {
 import {
 	BufferAttribute,
 	BufferAttribute,
 	BufferGeometry,
 	BufferGeometry,
+	ClampToEdgeWrapping,
 	FileLoader,
 	FileLoader,
+	Float32BufferAttribute,
 	Group,
 	Group,
+	LinearFilter,
+	LinearMipmapLinearFilter,
 	Loader,
 	Loader,
 	LoaderUtils,
 	LoaderUtils,
 	Matrix4,
 	Matrix4,
 	Mesh,
 	Mesh,
-	MeshPhongMaterial
+	MeshPhongMaterial,
+	MirroredRepeatWrapping,
+	NearestFilter,
+	RepeatWrapping,
+	TextureLoader,
+	sRGBEncoding
 } from "../../../build/three.module.js";
 } from "../../../build/three.module.js";
 
 
 var ThreeMFLoader = function ( manager ) {
 var ThreeMFLoader = function ( manager ) {
@@ -55,6 +66,8 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 	parse: function ( data ) {
 	parse: function ( data ) {
 
 
 		var scope = this;
 		var scope = this;
+		var textureLoader = new TextureLoader( this.manager );
+		var textureMap = {};
 
 
 		function loadDocument( data ) {
 		function loadDocument( data ) {
 
 
@@ -62,12 +75,14 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 			var file = null;
 			var file = null;
 
 
 			var relsName;
 			var relsName;
+			var modelRelsName;
 			var modelPartNames = [];
 			var modelPartNames = [];
 			var printTicketPartNames = [];
 			var printTicketPartNames = [];
 			var texturesPartNames = [];
 			var texturesPartNames = [];
 			var otherPartNames = [];
 			var otherPartNames = [];
 
 
 			var rels;
 			var rels;
+			var modelRels;
 			var modelParts = {};
 			var modelParts = {};
 			var printTicketParts = {};
 			var printTicketParts = {};
 			var texturesParts = {};
 			var texturesParts = {};
@@ -94,6 +109,10 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 					relsName = file;
 					relsName = file;
 
 
+				} else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) {
+
+					modelRelsName = file;
+
 				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
 				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
 
 
 					modelPartNames.push( file );
 					modelPartNames.push( file );
@@ -102,7 +121,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 					printTicketPartNames.push( file );
 					printTicketPartNames.push( file );
 
 
-				} else if ( file.match( /^3D\/Textures\/.*/ ) ) {
+				} else if ( file.match( /^3D\/Texture\/.*/ ) ) {
 
 
 					texturesPartNames.push( file );
 					texturesPartNames.push( file );
 
 
@@ -114,10 +133,24 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
+			//
+
 			var relsView = new Uint8Array( zip.file( relsName ).asArrayBuffer() );
 			var relsView = new Uint8Array( zip.file( relsName ).asArrayBuffer() );
 			var relsFileText = LoaderUtils.decodeText( relsView );
 			var relsFileText = LoaderUtils.decodeText( relsView );
 			rels = parseRelsXml( relsFileText );
 			rels = parseRelsXml( relsFileText );
 
 
+			//
+
+			if ( modelRelsName ) {
+
+				var relsView = new Uint8Array( zip.file( modelRelsName ).asArrayBuffer() );
+				var relsFileText = LoaderUtils.decodeText( relsView );
+				modelRels = parseRelsXml( relsFileText );
+
+			}
+
+			//
+
 			for ( var i = 0; i < modelPartNames.length; i ++ ) {
 			for ( var i = 0; i < modelPartNames.length; i ++ ) {
 
 
 				var modelPart = modelPartNames[ i ];
 				var modelPart = modelPartNames[ i ];
@@ -159,15 +192,18 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
+			//
+
 			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
 			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
 
 
 				var texturesPartName = texturesPartNames[ i ];
 				var texturesPartName = texturesPartNames[ i ];
-				texturesParts[ texturesPartName ] = zip.file( texturesPartName ).asBinary();
+				texturesParts[ texturesPartName ] = zip.file( texturesPartName ).asArrayBuffer();
 
 
 			}
 			}
 
 
 			return {
 			return {
 				rels: rels,
 				rels: rels,
+				modelRels: modelRels,
 				model: modelParts,
 				model: modelParts,
 				printTicket: printTicketParts,
 				printTicket: printTicketParts,
 				texture: texturesParts,
 				texture: texturesParts,
@@ -244,6 +280,49 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 		}
 		}
 
 
+		function parseTexture2DNode( texture2DNode ) {
+
+			var texture2dData = {
+				id: texture2DNode.getAttribute( 'id' ), // required
+				path: texture2DNode.getAttribute( 'path' ), // required
+				contenttype: texture2DNode.getAttribute( 'contenttype' ), // required
+				tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ),
+				tilestylev: texture2DNode.getAttribute( 'tilestylev' ),
+				filter: texture2DNode.getAttribute( 'filter' ),
+			};
+
+			return texture2dData;
+
+		}
+
+		function parseTextures2DGroupNodes( texture2DGroupNode ) {
+
+			var texture2DGroupData = {
+				id: texture2DGroupNode.getAttribute( 'id' ), // required
+				texid: texture2DGroupNode.getAttribute( 'texid' ), // required
+				displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' )
+			};
+
+			var tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' );
+
+			var uvs = [];
+
+			for ( var i = 0; i < tex2coordNodes.length; i ++ ) {
+
+				var tex2coordNode = tex2coordNodes[ i ];
+				var u = tex2coordNode.getAttribute( 'u' );
+				var v = tex2coordNode.getAttribute( 'v' );
+
+				uvs.push( parseFloat( u ), parseFloat( v ) );
+
+			}
+
+			texture2DGroupData[ 'uvs' ] = new Float32Array( uvs );
+
+			return texture2DGroupData;
+
+		}
+
 		function parseBasematerialNode( basematerialNode ) {
 		function parseBasematerialNode( basematerialNode ) {
 
 
 			var basematerialData = {};
 			var basematerialData = {};
@@ -273,13 +352,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
-			meshData[ 'vertices' ] = new Float32Array( vertices.length );
-
-			for ( var i = 0; i < vertices.length; i ++ ) {
-
-				meshData[ 'vertices' ][ i ] = vertices[ i ];
-
-			}
+			meshData[ 'vertices' ] = new Float32Array( vertices );
 
 
 			var triangleProperties = [];
 			var triangleProperties = [];
 			var triangles = [];
 			var triangles = [];
@@ -333,13 +406,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 			}
 			}
 
 
 			meshData[ 'triangleProperties' ] = triangleProperties;
 			meshData[ 'triangleProperties' ] = triangleProperties;
-			meshData[ 'triangles' ] = new Uint32Array( triangles.length );
-
-			for ( var i = 0; i < triangles.length; i ++ ) {
-
-				meshData[ 'triangles' ][ i ] = triangles[ i ];
-
-			}
+			meshData[ 'triangles' ] = new Uint32Array( triangles );
 
 
 			return meshData;
 			return meshData;
 
 
@@ -487,6 +554,34 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
+			//
+
+			resourcesData[ 'texture2d' ] = {};
+			var textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' );
+
+			for ( var i = 0; i < textures2DNodes.length; i ++ ) {
+
+				var textures2DNode = textures2DNodes[ i ];
+				var texture2DData = parseTexture2DNode( textures2DNode );
+				resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData;
+
+			}
+
+			//
+
+			resourcesData[ 'texture2dgroup' ] = {};
+			var textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' );
+
+			for ( var i = 0; i < textures2DGroupNodes.length; i ++ ) {
+
+				var textures2DGroupNode = textures2DGroupNodes[ i ];
+				var textures2DGroupData = parseTextures2DGroupNodes( textures2DGroupNode );
+				resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData;
+
+			}
+
+			//
+
 			resourcesData[ 'object' ] = {};
 			resourcesData[ 'object' ] = {};
 			var objectNodes = resourcesNode.querySelectorAll( 'object' );
 			var objectNodes = resourcesNode.querySelectorAll( 'object' );
 
 
@@ -560,15 +655,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 		}
 		}
 
 
-		function buildMesh( meshData, objects, modelData, objectData ) {
-
-			// geometry
-
-			var geometry = new BufferGeometry();
-			geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) );
-			geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) );
-
-			// groups
+		function buildGroups( geometry, modelData, meshData ) {
 
 
 			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
 			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
 			var triangleProperties = meshData[ 'triangleProperties' ];
 			var triangleProperties = meshData[ 'triangleProperties' ];
@@ -590,7 +677,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 					if ( currentMaterialIndex === triangleProperty.p1 ) {
 					if ( currentMaterialIndex === triangleProperty.p1 ) {
 
 
-						count += 3; // primitves per triangle
+						count += 3; // primitives per triangle
 
 
 					} else {
 					} else {
 
 
@@ -608,20 +695,215 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
 			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
 
 
-			geometry.computeBoundingSphere();
+		}
+
+		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;
+
+			if ( texture2dgroups ) {
+
+				var textureCoordinates = [];
+
+				var triangleProperties = meshData[ 'triangleProperties' ];
+				var texture2dgroupObjectLevel;
+
+				// check reference to texture coordinates on object level
+
+				var texid;
+				var pid = objectData.pid;
+
+				if ( pid && texture2dgroups[ pid ] ) texture2dgroupObjectLevel = texture2dgroups[ pid ];
+
+				// process all triangles
+
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+
+					var texture2dgroup = texture2dgroupObjectLevel;
+					var triangleProperty = triangleProperties[ i ];
+					pid = triangleProperty.pid;
+
+					// overwrite existing resource reference if necessary
+
+					if ( pid && texture2dgroups[ pid ] ) texture2dgroup = texture2dgroups[ pid ];
+
+					if ( texture2dgroup ) {
+
+						texid = texture2dgroup.texid; // the loader only supports a single texture for a single geometry right now (and not per face)
+						var uvs = texture2dgroup.uvs;
+
+						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
+						textureCoordinates.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
+
+						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 ] );
+
+					}
+
+				}
+
+				if ( textureCoordinates.length > 0 ) {
+
+					// uvs are defined on face level so the same vertex can have multiple uv coordinates
+
+					geometry = geometry.toNonIndexed();
+					geometry.setAttribute( 'uv', new Float32BufferAttribute( textureCoordinates, 2 ) );
+					geometry.__texid = texid; // save the relationship between texture coordinates and texture
+
+					return geometry;
+
+				}
+
+			}
+
+			return geometry;
+
+		}
+
+		function buildTexture( geometry, modelData, textureData ) {
+
+			var texid = geometry.__texid;
+
+			if ( texid !== undefined ) {
+
+				delete geometry.__texid;
+
+				if ( textureMap[ texid ] !== undefined ) {
+
+					return textureMap[ texid ];
+
+				} else {
+
+					var texture2ds = modelData.resources.texture2d;
+					var texture2d = texture2ds[ texid ];
+
+					if ( texture2d ) {
+
+						var data = textureData[ texture2d.path ];
+						var type = texture2d.contenttype;
+
+						var blob = new Blob( [ data ], { type: type } );
+						var sourceURI = URL.createObjectURL( blob );
+
+						var texture = textureLoader.load( sourceURI, function () {
+
+							URL.revokeObjectURL( sourceURI );
+
+						} );
+
+						texture.encoding = sRGBEncoding;
+
+						// texture parameters
+
+						switch ( texture2d.tilestyleu ) {
+
+							case 'wrap':
+								texture.wrapS = RepeatWrapping;
+								break;
+
+							case 'mirror':
+								texture.wrapS = MirroredRepeatWrapping;
+								break;
+
+							case 'none':
+							case 'clamp':
+								texture.wrapS = ClampToEdgeWrapping;
+								break;
+
+							default:
+								texture.wrapS = RepeatWrapping;
+
+						}
+
+						switch ( texture2d.tilestylev ) {
+
+							case 'wrap':
+								texture.wrapT = RepeatWrapping;
+								break;
+
+							case 'mirror':
+								texture.wrapT = MirroredRepeatWrapping;
+								break;
+
+							case 'none':
+							case 'clamp':
+								texture.wrapT = ClampToEdgeWrapping;
+								break;
+
+							default:
+								texture.wrapT = RepeatWrapping;
+
+						}
+
+						switch ( texture2d.filter ) {
+
+							case 'auto':
+								texture.magFilter = LinearFilter;
+								texture.minFilter = LinearMipmapLinearFilter;
+								break;
+
+							case 'linear':
+								texture.magFilter = LinearFilter;
+								texture.minFilter = LinearFilter;
+								break;
+
+							case 'nearest':
+								texture.magFilter = NearestFilter;
+								texture.minFilter = NearestFilter;
+								break;
+
+							default:
+								texture.magFilter = LinearFilter;
+								texture.minFilter = LinearMipmapLinearFilter;
+
+						}
+
+						textureMap[ texid ] = texture;
+
+						return texture;
+
+					}
+
+				}
+
+			}
+
+			return null;
+
+		}
+
+		function buildMesh( meshData, objects, modelData, textureData, objectData ) {
+
+			var geometry = buildGeometry( modelData, meshData, objectData );
+			var texture = buildTexture( geometry, modelData, textureData );
+
+			// groups
+
+			buildGroups( geometry, modelData, meshData );
 
 
 			// material
 			// material
 
 
-			var material;
+			var material = null;
 
 
 			// add material if an object-level definition is present
 			// add material if an object-level definition is present
 
 
+			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
+
 			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
 			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
 
 
 				var materialIndex = objectData.pindex;
 				var materialIndex = objectData.pindex;
 				var basematerialData = basematerialsData.basematerials[ materialIndex ];
 				var basematerialData = basematerialsData.basematerials[ materialIndex ];
 
 
-				material = getBuild( basematerialData, objects, modelData, objectData, buildBasematerial );
+				material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
 
 
 			}
 			}
 
 
@@ -636,8 +918,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 					var group = groups[ i ];
 					var group = groups[ i ];
 					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
 					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
-
-					material.push( getBuild( basematerialData, objects, modelData, objectData, buildBasematerial ) );
+					material.push( getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ) );
 
 
 				}
 				}
 
 
@@ -645,7 +926,19 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			// default material
 			// default material
 
 
-			if ( material === undefined ) material = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+			if ( material === null ) {
+
+				if ( texture === null ) {
+
+					material = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+
+				} else {
+
+					material = new MeshPhongMaterial( { map: texture, flatShading: true } );
+
+				}
+
+			}
 
 
 			return new Mesh( geometry, material );
 			return new Mesh( geometry, material );
 
 
@@ -767,11 +1060,11 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 		}
 		}
 
 
-		function getBuild( data, objects, modelData, objectData, builder ) {
+		function getBuild( data, objects, modelData, textureData, objectData, builder ) {
 
 
 			if ( data.build !== undefined ) return data.build;
 			if ( data.build !== undefined ) return data.build;
 
 
-			data.build = builder( data, objects, modelData, objectData );
+			data.build = builder( data, objects, modelData, textureData, objectData );
 
 
 			return data.build;
 			return data.build;
 
 
@@ -803,7 +1096,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 		}
 		}
 
 
-		function buildComposite( compositeData, objects, modelData ) {
+		function buildComposite( compositeData, objects, modelData, textureData ) {
 
 
 			var composite = new Group();
 			var composite = new Group();
 
 
@@ -814,7 +1107,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 				if ( build === undefined ) {
 				if ( build === undefined ) {
 
 
-					buildObject( component.objectId, objects, modelData );
+					buildObject( component.objectId, objects, modelData, textureData );
 					build = objects[ component.objectId ];
 					build = objects[ component.objectId ];
 
 
 				}
 				}
@@ -839,7 +1132,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 		}
 		}
 
 
-		function buildObject( objectId, objects, modelData ) {
+		function buildObject( objectId, objects, modelData, textureData ) {
 
 
 			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
 			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
 
 
@@ -852,13 +1145,13 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 				applyExtensions( extensions, meshData, modelXml );
 				applyExtensions( extensions, meshData, modelXml );
 
 
-				objects[ objectData.id ] = getBuild( meshData, objects, modelData, objectData, buildMesh );
+				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildMesh );
 
 
 			} else {
 			} else {
 
 
 				var compositeData = objectData[ 'components' ];
 				var compositeData = objectData[ 'components' ];
 
 
-				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, objectData, buildComposite );
+				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite );
 
 
 			}
 			}
 
 
@@ -867,8 +1160,26 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 		function buildObjects( data3mf ) {
 		function buildObjects( data3mf ) {
 
 
 			var modelsData = data3mf.model;
 			var modelsData = data3mf.model;
+			var modelRels = data3mf.modelRels;
 			var objects = {};
 			var objects = {};
 			var modelsKeys = Object.keys( modelsData );
 			var modelsKeys = Object.keys( modelsData );
+			var textureData = {};
+
+			// evaluate model relationship to a texture
+
+			if ( modelRels ) {
+
+				var textureKey = modelRels.target.substring( 1 );
+
+				if ( data3mf.texture[ textureKey ] ) {
+
+					textureData[ modelRels.target ] = data3mf.texture[ textureKey ];
+
+				}
+
+			}
+
+			// start build
 
 
 			for ( var i = 0; i < modelsKeys.length; i ++ ) {
 			for ( var i = 0; i < modelsKeys.length; i ++ ) {
 
 
@@ -881,7 +1192,7 @@ ThreeMFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 					var objectId = objectIds[ j ];
 					var objectId = objectIds[ j ];
 
 
-					buildObject( objectId, objects, modelData );
+					buildObject( objectId, objects, modelData, textureData );
 
 
 				}
 				}
 
 

+ 10 - 2
examples/webgl_loader_3mf_materials.html

@@ -62,7 +62,9 @@
 
 
 				//
 				//
 
 
-				var loader = new ThreeMFLoader();
+				var manager = new THREE.LoadingManager();
+
+				var loader = new ThreeMFLoader( manager );
 				loader.load( './models/3mf/truck.3mf', function ( object ) {
 				loader.load( './models/3mf/truck.3mf', function ( object ) {
 
 
 					object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) ); 	// z-up conversion
 					object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) ); 	// z-up conversion
@@ -75,9 +77,15 @@
 
 
 					scene.add( object );
 					scene.add( object );
 
 
+				} );
+
+				//
+
+				manager.onLoad = function () {
+
 					render();
 					render();
 
 
-				} );
+				};
 
 
 				//
 				//