Browse Source

- Original Repo Issue 47: Fixed incorrect vertex color pointerC initialization (omitting first set of values)
- Original Repo Pull Request 46: It is now possible to run `THREE.OBJLoader2` in nodejs 10.5.0+. Thanks to @Cobertos
- Replaced Singletons with pure function/prototype definitions (backport from dev (V3.0.0)). Reason: Counter issues with worker code Blob generation from minified code base (e.g when using webpack)
- #12942: Align `setPath` and `setResourcePath` meaning and handling

Kai Salmen 6 years ago
parent
commit
c1016a6c7e

File diff suppressed because it is too large
+ 178 - 457
examples/js/loaders/LoaderSupport.js


+ 878 - 885
examples/js/loaders/OBJLoader2.js

@@ -15,149 +15,150 @@ if ( THREE.LoaderSupport === undefined ) console.error( '"THREE.LoaderSupport" i
  *
  * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
  */
-THREE.OBJLoader2 = (function () {
 
-	var OBJLOADER2_VERSION = '2.4.2';
-	var Validator = THREE.LoaderSupport.Validator;
+THREE.OBJLoader2 = function ( manager ) {
+	console.info( 'Using THREE.OBJLoader2 version: ' + THREE.OBJLoader2.OBJLOADER2_VERSION );
 
-	function OBJLoader2( manager ) {
-		console.info( 'Using THREE.OBJLoader2 version: ' + OBJLOADER2_VERSION );
+	this.manager = THREE.LoaderSupport.Validator.verifyInput( manager, THREE.DefaultLoadingManager );
+	this.logging = {
+		enabled: true,
+		debug: false
+	};
 
-		this.manager = Validator.verifyInput( manager, THREE.DefaultLoadingManager );
-		this.logging = {
-			enabled: true,
-			debug: false
-		};
+	this.modelName = '';
+	this.instanceNo = 0;
+	this.path;
+	this.resourcePath;
+	this.useIndices = false;
+	this.disregardNormals = false;
+	this.materialPerSmoothingGroup = false;
+	this.useOAsMesh = false;
+	this.loaderRootNode = new THREE.Group();
 
-		this.modelName = '';
-		this.instanceNo = 0;
-		this.path = '';
-		this.useIndices = false;
-		this.disregardNormals = false;
-		this.materialPerSmoothingGroup = false;
-		this.useOAsMesh = false;
-		this.loaderRootNode = new THREE.Group();
-
-		this.meshBuilder = new THREE.LoaderSupport.MeshBuilder();
-		this.callbacks = new THREE.LoaderSupport.Callbacks();
-		this.workerSupport = new THREE.LoaderSupport.WorkerSupport();
-		this.terminateWorkerOnLoad = true;
-	}
+	this.meshBuilder = new THREE.LoaderSupport.MeshBuilder();
+	this.callbacks = new THREE.LoaderSupport.Callbacks();
+	this.workerSupport = new THREE.LoaderSupport.WorkerSupport();
+	this.terminateWorkerOnLoad = true;
+};
+
+THREE.OBJLoader2.OBJLOADER2_VERSION = '2.5.0';
+
+THREE.OBJLoader2.prototype = {
+
+	constructor: THREE.OBJLoader2,
 
 	/**
 	 * Enable or disable logging in general (except warn and error), plus enable or disable debug logging.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {boolean} enabled True or false.
 	 * @param {boolean} debug True or false.
 	 */
-	OBJLoader2.prototype.setLogging = function ( enabled, debug ) {
+	setLogging: function ( enabled, debug ) {
 		this.logging.enabled = enabled === true;
 		this.logging.debug = debug === true;
 		this.meshBuilder.setLogging( this.logging.enabled, this.logging.debug );
-	};
+	},
 
 	/**
 	 * Set the name of the model.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {string} modelName
 	 */
-	OBJLoader2.prototype.setModelName = function ( modelName ) {
-		this.modelName = Validator.verifyInput( modelName, this.modelName );
-	};
+	setModelName: function ( modelName ) {
+		this.modelName = THREE.LoaderSupport.Validator.verifyInput( modelName, this.modelName );
+	},
 
 	/**
 	 * The URL of the base path.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {string} path URL
 	 */
-	OBJLoader2.prototype.setPath = function ( path ) {
-		this.path = Validator.verifyInput( path, this.path );
-	};
+	setPath: function ( path ) {
+		this.path = THREE.LoaderSupport.Validator.verifyInput( path, this.path );
+	},
+
+	/**
+	 * Allow to specify resourcePath for dependencies of specified resource.
+	 * @param {string} resourcePath
+	 */
+	setResourcePath: function ( resourcePath ) {
+		this.resourcePath = THREE.LoaderSupport.Validator.verifyInput( resourcePath, this.resourcePath );
+	},
 
 	/**
 	 * Set the node where the loaded objects will be attached directly.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {THREE.Object3D} streamMeshesTo Object already attached to scenegraph where new meshes will be attached to
 	 */
-	OBJLoader2.prototype.setStreamMeshesTo = function ( streamMeshesTo ) {
-		this.loaderRootNode = Validator.verifyInput( streamMeshesTo, this.loaderRootNode );
-	};
+	setStreamMeshesTo: function ( streamMeshesTo ) {
+		this.loaderRootNode = THREE.LoaderSupport.Validator.verifyInput( streamMeshesTo, this.loaderRootNode );
+	},
 
 	/**
 	 * Set materials loaded by MTLLoader or any other supplier of an Array of {@link THREE.Material}.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {THREE.Material[]} materials Array of {@link THREE.Material}
 	 */
-	OBJLoader2.prototype.setMaterials = function ( materials ) {
+	setMaterials: function ( materials ) {
 		this.meshBuilder.setMaterials( materials );
-	};
+	},
 
 	/**
 	 * Instructs loaders to create indexed {@link THREE.BufferGeometry}.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {boolean} useIndices=false
 	 */
-	OBJLoader2.prototype.setUseIndices = function ( useIndices ) {
+	setUseIndices: function ( useIndices ) {
 		this.useIndices = useIndices === true;
-	};
+	},
 
 	/**
 	 * Tells whether normals should be completely disregarded and regenerated.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {boolean} disregardNormals=false
 	 */
-	OBJLoader2.prototype.setDisregardNormals = function ( disregardNormals ) {
+	setDisregardNormals: function ( disregardNormals ) {
 		this.disregardNormals = disregardNormals === true;
-	};
+	},
 
 	/**
 	 * Tells whether a material shall be created per smoothing group.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {boolean} materialPerSmoothingGroup=false
 	 */
-	OBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
+	setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) {
 		this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
-	};
+	},
 
 	/**
 	 * Usually 'o' is meta-information and does not result in creation of new meshes, but mesh creation on occurrence of "o" can be enforced.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {boolean} useOAsMesh=false
 	 */
-	OBJLoader2.prototype.setUseOAsMesh = function ( useOAsMesh ) {
+	setUseOAsMesh: function ( useOAsMesh ) {
 		this.useOAsMesh = useOAsMesh === true;
-	};
+	},
 
-	OBJLoader2.prototype._setCallbacks = function ( callbacks ) {
-		if ( Validator.isValid( callbacks.onProgress ) ) this.callbacks.setCallbackOnProgress( callbacks.onProgress );
-		if ( Validator.isValid( callbacks.onReportError ) ) this.callbacks.setCallbackOnReportError( callbacks.onReportError );
-		if ( Validator.isValid( callbacks.onMeshAlter ) ) this.callbacks.setCallbackOnMeshAlter( callbacks.onMeshAlter );
-		if ( Validator.isValid( callbacks.onLoad ) ) this.callbacks.setCallbackOnLoad( callbacks.onLoad );
-		if ( Validator.isValid( callbacks.onLoadMaterials ) ) this.callbacks.setCallbackOnLoadMaterials( callbacks.onLoadMaterials );
+	_setCallbacks: function ( callbacks ) {
+		if ( THREE.LoaderSupport.Validator.isValid( callbacks.onProgress ) ) this.callbacks.setCallbackOnProgress( callbacks.onProgress );
+		if ( THREE.LoaderSupport.Validator.isValid( callbacks.onReportError ) ) this.callbacks.setCallbackOnReportError( callbacks.onReportError );
+		if ( THREE.LoaderSupport.Validator.isValid( callbacks.onMeshAlter ) ) this.callbacks.setCallbackOnMeshAlter( callbacks.onMeshAlter );
+		if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoad ) ) this.callbacks.setCallbackOnLoad( callbacks.onLoad );
+		if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoadMaterials ) ) this.callbacks.setCallbackOnLoadMaterials( callbacks.onLoadMaterials );
 
 		this.meshBuilder._setCallbacks( this.callbacks );
-	};
+	},
 
 	/**
 	 * Announce feedback which is give to the registered callbacks.
-	 * @memberOf THREE.OBJLoader2
 	 * @private
 	 *
 	 * @param {string} type The type of event
 	 * @param {string} text Textual description of the event
 	 * @param {number} numericalValue Numerical value describing the progress
 	 */
-	OBJLoader2.prototype.onProgress = function ( type, text, numericalValue ) {
-		var content = Validator.isValid( text ) ? text: '';
+	onProgress: function ( type, text, numericalValue ) {
+		var content = THREE.LoaderSupport.Validator.isValid( text ) ? text: '';
 		var event = {
 			detail: {
 				type: type,
@@ -168,12 +169,12 @@ THREE.OBJLoader2 = (function () {
 			}
 		};
 
-		if ( Validator.isValid( this.callbacks.onProgress ) ) this.callbacks.onProgress( event );
+		if ( THREE.LoaderSupport.Validator.isValid( this.callbacks.onProgress ) ) this.callbacks.onProgress( event );
 
 		if ( this.logging.enabled && this.logging.debug ) console.debug( content );
-	};
+	},
 
-	OBJLoader2.prototype._onError = function ( event ) {
+	_onError: function ( event ) {
 		var output = 'Error occurred while downloading!';
 
 		if ( event.currentTarget && event.currentTarget.statusText !== null ) {
@@ -183,10 +184,10 @@ THREE.OBJLoader2 = (function () {
 		}
 		this.onProgress( 'error', output, -1 );
 		this._throwError( output );
-	};
+	},
 
-	OBJLoader2.prototype._throwError = function ( errorMessage ) {
-		if ( Validator.isValid( this.callbacks.onReportError ) )  {
+	_throwError: function ( errorMessage ) {
+		if ( THREE.LoaderSupport.Validator.isValid( this.callbacks.onReportError ) )  {
 
 			this.callbacks.onReportError( errorMessage );
 
@@ -195,34 +196,33 @@ THREE.OBJLoader2 = (function () {
 			throw errorMessage;
 
 		}
-	};
+	},
 
 	/**
 	 * Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer.
-	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {string}  url A string containing the path/URL of the file to be loaded.
+	 * @param {string} url A string containing the path/URL of the file to be loaded.
 	 * @param {callback} onLoad A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument.
 	 * @param {callback} [onProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
 	 * @param {callback} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
 	 * @param {callback} [onMeshAlter] A function to be called after a new mesh raw data becomes available for alteration.
 	 * @param {boolean} [useAsync] If true, uses async loading with worker, if false loads data synchronously.
 	 */
-	OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
+	load: function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
 		var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'OBJ' );
 		this._loadObj( resource, onLoad, onProgress, onError, onMeshAlter, useAsync );
-	};
+	},
 
-	OBJLoader2.prototype._loadObj = function ( resource, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
+	_loadObj: function ( resource, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
 		var scope = this;
-		if ( ! Validator.isValid( onError ) ) {
+		if ( ! THREE.LoaderSupport.Validator.isValid( onError ) ) {
 			onError = function ( event ) {
 				scope._onError( event );
-			};
+			}
 		}
 
 		// fast-fail
-		if ( ! Validator.isValid( resource ) ) onError( 'An invalid ResourceDescriptor was provided. Unable to continue!' );
+		if ( ! THREE.LoaderSupport.Validator.isValid( resource ) ) onError( 'An invalid ResourceDescriptor was provided. Unable to continue!' );
 		var fileLoaderOnLoad = function ( content ) {
 
 			resource.content = content;
@@ -247,15 +247,17 @@ THREE.OBJLoader2 = (function () {
 
 			}
 		};
+		this.setPath( resource.path );
+		this.setResourcePath( resource.resourcePath );
 
 		// fast-fail
-		if ( ! Validator.isValid( resource.url ) || Validator.isValid( resource.content ) ) {
+		if ( ! THREE.LoaderSupport.Validator.isValid( resource.url ) || THREE.LoaderSupport.Validator.isValid( resource.content ) ) {
 
-			fileLoaderOnLoad( Validator.isValid( resource.content ) ? resource.content : null );
+			fileLoaderOnLoad( THREE.LoaderSupport.Validator.isValid( resource.content ) ? resource.content : null );
 
 		} else {
 
-			if ( ! Validator.isValid( onProgress ) ) {
+			if ( ! THREE.LoaderSupport.Validator.isValid( onProgress ) ) {
 				var numericalValueRef = 0;
 				var numericalValue = 0;
 				onProgress = function ( event ) {
@@ -274,22 +276,20 @@ THREE.OBJLoader2 = (function () {
 
 
 			var fileLoader = new THREE.FileLoader( this.manager );
-			fileLoader.setPath( this.path );
+			fileLoader.setPath( this.path || this.resourcePath );
 			fileLoader.setResponseType( 'arraybuffer' );
-			fileLoader.load( resource.url, fileLoaderOnLoad, onProgress, onError );
+			fileLoader.load( resource.name, fileLoaderOnLoad, onProgress, onError );
 
 		}
-	};
-
+	},
 
 	/**
 	 * Run the loader according the provided instructions.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution
 	 * @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport
 	 */
-	OBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {
+	run: function ( prepData, workerSupportExternal ) {
 		this._applyPrepData( prepData );
 		var available = prepData.checkResourceDescriptorFiles( prepData.resources,
 			[
@@ -298,7 +298,7 @@ THREE.OBJLoader2 = (function () {
 				{ ext: "zip", type: "String", ignore: true }
 			]
 		);
-		if ( Validator.isValid( workerSupportExternal ) ) {
+		if ( THREE.LoaderSupport.Validator.isValid( workerSupportExternal ) ) {
 
 			this.terminateWorkerOnLoad = false;
 			this.workerSupport = workerSupportExternal;
@@ -313,10 +313,10 @@ THREE.OBJLoader2 = (function () {
 
 		};
 		this._loadMtl( available.mtl, onMaterialsLoaded, null, null, prepData.crossOrigin, prepData.materialOptions );
-	};
+	},
 
-	OBJLoader2.prototype._applyPrepData = function ( prepData ) {
-		if ( Validator.isValid( prepData ) ) {
+	_applyPrepData: function ( prepData ) {
+		if ( THREE.LoaderSupport.Validator.isValid( prepData ) ) {
 
 			this.setLogging( prepData.logging.enabled, prepData.logging.debug );
 			this.setModelName( prepData.modelName );
@@ -330,17 +330,16 @@ THREE.OBJLoader2 = (function () {
 			this._setCallbacks( prepData.getCallbacks() );
 
 		}
-	};
+	},
 
 	/**
 	 * Parses OBJ data synchronously from arraybuffer or string.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {arraybuffer|string} content OBJ data as Uint8Array or String
 	 */
-	OBJLoader2.prototype.parse = function ( content ) {
+	parse: function ( content ) {
 		// fast-fail in case of illegal data
-		if ( ! Validator.isValid( content ) ) {
+		if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) {
 
 			console.warn( 'Provided content is not a valid ArrayBuffer or String.' );
 			return this.loaderRootNode;
@@ -349,7 +348,7 @@ THREE.OBJLoader2 = (function () {
 		if ( this.logging.enabled ) console.time( 'OBJLoader2 parse: ' + this.modelName );
 		this.meshBuilder.init();
 
-		var parser = new Parser();
+		var parser = new THREE.OBJLoader2.Parser();
 		parser.setLogging( this.logging.enabled, this.logging.debug );
 		parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
 		parser.setUseOAsMesh( this.useOAsMesh );
@@ -391,16 +390,15 @@ THREE.OBJLoader2 = (function () {
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2 parse: ' + this.modelName );
 
 		return this.loaderRootNode;
-	};
+	},
 
 	/**
 	 * Parses OBJ content asynchronously from arraybuffer.
-	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {arraybuffer} content OBJ data as Uint8Array
 	 * @param {callback} onLoad Called after worker successfully completed loading
 	 */
-	OBJLoader2.prototype.parseAsync = function ( content, onLoad ) {
+	parseAsync: function ( content, onLoad ) {
 		var scope = this;
 		var measureTime = false;
 		var scopedOnLoad = function () {
@@ -416,10 +414,10 @@ THREE.OBJLoader2 = (function () {
 			if ( measureTime && scope.logging.enabled ) console.timeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
 		};
 		// fast-fail in case of illegal data
-		if ( ! Validator.isValid( content ) ) {
+		if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) {
 
 			console.warn( 'Provided content is not a valid ArrayBuffer.' );
-			scopedOnLoad();
+			scopedOnLoad()
 
 		} else {
 
@@ -437,18 +435,18 @@ THREE.OBJLoader2 = (function () {
 				scope.loaderRootNode.add( mesh );
 			}
 		};
-		var buildCode = function ( funcBuildObject, funcBuildSingleton ) {
+		var buildCode = function ( codeSerializer ) {
 			var workerCode = '';
 			workerCode += '/**\n';
 			workerCode += '  * This code was constructed by OBJLoader2 buildCode.\n';
 			workerCode += '  */\n\n';
-			workerCode += 'THREE = { LoaderSupport: {} };\n\n';
-			workerCode += funcBuildObject( 'THREE.LoaderSupport.Validator', Validator );
-			workerCode += funcBuildSingleton( 'Parser', Parser );
+			workerCode += 'THREE = { LoaderSupport: {}, OBJLoader2: {} };\n\n';
+			workerCode += codeSerializer.serializeObject( 'THREE.LoaderSupport.Validator', THREE.LoaderSupport.Validator );
+			workerCode += codeSerializer.serializeClass( 'THREE.OBJLoader2.Parser', THREE.OBJLoader2.Parser );
 
 			return workerCode;
 		};
-		this.workerSupport.validate( buildCode, 'Parser' );
+		this.workerSupport.validate( buildCode, 'THREE.OBJLoader2.Parser' );
 		this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
 		if ( scope.terminateWorkerOnLoad ) this.workerSupport.setTerminateRequested( true );
 
@@ -482,975 +480,970 @@ THREE.OBJLoader2 = (function () {
 				}
 			}
 		);
-	};
-
+	},
 
 	/**
-	 * Parse OBJ data either from ArrayBuffer or string
-	 * @class
+	 * Utility method for loading an mtl file according resource description. Provide url or content.
+	 *
+	 * @param {string} url URL to the file
+	 * @param {Object} content The file content as arraybuffer or text
+	 * @param {function} onLoad Callback to be called after successful load
+	 * @param {callback} [onProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
+	 * @param {callback} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
+	 * @param {string} [crossOrigin] CORS value
+ 	 * @param {Object} [materialOptions] Set material loading options for MTLLoader
 	 */
-	var Parser = (function () {
-
-		function Parser() {
-			this.callbackProgress = null;
-			this.callbackMeshBuilder = null;
-			this.contentRef = null;
-			this.legacyMode = false;
-
-			this.materials = {};
-			this.useAsync = false;
-			this.materialPerSmoothingGroup = false;
-			this.useOAsMesh = false;
-			this.useIndices = false;
-			this.disregardNormals = false;
-
-			this.vertices = [];
-			this.colors = [];
-			this.normals = [];
-			this.uvs = [];
-
-			this.rawMesh = {
-				objectName: '',
-				groupName: '',
-				activeMtlName: '',
-				mtllibName: '',
-
-				// reset with new mesh
-				faceType: -1,
-				subGroups: [],
-				subGroupInUse: null,
-				smoothingGroup: {
-					splitMaterials: false,
-					normalized: -1,
-					real: -1
-				},
-				counts: {
-					doubleIndicesCount: 0,
-					faceCount: 0,
-					mtlCount: 0,
-					smoothingGroupCount: 0
-				}
-			};
-
-			this.inputObjectCount = 1;
-			this.outputObjectCount = 1;
-			this.globalCounts = {
-				vertices: 0,
-				faces: 0,
-				doubleIndicesCount: 0,
-				lineByte: 0,
-				currentByte: 0,
-				totalBytes: 0
-			};
-
-			this.logging = {
-				enabled: true,
-				debug: false
-			};
-		}
-
-		Parser.prototype.resetRawMesh = function () {
-			// faces are stored according combined index of group, material and smoothingGroup (0 or not)
-			this.rawMesh.subGroups = [];
-			this.rawMesh.subGroupInUse = null;
-			this.rawMesh.smoothingGroup.normalized = -1;
-			this.rawMesh.smoothingGroup.real = -1;
-
-			// this default index is required as it is possible to define faces without 'g' or 'usemtl'
-			this.pushSmoothingGroup( 1 );
-
-			this.rawMesh.counts.doubleIndicesCount = 0;
-			this.rawMesh.counts.faceCount = 0;
-			this.rawMesh.counts.mtlCount = 0;
-			this.rawMesh.counts.smoothingGroupCount = 0;
-		};
-
-		Parser.prototype.setUseAsync = function ( useAsync ) {
-			this.useAsync = useAsync;
-		};
-
-		Parser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
-			this.materialPerSmoothingGroup = materialPerSmoothingGroup;
-		};
-
-		Parser.prototype.setUseOAsMesh = function ( useOAsMesh ) {
-			this.useOAsMesh = useOAsMesh;
-		};
+	loadMtl: function ( url, content, onLoad, onProgress, onError, crossOrigin, materialOptions ) {
+		var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
+		resource.setContent( content );
+		this._loadMtl( resource, onLoad, onProgress, onError, crossOrigin, materialOptions );
+	},
 
-		Parser.prototype.setUseIndices = function ( useIndices ) {
-			this.useIndices = useIndices;
-		};
+	_loadMtl: function ( resource, onLoad, onProgress, onError, crossOrigin, materialOptions ) {
+		if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' );
+		if ( THREE.LoaderSupport.Validator.isValid( resource ) && this.logging.enabled ) console.time( 'Loading MTL: ' + resource.name );
 
-		Parser.prototype.setDisregardNormals = function ( disregardNormals ) {
-			this.disregardNormals = disregardNormals;
-		};
+		var materials = [];
+		var scope = this;
+		var processMaterials = function ( materialCreator ) {
+			var materialCreatorMaterials = [];
+			if ( THREE.LoaderSupport.Validator.isValid( materialCreator ) ) {
 
-		Parser.prototype.setMaterials = function ( materials ) {
-			this.materials = THREE.LoaderSupport.Validator.verifyInput( materials, this.materials );
-			this.materials = THREE.LoaderSupport.Validator.verifyInput( this.materials, {} );
-		};
+				materialCreator.preload();
+				materialCreatorMaterials = materialCreator.materials;
+				for ( var materialName in materialCreatorMaterials ) {
 
-		Parser.prototype.setCallbackMeshBuilder = function ( callbackMeshBuilder ) {
-			if ( ! THREE.LoaderSupport.Validator.isValid( callbackMeshBuilder ) ) {
+					if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
 
-				this._throwError( 'Unable to run as no "MeshBuilder" callback is set.' );
+						materials[ materialName ] = materialCreatorMaterials[ materialName ];
 
+					}
+				}
 			}
-			this.callbackMeshBuilder = callbackMeshBuilder;
-		};
-
-		Parser.prototype.setCallbackProgress = function ( callbackProgress ) {
-			this.callbackProgress = callbackProgress;
-		};
 
-		Parser.prototype.setLogging = function ( enabled, debug ) {
-			this.logging.enabled = enabled === true;
-			this.logging.debug = debug === true;
+			if ( THREE.LoaderSupport.Validator.isValid( resource ) && scope.logging.enabled ) console.timeEnd( 'Loading MTL: ' + resource.name );
+			onLoad( materials, materialCreator );
 		};
 
-		Parser.prototype.configure = function () {
-			this.pushSmoothingGroup( 1 );
-
-			if ( this.logging.enabled ) {
-
-				var matKeys = Object.keys( this.materials );
-				var matNames = ( matKeys.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + matKeys.join( '\n\t\t- ' ) : '\n\tmaterialNames: None';
-				var printedConfig = 'OBJLoader2.Parser configuration:'
-					+ matNames
-					+ '\n\tuseAsync: ' + this.useAsync
-					+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
-					+ '\n\tuseOAsMesh: ' + this.useOAsMesh
-					+ '\n\tuseIndices: ' + this.useIndices
-					+ '\n\tdisregardNormals: ' + this.disregardNormals
-					+ '\n\tcallbackMeshBuilderName: ' + this.callbackMeshBuilder.name
-					+ '\n\tcallbackProgressName: ' + this.callbackProgress.name;
-				console.info( printedConfig );
-			}
-		};
+		// fast-fail
+		if ( ! THREE.LoaderSupport.Validator.isValid( resource ) || ( ! THREE.LoaderSupport.Validator.isValid( resource.content ) && ! THREE.LoaderSupport.Validator.isValid( resource.url ) ) ) {
 
-		/**
-		 * Parse the provided arraybuffer
-		 * @memberOf Parser
-		 *
-		 * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
-		 */
-		Parser.prototype.parse = function ( arrayBuffer ) {
-			if ( this.logging.enabled ) console.time( 'OBJLoader2.Parser.parse' );
-			this.configure();
-
-			var arrayBufferView = new Uint8Array( arrayBuffer );
-			this.contentRef = arrayBufferView;
-			var length = arrayBufferView.byteLength;
-			this.globalCounts.totalBytes = length;
-			var buffer = new Array( 128 );
-
-			for ( var code, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) {
-
-				code = arrayBufferView[ i ];
-				switch ( code ) {
-					// space
-					case 32:
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						word = '';
-						break;
-					// slash
-					case 47:
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						slashesCount++;
-						word = '';
-						break;
-
-					// LF
-					case 10:
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						word = '';
-						this.globalCounts.lineByte = this.globalCounts.currentByte;
-						this.globalCounts.currentByte = i;
-						this.processLine( buffer, bufferPointer, slashesCount );
-						bufferPointer = 0;
-						slashesCount = 0;
-						break;
-
-					// CR
-					case 13:
-						break;
-
-					default:
-						word += String.fromCharCode( code );
-						break;
-				}
-			}
-			this.finalizeParsing();
-			if ( this.logging.enabled ) console.timeEnd(  'OBJLoader2.Parser.parse' );
-		};
+			processMaterials();
 
-		/**
-		 * Parse the provided text
-		 * @memberOf Parser
-		 *
-		 * @param {string} text OBJ data as string
-		 */
-		Parser.prototype.parseText = function ( text ) {
-			if ( this.logging.enabled ) console.time(  'OBJLoader2.Parser.parseText' );
-			this.configure();
-			this.legacyMode = true;
-			this.contentRef = text;
-			var length = text.length;
-			this.globalCounts.totalBytes = length;
-			var buffer = new Array( 128 );
-
-			for ( var char, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) {
-
-				char = text[ i ];
-				switch ( char ) {
-					case ' ':
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						word = '';
-						break;
-
-					case '/':
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						slashesCount++;
-						word = '';
-						break;
-
-					case '\n':
-						if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
-						word = '';
-						this.globalCounts.lineByte = this.globalCounts.currentByte;
-						this.globalCounts.currentByte = i;
-						this.processLine( buffer, bufferPointer, slashesCount );
-						bufferPointer = 0;
-						slashesCount = 0;
-						break;
-
-					case '\r':
-						break;
-
-					default:
-						word += char;
-				}
-			}
-			this.finalizeParsing();
-			if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2.Parser.parseText' );
-		};
+		} else {
 
-		Parser.prototype.processLine = function ( buffer, bufferPointer, slashesCount ) {
-			if ( bufferPointer < 1 ) return;
+			var mtlLoader = new THREE.MTLLoader( this.manager );
+			crossOrigin = THREE.LoaderSupport.Validator.verifyInput( crossOrigin, 'anonymous' );
+			mtlLoader.setCrossOrigin( crossOrigin );
+			mtlLoader.setResourcePath( resource.resourcePath || resource.path );
+			if ( THREE.LoaderSupport.Validator.isValid( materialOptions ) ) mtlLoader.setMaterialOptions( materialOptions );
 
-			var reconstructString = function ( content, legacyMode, start, stop ) {
-				var line = '';
-				if ( stop > start ) {
+			var parseTextWithMtlLoader = function ( content ) {
+				var contentAsText = content;
+				if ( typeof( content ) !== 'string' && ! ( content instanceof String ) ) {
 
-					var i;
-					if ( legacyMode ) {
+					if ( content.length > 0 || content.byteLength > 0 ) {
 
-						for ( i = start; i < stop; i++ ) line += content[ i ];
+						contentAsText = THREE.LoaderUtils.decodeText( content );
 
 					} else {
 
-
-						for ( i = start; i < stop; i++ ) line += String.fromCharCode( content[ i ] );
-
+						this._throwError( 'Unable to parse mtl as it it seems to be neither a String, an Array or an ArrayBuffer!' );
 					}
-					line = line.trim();
 
 				}
-				return line;
+				processMaterials( mtlLoader.parse( contentAsText ) );
 			};
 
-			var bufferLength, length, i, lineDesignation;
-			lineDesignation = buffer [ 0 ];
-			switch ( lineDesignation ) {
-				case 'v':
-					this.vertices.push( parseFloat( buffer[ 1 ] ) );
-					this.vertices.push( parseFloat( buffer[ 2 ] ) );
-					this.vertices.push( parseFloat( buffer[ 3 ] ) );
-					if ( bufferPointer > 4 ) {
+			if ( THREE.LoaderSupport.Validator.isValid( resource.content ) ) {
 
-						this.colors.push( parseFloat( buffer[ 4 ] ) );
-						this.colors.push( parseFloat( buffer[ 5 ] ) );
-						this.colors.push( parseFloat( buffer[ 6 ] ) );
+				parseTextWithMtlLoader( resource.content );
+
+			} else if ( THREE.LoaderSupport.Validator.isValid( resource.url ) ) {
 
+				var fileLoader = new THREE.FileLoader( this.manager );
+				if ( ! THREE.LoaderSupport.Validator.isValid( onError ) ) {
+					onError = function ( event ) {
+						scope._onError( event );
 					}
-					break;
+				}
+				if ( ! THREE.LoaderSupport.Validator.isValid( onProgress ) ) {
+					var numericalValueRef = 0;
+					var numericalValue = 0;
+					onProgress = function ( event ) {
+						if ( ! event.lengthComputable ) return;
 
-				case 'vt':
-					this.uvs.push( parseFloat( buffer[ 1 ] ) );
-					this.uvs.push( parseFloat( buffer[ 2 ] ) );
-					break;
+						numericalValue = event.loaded / event.total;
+						if ( numericalValue > numericalValueRef ) {
 
-				case 'vn':
-					this.normals.push( parseFloat( buffer[ 1 ] ) );
-					this.normals.push( parseFloat( buffer[ 2 ] ) );
-					this.normals.push( parseFloat( buffer[ 3 ] ) );
-					break;
+							numericalValueRef = numericalValue;
+							var output = 'Download of "' + resource.url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
+							scope.onProgress( 'progressLoad', output, numericalValue );
 
-				case 'f':
-					bufferLength = bufferPointer - 1;
+						}
+					};
+				}
 
-					// "f vertex ..."
-					if ( slashesCount === 0 ) {
+				fileLoader.load( resource.url, parseTextWithMtlLoader, onProgress, onError );
 
-						this.checkFaceType( 0 );
-						for ( i = 2, length = bufferLength; i < length; i ++ ) {
+			}
+		}
+	}
+};
 
-							this.buildFace( buffer[ 1 ] );
-							this.buildFace( buffer[ i ] );
-							this.buildFace( buffer[ i + 1 ] );
 
-						}
+/**
+ * Parse OBJ data either from ArrayBuffer or string
+ * @class
+ */
+THREE.OBJLoader2.Parser = function () {
+	this.callbackProgress = null;
+	this.callbackMeshBuilder = null;
+	this.contentRef = null;
+	this.legacyMode = false;
+
+	this.materials = {};
+	this.useAsync = false;
+	this.materialPerSmoothingGroup = false;
+	this.useOAsMesh = false;
+	this.useIndices = false;
+	this.disregardNormals = false;
+
+	this.vertices = [];
+	this.colors = [];
+	this.normals = [];
+	this.uvs = [];
+
+	this.rawMesh = {
+		objectName: '',
+		groupName: '',
+		activeMtlName: '',
+		mtllibName: '',
+
+		// reset with new mesh
+		faceType: -1,
+		subGroups: [],
+		subGroupInUse: null,
+		smoothingGroup: {
+			splitMaterials: false,
+			normalized: -1,
+			real: -1
+		},
+		counts: {
+			doubleIndicesCount: 0,
+			faceCount: 0,
+			mtlCount: 0,
+			smoothingGroupCount: 0
+		}
+	};
 
-					// "f vertex/uv ..."
-					} else if  ( bufferLength === slashesCount * 2 ) {
+	this.inputObjectCount = 1;
+	this.outputObjectCount = 1;
+	this.globalCounts = {
+		vertices: 0,
+		faces: 0,
+		doubleIndicesCount: 0,
+		lineByte: 0,
+		currentByte: 0,
+		totalBytes: 0
+	};
 
-						this.checkFaceType( 1 );
-						for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
+	this.logging = {
+		enabled: true,
+		debug: false
+	};
+};
 
-							this.buildFace( buffer[ 1 ], buffer[ 2 ] );
-							this.buildFace( buffer[ i ], buffer[ i + 1 ] );
-							this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
 
-						}
+THREE.OBJLoader2.Parser.prototype = {
 
-					// "f vertex/uv/normal ..."
-					} else if  ( bufferLength * 2 === slashesCount * 3 ) {
+	constructor: THREE.OBJLoader2.Parser,
 
-						this.checkFaceType( 2 );
-						for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
+	resetRawMesh: function () {
+		// faces are stored according combined index of group, material and smoothingGroup (0 or not)
+		this.rawMesh.subGroups = [];
+		this.rawMesh.subGroupInUse = null;
+		this.rawMesh.smoothingGroup.normalized = -1;
+		this.rawMesh.smoothingGroup.real = -1;
 
-							this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
-							this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
-							this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
+		// this default index is required as it is possible to define faces without 'g' or 'usemtl'
+		this.pushSmoothingGroup( 1 );
 
-						}
+		this.rawMesh.counts.doubleIndicesCount = 0;
+		this.rawMesh.counts.faceCount = 0;
+		this.rawMesh.counts.mtlCount = 0;
+		this.rawMesh.counts.smoothingGroupCount = 0;
+	},
 
-					// "f vertex//normal ..."
-					} else {
+	setUseAsync: function ( useAsync ) {
+		this.useAsync = useAsync;
+	},
 
-						this.checkFaceType( 3 );
-						for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
+	setMaterialPerSmoothingGroup: function ( materialPerSmoothingGroup ) {
+		this.materialPerSmoothingGroup = materialPerSmoothingGroup;
+	},
 
-							this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
-							this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
-							this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
+	setUseOAsMesh: function ( useOAsMesh ) {
+		this.useOAsMesh = useOAsMesh;
+	},
 
-						}
+	setUseIndices: function ( useIndices ) {
+		this.useIndices = useIndices;
+	},
 
-					}
-					break;
+	setDisregardNormals: function ( disregardNormals ) {
+		this.disregardNormals = disregardNormals;
+	},
 
-				case 'l':
-				case 'p':
-					bufferLength = bufferPointer - 1;
-					if ( bufferLength === slashesCount * 2 )  {
+	setMaterials: function ( materials ) {
+		this.materials = THREE.LoaderSupport.Validator.verifyInput( materials, this.materials );
+		this.materials = THREE.LoaderSupport.Validator.verifyInput( this.materials, {} );
+	},
 
-						this.checkFaceType( 4 );
-						for ( i = 1, length = bufferLength + 1; i < length; i += 2 ) this.buildFace( buffer[ i ], buffer[ i + 1 ] );
+	setCallbackMeshBuilder: function ( callbackMeshBuilder ) {
+		if ( ! THREE.LoaderSupport.Validator.isValid( callbackMeshBuilder ) ) {
 
-					} else {
+			this._throwError( 'Unable to run as no "MeshBuilder" callback is set.' );
 
-						this.checkFaceType( ( lineDesignation === 'l' ) ? 5 : 6  );
-						for ( i = 1, length = bufferLength + 1; i < length; i ++ ) this.buildFace( buffer[ i ] );
+		}
+		this.callbackMeshBuilder = callbackMeshBuilder;
+	},
 
-					}
-					break;
+	setCallbackProgress: function ( callbackProgress ) {
+		this.callbackProgress = callbackProgress;
+	},
+
+	setLogging: function ( enabled, debug ) {
+		this.logging.enabled = enabled === true;
+		this.logging.debug = debug === true;
+	},
+
+	configure: function () {
+		this.pushSmoothingGroup( 1 );
+
+		if ( this.logging.enabled ) {
+
+			var matKeys = Object.keys( this.materials );
+			var matNames = ( matKeys.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + matKeys.join( '\n\t\t- ' ) : '\n\tmaterialNames: None';
+			var printedConfig = 'OBJLoader2.Parser configuration:'
+				+ matNames
+				+ '\n\tuseAsync: ' + this.useAsync
+				+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
+				+ '\n\tuseOAsMesh: ' + this.useOAsMesh
+				+ '\n\tuseIndices: ' + this.useIndices
+				+ '\n\tdisregardNormals: ' + this.disregardNormals
+				+ '\n\tcallbackMeshBuilderName: ' + this.callbackMeshBuilder.name
+				+ '\n\tcallbackProgressName: ' + this.callbackProgress.name;
+			console.info( printedConfig );
+		}
+	},
 
-				case 's':
-					this.pushSmoothingGroup( buffer[ 1 ] );
+	/**
+	 * Parse the provided arraybuffer
+	 *
+	 * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
+	 */
+	parse: function ( arrayBuffer ) {
+		if ( this.logging.enabled ) console.time( 'OBJLoader2.Parser.parse' );
+		this.configure();
+
+		var arrayBufferView = new Uint8Array( arrayBuffer );
+		this.contentRef = arrayBufferView;
+		var length = arrayBufferView.byteLength;
+		this.globalCounts.totalBytes = length;
+		var buffer = new Array( 128 );
+
+		for ( var code, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) {
+
+			code = arrayBufferView[ i ];
+			switch ( code ) {
+				// space
+				case 32:
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					word = '';
+					break;
+				// slash
+				case 47:
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					slashesCount++;
+					word = '';
 					break;
 
-				case 'g':
-					// 'g' leads to creation of mesh if valid data (faces declaration was done before), otherwise only groupName gets set
-					this.processCompletedMesh();
-					this.rawMesh.groupName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
+				// LF
+				case 10:
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					word = '';
+					this.globalCounts.lineByte = this.globalCounts.currentByte;
+					this.globalCounts.currentByte = i;
+					this.processLine( buffer, bufferPointer, slashesCount );
+					bufferPointer = 0;
+					slashesCount = 0;
 					break;
 
-				case 'o':
-					// 'o' is meta-information and usually does not result in creation of new meshes, but can be enforced with "useOAsMesh"
-					if ( this.useOAsMesh ) this.processCompletedMesh();
-					this.rawMesh.objectName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
+				// CR
+				case 13:
 					break;
 
-				case 'mtllib':
-					this.rawMesh.mtllibName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
+				default:
+					word += String.fromCharCode( code );
 					break;
+			}
+		}
+		this.finalizeParsing();
+		if ( this.logging.enabled ) console.timeEnd(  'OBJLoader2.Parser.parse' );
+	},
 
-				case 'usemtl':
-					var mtlName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
-					if ( mtlName !== '' && this.rawMesh.activeMtlName !== mtlName ) {
+	/**
+	 * Parse the provided text
+	 *
+	 * @param {string} text OBJ data as string
+	 */
+	parseText: function ( text ) {
+		if ( this.logging.enabled ) console.time(  'OBJLoader2.Parser.parseText' );
+		this.configure();
+		this.legacyMode = true;
+		this.contentRef = text;
+		var length = text.length;
+		this.globalCounts.totalBytes = length;
+		var buffer = new Array( 128 );
+
+		for ( var char, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i++ ) {
+
+			char = text[ i ];
+			switch ( char ) {
+				case ' ':
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					word = '';
+					break;
 
-						this.rawMesh.activeMtlName = mtlName;
-						this.rawMesh.counts.mtlCount++;
-						this.checkSubGroup();
+				case '/':
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					slashesCount++;
+					word = '';
+					break;
 
-					}
+				case '\n':
+					if ( word.length > 0 ) buffer[ bufferPointer++ ] = word;
+					word = '';
+					this.globalCounts.lineByte = this.globalCounts.currentByte;
+					this.globalCounts.currentByte = i;
+					this.processLine( buffer, bufferPointer, slashesCount );
+					bufferPointer = 0;
+					slashesCount = 0;
 					break;
 
-				default:
+				case '\r':
 					break;
-			}
-		};
 
-		Parser.prototype.pushSmoothingGroup = function ( smoothingGroup ) {
-			var smoothingGroupInt = parseInt( smoothingGroup );
-			if ( isNaN( smoothingGroupInt ) ) {
-				smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
+				default:
+					word += char;
 			}
+		}
+		this.finalizeParsing();
+		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2.Parser.parseText' );
+	},
 
-			var smoothCheck = this.rawMesh.smoothingGroup.normalized;
-			this.rawMesh.smoothingGroup.normalized = this.rawMesh.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
-			this.rawMesh.smoothingGroup.real = smoothingGroupInt;
+	processLine: function ( buffer, bufferPointer, slashesCount ) {
+		if ( bufferPointer < 1 ) return;
 
-			if ( smoothCheck !== smoothingGroupInt ) {
+		var reconstructString = function ( content, legacyMode, start, stop ) {
+			var line = '';
+			if ( stop > start ) {
 
-				this.rawMesh.counts.smoothingGroupCount++;
-				this.checkSubGroup();
+				var i;
+				if ( legacyMode ) {
 
-			}
-		};
+					for ( i = start; i < stop; i++ ) line += content[ i ];
 
-		/**
-		 * Expanded faceTypes include all four face types, both line types and the point type
-		 * faceType = 0: "f vertex ..."
-		 * faceType = 1: "f vertex/uv ..."
-		 * faceType = 2: "f vertex/uv/normal ..."
-		 * faceType = 3: "f vertex//normal ..."
-		 * faceType = 4: "l vertex/uv ..." or "l vertex ..."
-		 * faceType = 5: "l vertex ..."
-		 * faceType = 6: "p vertex ..."
-		 */
-		Parser.prototype.checkFaceType = function ( faceType ) {
-			if ( this.rawMesh.faceType !== faceType ) {
+				} else {
 
-				this.processCompletedMesh();
-				this.rawMesh.faceType = faceType;
-				this.checkSubGroup();
 
-			}
-		};
+					for ( i = start; i < stop; i++ ) line += String.fromCharCode( content[ i ] );
 
-		Parser.prototype.checkSubGroup = function () {
-			var index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized;
-			this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ];
-
-			if ( ! THREE.LoaderSupport.Validator.isValid( this.rawMesh.subGroupInUse ) ) {
-
-				this.rawMesh.subGroupInUse = {
-					index: index,
-					objectName: this.rawMesh.objectName,
-					groupName: this.rawMesh.groupName,
-					materialName: this.rawMesh.activeMtlName,
-					smoothingGroup: this.rawMesh.smoothingGroup.normalized,
-					vertices: [],
-					indexMappingsCount: 0,
-					indexMappings: [],
-					indices: [],
-					colors: [],
-					uvs: [],
-					normals: []
-				};
-				this.rawMesh.subGroups[ index ] = this.rawMesh.subGroupInUse;
+				}
+				line = line.trim();
 
 			}
+			return line;
 		};
 
-		Parser.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
-			if ( this.disregardNormals ) faceIndexN = undefined;
-			var scope = this;
-			var updateSubGroupInUse = function () {
+		var bufferLength, length, i, lineDesignation;
+		lineDesignation = buffer [ 0 ];
+		switch ( lineDesignation ) {
+			case 'v':
+				this.vertices.push( parseFloat( buffer[ 1 ] ) );
+				this.vertices.push( parseFloat( buffer[ 2 ] ) );
+				this.vertices.push( parseFloat( buffer[ 3 ] ) );
+				if ( bufferPointer > 4 ) {
 
-				var faceIndexVi = parseInt( faceIndexV );
-				var indexPointerV = 3 * ( faceIndexVi > 0 ? faceIndexVi - 1 : faceIndexVi + scope.vertices.length / 3 );
+					this.colors.push( parseFloat( buffer[ 4 ] ) );
+					this.colors.push( parseFloat( buffer[ 5 ] ) );
+					this.colors.push( parseFloat( buffer[ 6 ] ) );
 
-				var vertices = scope.rawMesh.subGroupInUse.vertices;
-				vertices.push( scope.vertices[ indexPointerV++ ] );
-				vertices.push( scope.vertices[ indexPointerV++ ] );
-				vertices.push( scope.vertices[ indexPointerV ] );
+				}
+				break;
 
-				var indexPointerC = scope.colors.length > 0 ? indexPointerV + 1 : null;
-				if ( indexPointerC !== null ) {
+			case 'vt':
+				this.uvs.push( parseFloat( buffer[ 1 ] ) );
+				this.uvs.push( parseFloat( buffer[ 2 ] ) );
+				break;
 
-					var colors = scope.rawMesh.subGroupInUse.colors;
-					colors.push( scope.colors[ indexPointerC++ ] );
-					colors.push( scope.colors[ indexPointerC++ ] );
-					colors.push( scope.colors[ indexPointerC ] );
+			case 'vn':
+				this.normals.push( parseFloat( buffer[ 1 ] ) );
+				this.normals.push( parseFloat( buffer[ 2 ] ) );
+				this.normals.push( parseFloat( buffer[ 3 ] ) );
+				break;
 
-				}
-				if ( faceIndexU ) {
+			case 'f':
+				bufferLength = bufferPointer - 1;
 
-					var faceIndexUi = parseInt( faceIndexU );
-					var indexPointerU = 2 * ( faceIndexUi > 0 ? faceIndexUi - 1 : faceIndexUi + scope.uvs.length / 2 );
-					var uvs = scope.rawMesh.subGroupInUse.uvs;
-					uvs.push( scope.uvs[ indexPointerU++ ] );
-					uvs.push( scope.uvs[ indexPointerU ] );
+				// "f vertex ..."
+				if ( slashesCount === 0 ) {
 
-				}
-				if ( faceIndexN ) {
+					this.checkFaceType( 0 );
+					for ( i = 2, length = bufferLength; i < length; i ++ ) {
 
-					var faceIndexNi = parseInt( faceIndexN );
-					var indexPointerN = 3 * ( faceIndexNi > 0 ? faceIndexNi - 1 : faceIndexNi + scope.normals.length / 3 );
-					var normals = scope.rawMesh.subGroupInUse.normals;
-					normals.push( scope.normals[ indexPointerN++ ] );
-					normals.push( scope.normals[ indexPointerN++ ] );
-					normals.push( scope.normals[ indexPointerN ] );
+						this.buildFace( buffer[ 1 ] );
+						this.buildFace( buffer[ i ] );
+						this.buildFace( buffer[ i + 1 ] );
 
-				}
-			};
+					}
 
-			if ( this.useIndices ) {
+					// "f vertex/uv ..."
+				} else if  ( bufferLength === slashesCount * 2 ) {
 
-				var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
-				var indicesPointer = this.rawMesh.subGroupInUse.indexMappings[ mappingName ];
-				if ( THREE.LoaderSupport.Validator.isValid( indicesPointer ) ) {
+					this.checkFaceType( 1 );
+					for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
 
-					this.rawMesh.counts.doubleIndicesCount++;
+						this.buildFace( buffer[ 1 ], buffer[ 2 ] );
+						this.buildFace( buffer[ i ], buffer[ i + 1 ] );
+						this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
 
-				} else {
+					}
 
-					indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3;
-					updateSubGroupInUse();
-					this.rawMesh.subGroupInUse.indexMappings[ mappingName ] = indicesPointer;
-					this.rawMesh.subGroupInUse.indexMappingsCount++;
+					// "f vertex/uv/normal ..."
+				} else if  ( bufferLength * 2 === slashesCount * 3 ) {
 
-				}
-				this.rawMesh.subGroupInUse.indices.push( indicesPointer );
+					this.checkFaceType( 2 );
+					for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
 
-			} else {
+						this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
+						this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
+						this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
 
-				updateSubGroupInUse();
+					}
 
-			}
-			this.rawMesh.counts.faceCount++;
-		};
+					// "f vertex//normal ..."
+				} else {
 
-		Parser.prototype.createRawMeshReport = function ( inputObjectCount ) {
-			return 'Input Object number: ' + inputObjectCount +
-				'\n\tObject name: ' + this.rawMesh.objectName +
-				'\n\tGroup name: ' + this.rawMesh.groupName +
-				'\n\tMtllib name: ' + this.rawMesh.mtllibName +
-				'\n\tVertex count: ' + this.vertices.length / 3 +
-				'\n\tNormal count: ' + this.normals.length / 3 +
-				'\n\tUV count: ' + this.uvs.length / 2 +
-				'\n\tSmoothingGroup count: ' + this.rawMesh.counts.smoothingGroupCount +
-				'\n\tMaterial count: ' + this.rawMesh.counts.mtlCount +
-				'\n\tReal MeshOutputGroup count: ' + this.rawMesh.subGroups.length;
-		};
+					this.checkFaceType( 3 );
+					for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
 
-		/**
-		 * Clear any empty subGroup and calculate absolute vertex, normal and uv counts
-		 */
-		Parser.prototype.finalizeRawMesh = function () {
-			var meshOutputGroupTemp = [];
-			var meshOutputGroup;
-			var absoluteVertexCount = 0;
-			var absoluteIndexMappingsCount = 0;
-			var absoluteIndexCount = 0;
-			var absoluteColorCount = 0;
-			var absoluteNormalCount = 0;
-			var absoluteUvCount = 0;
-			var indices;
-			for ( var name in this.rawMesh.subGroups ) {
-
-				meshOutputGroup = this.rawMesh.subGroups[ name ];
-				if ( meshOutputGroup.vertices.length > 0 ) {
-
-					indices = meshOutputGroup.indices;
-					if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {
-
-						for ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;
+						this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
+						this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
+						this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
 
 					}
-					meshOutputGroupTemp.push( meshOutputGroup );
-					absoluteVertexCount += meshOutputGroup.vertices.length;
-					absoluteIndexMappingsCount += meshOutputGroup.indexMappingsCount;
-					absoluteIndexCount += meshOutputGroup.indices.length;
-					absoluteColorCount += meshOutputGroup.colors.length;
-					absoluteUvCount += meshOutputGroup.uvs.length;
-					absoluteNormalCount += meshOutputGroup.normals.length;
 
 				}
-			}
+				break;
 
-			// do not continue if no result
-			var result = null;
-			if ( meshOutputGroupTemp.length > 0 ) {
-
-				result = {
-					name: this.rawMesh.groupName !== '' ? this.rawMesh.groupName : this.rawMesh.objectName,
-					subGroups: meshOutputGroupTemp,
-					absoluteVertexCount: absoluteVertexCount,
-					absoluteIndexCount: absoluteIndexCount,
-					absoluteColorCount: absoluteColorCount,
-					absoluteNormalCount: absoluteNormalCount,
-					absoluteUvCount: absoluteUvCount,
-					faceCount: this.rawMesh.counts.faceCount,
-					doubleIndicesCount: this.rawMesh.counts.doubleIndicesCount
-				};
+			case 'l':
+			case 'p':
+				bufferLength = bufferPointer - 1;
+				if ( bufferLength === slashesCount * 2 )  {
 
-			}
-			return result;
-		};
+					this.checkFaceType( 4 );
+					for ( i = 1, length = bufferLength + 1; i < length; i += 2 ) this.buildFace( buffer[ i ], buffer[ i + 1 ] );
 
-		Parser.prototype.processCompletedMesh = function () {
-			var result = this.finalizeRawMesh();
-			if ( THREE.LoaderSupport.Validator.isValid( result ) ) {
-
-				if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) {
+				} else {
 
-					this._throwError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
+					this.checkFaceType( ( lineDesignation === 'l' ) ? 5 : 6  );
+					for ( i = 1, length = bufferLength + 1; i < length; i ++ ) this.buildFace( buffer[ i ] );
 
 				}
-				if ( this.logging.enabled && this.logging.debug ) console.debug( this.createRawMeshReport( this.inputObjectCount ) );
-				this.inputObjectCount++;
+				break;
 
-				this.buildMesh( result );
-				var progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes;
-				this.callbackProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
-				this.resetRawMesh();
-				return true;
+			case 's':
+				this.pushSmoothingGroup( buffer[ 1 ] );
+				break;
 
-			} else {
-
-				return false;
-			}
-		};
+			case 'g':
+				// 'g' leads to creation of mesh if valid data (faces declaration was done before), otherwise only groupName gets set
+				this.processCompletedMesh();
+				this.rawMesh.groupName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
+				break;
 
-		/**
-		 * SubGroups are transformed to too intermediate format that is forwarded to the MeshBuilder.
-		 * It is ensured that SubGroups only contain objects with vertices (no need to check).
-		 *
-		 * @param result
-		 */
-		Parser.prototype.buildMesh = function ( result ) {
-			var meshOutputGroups = result.subGroups;
-
-			var vertexFA = new Float32Array( result.absoluteVertexCount );
-			this.globalCounts.vertices += result.absoluteVertexCount / 3;
-			this.globalCounts.faces += result.faceCount;
-			this.globalCounts.doubleIndicesCount += result.doubleIndicesCount;
-			var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
-			var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
-			var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
-			var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
-			var haveVertexColors = THREE.LoaderSupport.Validator.isValid( colorFA );
-
-			var meshOutputGroup;
-			var materialNames = [];
-
-			var createMultiMaterial = ( meshOutputGroups.length > 1 );
-			var materialIndex = 0;
-			var materialIndexMapping = [];
-			var selectedMaterialIndex;
-			var materialGroup;
-			var materialGroups = [];
-
-			var vertexFAOffset = 0;
-			var indexUAOffset = 0;
-			var colorFAOffset = 0;
-			var normalFAOffset = 0;
-			var uvFAOffset = 0;
-			var materialGroupOffset = 0;
-			var materialGroupLength = 0;
-
-			var materialOrg, material, materialName, materialNameOrg;
-			// only one specific face type
-			for ( var oodIndex in meshOutputGroups ) {
-
-				if ( ! meshOutputGroups.hasOwnProperty( oodIndex ) ) continue;
-				meshOutputGroup = meshOutputGroups[ oodIndex ];
-
-				materialNameOrg = meshOutputGroup.materialName;
-				if ( this.rawMesh.faceType < 4 ) {
-
-					materialName = materialNameOrg + ( haveVertexColors ? '_vertexColor' : '' ) + ( meshOutputGroup.smoothingGroup === 0 ? '_flat' : '' );
+			case 'o':
+				// 'o' is meta-information and usually does not result in creation of new meshes, but can be enforced with "useOAsMesh"
+				if ( this.useOAsMesh ) this.processCompletedMesh();
+				this.rawMesh.objectName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 2, this.globalCounts.currentByte );
+				break;
 
+			case 'mtllib':
+				this.rawMesh.mtllibName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
+				break;
 
-				} else {
+			case 'usemtl':
+				var mtlName = reconstructString( this.contentRef, this.legacyMode, this.globalCounts.lineByte + 7, this.globalCounts.currentByte );
+				if ( mtlName !== '' && this.rawMesh.activeMtlName !== mtlName ) {
 
-					materialName = this.rawMesh.faceType === 6 ? 'defaultPointMaterial' : 'defaultLineMaterial';
+					this.rawMesh.activeMtlName = mtlName;
+					this.rawMesh.counts.mtlCount++;
+					this.checkSubGroup();
 
 				}
-				materialOrg = this.materials[ materialNameOrg ];
-				material = this.materials[ materialName ];
+				break;
 
-				// both original and derived names do not lead to an existing material => need to use a default material
-				if ( ! THREE.LoaderSupport.Validator.isValid( materialOrg ) && ! THREE.LoaderSupport.Validator.isValid( material ) ) {
+			default:
+				break;
+		}
+	},
 
-					var defaultMaterialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
-					materialOrg = this.materials[ defaultMaterialName ];
-					if ( this.logging.enabled ) console.warn( 'object_group "' + meshOutputGroup.objectName + '_' +
-						meshOutputGroup.groupName + '" was defined with unresolvable material "' +
-						materialNameOrg + '"! Assigning "' + defaultMaterialName + '".' );
-					materialNameOrg = defaultMaterialName;
+	pushSmoothingGroup: function ( smoothingGroup ) {
+		var smoothingGroupInt = parseInt( smoothingGroup );
+		if ( isNaN( smoothingGroupInt ) ) {
+			smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
+		}
 
-					// if names are identical then there is no need for later manipulation
-					if ( materialNameOrg === materialName ) {
+		var smoothCheck = this.rawMesh.smoothingGroup.normalized;
+		this.rawMesh.smoothingGroup.normalized = this.rawMesh.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
+		this.rawMesh.smoothingGroup.real = smoothingGroupInt;
 
-						material = materialOrg;
-						materialName = defaultMaterialName;
+		if ( smoothCheck !== smoothingGroupInt ) {
 
-					}
+			this.rawMesh.counts.smoothingGroupCount++;
+			this.checkSubGroup();
 
-				}
-				if ( ! THREE.LoaderSupport.Validator.isValid( material ) ) {
-
-					var materialCloneInstructions = {
-						materialNameOrg: materialNameOrg,
-						materialName: materialName,
-						materialProperties: {
-							vertexColors: haveVertexColors ? 2 : 0,
-							flatShading: meshOutputGroup.smoothingGroup === 0
-						}
-					};
-					var payload = {
-						cmd: 'materialData',
-						materials: {
-							materialCloneInstructions: materialCloneInstructions
-						}
-					};
-					this.callbackMeshBuilder( payload );
+		}
+	},
 
-					// fake entry for async; sync Parser always works on material references (Builder update directly visible here)
-					if ( this.useAsync ) this.materials[ materialName ] = materialCloneInstructions;
+	/**
+	 * Expanded faceTypes include all four face types, both line types and the point type
+	 * faceType = 0: "f vertex ..."
+	 * faceType = 1: "f vertex/uv ..."
+	 * faceType = 2: "f vertex/uv/normal ..."
+	 * faceType = 3: "f vertex//normal ..."
+	 * faceType = 4: "l vertex/uv ..." or "l vertex ..."
+	 * faceType = 5: "l vertex ..."
+	 * faceType = 6: "p vertex ..."
+	 */
+	checkFaceType: function ( faceType ) {
+		if ( this.rawMesh.faceType !== faceType ) {
 
-				}
+			this.processCompletedMesh();
+			this.rawMesh.faceType = faceType;
+			this.checkSubGroup();
+
+		}
+	},
+
+	checkSubGroup: function () {
+		var index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized;
+		this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ];
+
+		if ( ! THREE.LoaderSupport.Validator.isValid( this.rawMesh.subGroupInUse ) ) {
+
+			this.rawMesh.subGroupInUse = {
+				index: index,
+				objectName: this.rawMesh.objectName,
+				groupName: this.rawMesh.groupName,
+				materialName: this.rawMesh.activeMtlName,
+				smoothingGroup: this.rawMesh.smoothingGroup.normalized,
+				vertices: [],
+				indexMappingsCount: 0,
+				indexMappings: [],
+				indices: [],
+				colors: [],
+				uvs: [],
+				normals: []
+			};
+			this.rawMesh.subGroups[ index ] = this.rawMesh.subGroupInUse;
+
+		}
+	},
 
-				if ( createMultiMaterial ) {
+	buildFace: function ( faceIndexV, faceIndexU, faceIndexN ) {
+		if ( this.disregardNormals ) faceIndexN = undefined;
+		var scope = this;
+		var updateSubGroupInUse = function () {
 
-					// re-use material if already used before. Reduces materials array size and eliminates duplicates
-					selectedMaterialIndex = materialIndexMapping[ materialName ];
-					if ( ! selectedMaterialIndex ) {
+			var faceIndexVi = parseInt( faceIndexV );
+			var indexPointerV = 3 * ( faceIndexVi > 0 ? faceIndexVi - 1 : faceIndexVi + scope.vertices.length / 3 );
+			var indexPointerC = scope.colors.length > 0 ? indexPointerV : null;
 
-						selectedMaterialIndex = materialIndex;
-						materialIndexMapping[ materialName ] = materialIndex;
-						materialNames.push( materialName );
-						materialIndex++;
+			var vertices = scope.rawMesh.subGroupInUse.vertices;
+			vertices.push( scope.vertices[ indexPointerV++ ] );
+			vertices.push( scope.vertices[ indexPointerV++ ] );
+			vertices.push( scope.vertices[ indexPointerV ] );
 
-					}
-					materialGroupLength = this.useIndices ? meshOutputGroup.indices.length : meshOutputGroup.vertices.length / 3;
-					materialGroup = {
-						start: materialGroupOffset,
-						count: materialGroupLength,
-						index: selectedMaterialIndex
-					};
-					materialGroups.push( materialGroup );
-					materialGroupOffset += materialGroupLength;
+			if ( indexPointerC !== null ) {
 
-				} else {
+				var colors = scope.rawMesh.subGroupInUse.colors;
+				colors.push( scope.colors[ indexPointerC++ ] );
+				colors.push( scope.colors[ indexPointerC++ ] );
+				colors.push( scope.colors[ indexPointerC ] );
 
-					materialNames.push( materialName );
+			}
+			if ( faceIndexU ) {
 
-				}
+				var faceIndexUi = parseInt( faceIndexU );
+				var indexPointerU = 2 * ( faceIndexUi > 0 ? faceIndexUi - 1 : faceIndexUi + scope.uvs.length / 2 );
+				var uvs = scope.rawMesh.subGroupInUse.uvs;
+				uvs.push( scope.uvs[ indexPointerU++ ] );
+				uvs.push( scope.uvs[ indexPointerU ] );
 
-				vertexFA.set( meshOutputGroup.vertices, vertexFAOffset );
-				vertexFAOffset += meshOutputGroup.vertices.length;
+			}
+			if ( faceIndexN ) {
 
-				if ( indexUA ) {
+				var faceIndexNi = parseInt( faceIndexN );
+				var indexPointerN = 3 * ( faceIndexNi > 0 ? faceIndexNi - 1 : faceIndexNi + scope.normals.length / 3 );
+				var normals = scope.rawMesh.subGroupInUse.normals;
+				normals.push( scope.normals[ indexPointerN++ ] );
+				normals.push( scope.normals[ indexPointerN++ ] );
+				normals.push( scope.normals[ indexPointerN ] );
 
-					indexUA.set( meshOutputGroup.indices, indexUAOffset );
-					indexUAOffset += meshOutputGroup.indices.length;
+			}
+		};
 
-				}
+		if ( this.useIndices ) {
 
-				if ( colorFA ) {
+			var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
+			var indicesPointer = this.rawMesh.subGroupInUse.indexMappings[ mappingName ];
+			if ( THREE.LoaderSupport.Validator.isValid( indicesPointer ) ) {
 
-					colorFA.set( meshOutputGroup.colors, colorFAOffset );
-					colorFAOffset += meshOutputGroup.colors.length;
+				this.rawMesh.counts.doubleIndicesCount++;
 
-				}
+			} else {
 
-				if ( normalFA ) {
+				indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3;
+				updateSubGroupInUse();
+				this.rawMesh.subGroupInUse.indexMappings[ mappingName ] = indicesPointer;
+				this.rawMesh.subGroupInUse.indexMappingsCount++;
 
-					normalFA.set( meshOutputGroup.normals, normalFAOffset );
-					normalFAOffset += meshOutputGroup.normals.length;
+			}
+			this.rawMesh.subGroupInUse.indices.push( indicesPointer );
 
-				}
-				if ( uvFA ) {
+		} else {
 
-					uvFA.set( meshOutputGroup.uvs, uvFAOffset );
-					uvFAOffset += meshOutputGroup.uvs.length;
+			updateSubGroupInUse();
 
-				}
+		}
+		this.rawMesh.counts.faceCount++;
+	},
+
+	createRawMeshReport: function ( inputObjectCount ) {
+		return 'Input Object number: ' + inputObjectCount +
+			'\n\tObject name: ' + this.rawMesh.objectName +
+			'\n\tGroup name: ' + this.rawMesh.groupName +
+			'\n\tMtllib name: ' + this.rawMesh.mtllibName +
+			'\n\tVertex count: ' + this.vertices.length / 3 +
+			'\n\tNormal count: ' + this.normals.length / 3 +
+			'\n\tUV count: ' + this.uvs.length / 2 +
+			'\n\tSmoothingGroup count: ' + this.rawMesh.counts.smoothingGroupCount +
+			'\n\tMaterial count: ' + this.rawMesh.counts.mtlCount +
+			'\n\tReal MeshOutputGroup count: ' + this.rawMesh.subGroups.length;
+	},
+
+	/**
+	 * Clear any empty subGroup and calculate absolute vertex, normal and uv counts
+	 */
+	finalizeRawMesh: function () {
+		var meshOutputGroupTemp = [];
+		var meshOutputGroup;
+		var absoluteVertexCount = 0;
+		var absoluteIndexMappingsCount = 0;
+		var absoluteIndexCount = 0;
+		var absoluteColorCount = 0;
+		var absoluteNormalCount = 0;
+		var absoluteUvCount = 0;
+		var indices;
+		for ( var name in this.rawMesh.subGroups ) {
+
+			meshOutputGroup = this.rawMesh.subGroups[ name ];
+			if ( meshOutputGroup.vertices.length > 0 ) {
+
+				indices = meshOutputGroup.indices;
+				if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {
+
+					for ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;
 
-				if ( this.logging.enabled && this.logging.debug ) {
-					var materialIndexLine = THREE.LoaderSupport.Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : '';
-					var createdReport = '\tOutput Object no.: ' + this.outputObjectCount +
-						'\n\t\tgroupName: ' + meshOutputGroup.groupName +
-						'\n\t\tIndex: ' + meshOutputGroup.index +
-						'\n\t\tfaceType: ' + this.rawMesh.faceType +
-						'\n\t\tmaterialName: ' + meshOutputGroup.materialName +
-						'\n\t\tsmoothingGroup: ' + meshOutputGroup.smoothingGroup +
-						materialIndexLine +
-						'\n\t\tobjectName: ' + meshOutputGroup.objectName +
-						'\n\t\t#vertices: ' + meshOutputGroup.vertices.length / 3 +
-						'\n\t\t#indices: ' + meshOutputGroup.indices.length +
-						'\n\t\t#colors: ' + meshOutputGroup.colors.length / 3 +
-						'\n\t\t#uvs: ' + meshOutputGroup.uvs.length / 2 +
-						'\n\t\t#normals: ' + meshOutputGroup.normals.length / 3;
-					console.debug( createdReport );
 				}
+				meshOutputGroupTemp.push( meshOutputGroup );
+				absoluteVertexCount += meshOutputGroup.vertices.length;
+				absoluteIndexMappingsCount += meshOutputGroup.indexMappingsCount;
+				absoluteIndexCount += meshOutputGroup.indices.length;
+				absoluteColorCount += meshOutputGroup.colors.length;
+				absoluteUvCount += meshOutputGroup.uvs.length;
+				absoluteNormalCount += meshOutputGroup.normals.length;
 
 			}
+		}
 
-			this.outputObjectCount++;
-			this.callbackMeshBuilder(
-				{
-					cmd: 'meshData',
-					progress: {
-						numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes
-					},
-					params: {
-						meshName: result.name
-					},
-					materials: {
-						multiMaterial: createMultiMaterial,
-						materialNames: materialNames,
-						materialGroups: materialGroups
-					},
-					buffers: {
-						vertices: vertexFA,
-						indices: indexUA,
-						colors: colorFA,
-						normals: normalFA,
-						uvs: uvFA
-					},
-					// 0: mesh, 1: line, 2: point
-					geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1
-				},
-				[ vertexFA.buffer ],
-				THREE.LoaderSupport.Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
-				THREE.LoaderSupport.Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
-				THREE.LoaderSupport.Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
-				THREE.LoaderSupport.Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
-			);
-		};
+		// do not continue if no result
+		var result = null;
+		if ( meshOutputGroupTemp.length > 0 ) {
+
+			result = {
+				name: this.rawMesh.groupName !== '' ? this.rawMesh.groupName : this.rawMesh.objectName,
+				subGroups: meshOutputGroupTemp,
+				absoluteVertexCount: absoluteVertexCount,
+				absoluteIndexCount: absoluteIndexCount,
+				absoluteColorCount: absoluteColorCount,
+				absoluteNormalCount: absoluteNormalCount,
+				absoluteUvCount: absoluteUvCount,
+				faceCount: this.rawMesh.counts.faceCount,
+				doubleIndicesCount: this.rawMesh.counts.doubleIndicesCount
+			};
+
+		}
+		return result;
+	},
 
-		Parser.prototype.finalizeParsing = function () {
-			if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.outputObjectCount );
-			if ( this.processCompletedMesh() && this.logging.enabled ) {
+	processCompletedMesh: function () {
+		var result = this.finalizeRawMesh();
+		if ( THREE.LoaderSupport.Validator.isValid( result ) ) {
 
-				var parserFinalReport = 'Overall counts: ' +
-					'\n\tVertices: ' + this.globalCounts.vertices +
-					'\n\tFaces: ' + this.globalCounts.faces +
-					'\n\tMultiple definitions: ' + this.globalCounts.doubleIndicesCount;
-				console.info( parserFinalReport );
+			if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) {
+
+				this._throwError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
 
 			}
-		};
+			if ( this.logging.enabled && this.logging.debug ) console.debug( this.createRawMeshReport( this.inputObjectCount ) );
+			this.inputObjectCount++;
 
-		return Parser;
-	})();
+			this.buildMesh( result );
+			var progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes;
+			this.callbackProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
+			this.resetRawMesh();
+			return true;
+
+		} else {
+
+			return false;
+		}
+	},
 
 	/**
-	 * Utility method for loading an mtl file according resource description. Provide url or content.
-	 * @memberOf THREE.OBJLoader2
+	 * SubGroups are transformed to too intermediate format that is forwarded to the MeshBuilder.
+	 * It is ensured that SubGroups only contain objects with vertices (no need to check).
 	 *
-	 * @param {string} url URL to the file
-	 * @param {Object} content The file content as arraybuffer or text
-	 * @param {function} onLoad Callback to be called after successful load
-	 * @param {callback} [onProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes.
-	 * @param {callback} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument.
-	 * @param {string} [crossOrigin] CORS value
- 	 * @param {Object} [materialOptions] Set material loading options for MTLLoader
+	 * @param result
 	 */
-	OBJLoader2.prototype.loadMtl = function ( url, content, onLoad, onProgress, onError, crossOrigin, materialOptions ) {
-		var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
-		resource.setContent( content );
-		this._loadMtl( resource, onLoad, onProgress, onError, crossOrigin, materialOptions );
-	};
+	buildMesh: function ( result ) {
+		var meshOutputGroups = result.subGroups;
+
+		var vertexFA = new Float32Array( result.absoluteVertexCount );
+		this.globalCounts.vertices += result.absoluteVertexCount / 3;
+		this.globalCounts.faces += result.faceCount;
+		this.globalCounts.doubleIndicesCount += result.doubleIndicesCount;
+		var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
+		var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
+		var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
+		var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
+		var haveVertexColors = THREE.LoaderSupport.Validator.isValid( colorFA );
+
+		var meshOutputGroup;
+		var materialNames = [];
+
+		var createMultiMaterial = ( meshOutputGroups.length > 1 );
+		var materialIndex = 0;
+		var materialIndexMapping = [];
+		var selectedMaterialIndex;
+		var materialGroup;
+		var materialGroups = [];
+
+		var vertexFAOffset = 0;
+		var indexUAOffset = 0;
+		var colorFAOffset = 0;
+		var normalFAOffset = 0;
+		var uvFAOffset = 0;
+		var materialGroupOffset = 0;
+		var materialGroupLength = 0;
+
+		var materialOrg, material, materialName, materialNameOrg;
+		// only one specific face type
+		for ( var oodIndex in meshOutputGroups ) {
+
+			if ( ! meshOutputGroups.hasOwnProperty( oodIndex ) ) continue;
+			meshOutputGroup = meshOutputGroups[ oodIndex ];
+
+			materialNameOrg = meshOutputGroup.materialName;
+			if ( this.rawMesh.faceType < 4 ) {
+
+				materialName = materialNameOrg + ( haveVertexColors ? '_vertexColor' : '' ) + ( meshOutputGroup.smoothingGroup === 0 ? '_flat' : '' );
 
 
-	OBJLoader2.prototype._loadMtl = function ( resource, onLoad, onProgress, onError, crossOrigin, materialOptions ) {
-		if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' );
-		if ( Validator.isValid( resource ) && this.logging.enabled ) console.time( 'Loading MTL: ' + resource.name );
+			} else {
 
-		var materials = [];
-		var scope = this;
-		var processMaterials = function ( materialCreator ) {
-			var materialCreatorMaterials = [];
-			if ( Validator.isValid( materialCreator ) ) {
+				materialName = this.rawMesh.faceType === 6 ? 'defaultPointMaterial' : 'defaultLineMaterial';
 
-				materialCreator.preload();
-				materialCreatorMaterials = materialCreator.materials;
-				for ( var materialName in materialCreatorMaterials ) {
+			}
+			materialOrg = this.materials[ materialNameOrg ];
+			material = this.materials[ materialName ];
 
-					if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
+			// both original and derived names do not lead to an existing material => need to use a default material
+			if ( ! THREE.LoaderSupport.Validator.isValid( materialOrg ) && ! THREE.LoaderSupport.Validator.isValid( material ) ) {
 
-						materials[ materialName ] = materialCreatorMaterials[ materialName ];
+				var defaultMaterialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
+				materialOrg = this.materials[ defaultMaterialName ];
+				if ( this.logging.enabled ) console.warn( 'object_group "' + meshOutputGroup.objectName + '_' +
+					meshOutputGroup.groupName + '" was defined with unresolvable material "' +
+					materialNameOrg + '"! Assigning "' + defaultMaterialName + '".' );
+				materialNameOrg = defaultMaterialName;
+
+				// if names are identical then there is no need for later manipulation
+				if ( materialNameOrg === materialName ) {
+
+					material = materialOrg;
+					materialName = defaultMaterialName;
 
-					}
 				}
+
 			}
+			if ( ! THREE.LoaderSupport.Validator.isValid( material ) ) {
+
+				var materialCloneInstructions = {
+					materialNameOrg: materialNameOrg,
+					materialName: materialName,
+					materialProperties: {
+						vertexColors: haveVertexColors ? 2 : 0,
+						flatShading: meshOutputGroup.smoothingGroup === 0
+					}
+				};
+				var payload = {
+					cmd: 'materialData',
+					materials: {
+						materialCloneInstructions: materialCloneInstructions
+					}
+				};
+				this.callbackMeshBuilder( payload );
 
-			if ( Validator.isValid( resource ) && scope.logging.enabled ) console.timeEnd( 'Loading MTL: ' + resource.name );
-			onLoad( materials, materialCreator );
-		};
+				// fake entry for async; sync Parser always works on material references (Builder update directly visible here)
+				if ( this.useAsync ) this.materials[ materialName ] = materialCloneInstructions;
 
-		// fast-fail
-		if ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {
+			}
 
-			processMaterials();
+			if ( createMultiMaterial ) {
 
-		} else {
+				// re-use material if already used before. Reduces materials array size and eliminates duplicates
+				selectedMaterialIndex = materialIndexMapping[ materialName ];
+				if ( ! selectedMaterialIndex ) {
 
-			var mtlLoader = new THREE.MTLLoader( this.manager );
-			crossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );
-			mtlLoader.setCrossOrigin( crossOrigin );
-			mtlLoader.setResourcePath( resource.path );
-			if ( Validator.isValid( materialOptions ) ) mtlLoader.setMaterialOptions( materialOptions );
+					selectedMaterialIndex = materialIndex;
+					materialIndexMapping[ materialName ] = materialIndex;
+					materialNames.push( materialName );
+					materialIndex++;
 
-			var parseTextWithMtlLoader = function ( content ) {
-				var contentAsText = content;
-				if ( typeof( content ) !== 'string' && ! ( content instanceof String ) ) {
+				}
+				materialGroupLength = this.useIndices ? meshOutputGroup.indices.length : meshOutputGroup.vertices.length / 3;
+				materialGroup = {
+					start: materialGroupOffset,
+					count: materialGroupLength,
+					index: selectedMaterialIndex
+				};
+				materialGroups.push( materialGroup );
+				materialGroupOffset += materialGroupLength;
 
-					if ( content.length > 0 || content.byteLength > 0 ) {
+			} else {
 
-						contentAsText = THREE.LoaderUtils.decodeText( content );
+				materialNames.push( materialName );
 
-					} else {
+			}
 
-						this._throwError( 'Unable to parse mtl as it it seems to be neither a String, an Array or an ArrayBuffer!' );
-					}
+			vertexFA.set( meshOutputGroup.vertices, vertexFAOffset );
+			vertexFAOffset += meshOutputGroup.vertices.length;
 
-				}
-				processMaterials( mtlLoader.parse( contentAsText ) );
-			};
+			if ( indexUA ) {
 
-			if ( Validator.isValid( resource.content ) ) {
+				indexUA.set( meshOutputGroup.indices, indexUAOffset );
+				indexUAOffset += meshOutputGroup.indices.length;
 
-				parseTextWithMtlLoader( resource.content );
+			}
 
-			} else if ( Validator.isValid( resource.url ) ) {
+			if ( colorFA ) {
 
-				var fileLoader = new THREE.FileLoader( this.manager );
-				if ( ! Validator.isValid( onError ) ) {
-					onError = function ( event ) {
-						scope._onError( event );
-					};
-				}
-				if ( ! Validator.isValid( onProgress ) ) {
-					var numericalValueRef = 0;
-					var numericalValue = 0;
-					onProgress = function ( event ) {
-						if ( ! event.lengthComputable ) return;
+				colorFA.set( meshOutputGroup.colors, colorFAOffset );
+				colorFAOffset += meshOutputGroup.colors.length;
 
-						numericalValue = event.loaded / event.total;
-						if ( numericalValue > numericalValueRef ) {
+			}
 
-							numericalValueRef = numericalValue;
-							var output = 'Download of "' + resource.url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
-							scope.onProgress( 'progressLoad', output, numericalValue );
+			if ( normalFA ) {
 
-						}
-					};
-				}
+				normalFA.set( meshOutputGroup.normals, normalFAOffset );
+				normalFAOffset += meshOutputGroup.normals.length;
 
-				fileLoader.load( resource.url, parseTextWithMtlLoader, onProgress, onError );
+			}
+			if ( uvFA ) {
+
+				uvFA.set( meshOutputGroup.uvs, uvFAOffset );
+				uvFAOffset += meshOutputGroup.uvs.length;
 
 			}
+
+			if ( this.logging.enabled && this.logging.debug ) {
+				var materialIndexLine = THREE.LoaderSupport.Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : '';
+				var createdReport = '\tOutput Object no.: ' + this.outputObjectCount +
+					'\n\t\tgroupName: ' + meshOutputGroup.groupName +
+					'\n\t\tIndex: ' + meshOutputGroup.index +
+					'\n\t\tfaceType: ' + this.rawMesh.faceType +
+					'\n\t\tmaterialName: ' + meshOutputGroup.materialName +
+					'\n\t\tsmoothingGroup: ' + meshOutputGroup.smoothingGroup +
+					materialIndexLine +
+					'\n\t\tobjectName: ' + meshOutputGroup.objectName +
+					'\n\t\t#vertices: ' + meshOutputGroup.vertices.length / 3 +
+					'\n\t\t#indices: ' + meshOutputGroup.indices.length +
+					'\n\t\t#colors: ' + meshOutputGroup.colors.length / 3 +
+					'\n\t\t#uvs: ' + meshOutputGroup.uvs.length / 2 +
+					'\n\t\t#normals: ' + meshOutputGroup.normals.length / 3;
+				console.debug( createdReport );
+			}
+
 		}
-	};
 
-	return OBJLoader2;
-})();
+		this.outputObjectCount++;
+		this.callbackMeshBuilder(
+			{
+				cmd: 'meshData',
+				progress: {
+					numericalValue: this.globalCounts.currentByte / this.globalCounts.totalBytes
+				},
+				params: {
+					meshName: result.name
+				},
+				materials: {
+					multiMaterial: createMultiMaterial,
+					materialNames: materialNames,
+					materialGroups: materialGroups
+				},
+				buffers: {
+					vertices: vertexFA,
+					indices: indexUA,
+					colors: colorFA,
+					normals: normalFA,
+					uvs: uvFA
+				},
+				// 0: mesh, 1: line, 2: point
+				geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1
+			},
+			[ vertexFA.buffer ],
+			THREE.LoaderSupport.Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
+			THREE.LoaderSupport.Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
+			THREE.LoaderSupport.Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
+			THREE.LoaderSupport.Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
+		);
+	},
+
+	finalizeParsing: function () {
+		if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.outputObjectCount );
+		if ( this.processCompletedMesh() && this.logging.enabled ) {
+
+			var parserFinalReport = 'Overall counts: ' +
+				'\n\tVertices: ' + this.globalCounts.vertices +
+				'\n\tFaces: ' + this.globalCounts.faces +
+				'\n\tMultiple definitions: ' + this.globalCounts.doubleIndicesCount;
+			console.info( parserFinalReport );
+
+		}
+	}
+};

+ 40 - 68
examples/webgl_loader_obj2.html

@@ -83,34 +83,31 @@
 
 			'use strict';
 
-			var OBJLoader2Example = ( function () {
-
-				var Validator = THREE.LoaderSupport.Validator;
-
-				function OBJLoader2Example( elementToBindTo ) {
-
-					this.renderer = null;
-					this.canvas = elementToBindTo;
-					this.aspectRatio = 1;
-					this.recalcAspectRatio();
-
-					this.scene = null;
-					this.cameraDefaults = {
-						posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
-						posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
-						near: 0.1,
-						far: 10000,
-						fov: 45
-					};
-					this.camera = null;
-					this.cameraTarget = this.cameraDefaults.posCameraTarget;
+			var OBJLoader2Example = function ( elementToBindTo ) {
+				this.renderer = null;
+				this.canvas = elementToBindTo;
+				this.aspectRatio = 1;
+				this.recalcAspectRatio();
+
+				this.scene = null;
+				this.cameraDefaults = {
+					posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
+					posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
+					near: 0.1,
+					far: 10000,
+					fov: 45
+				};
+				this.camera = null;
+				this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
-					this.controls = null;
+				this.controls = null;
+			};
 
-				}
+			OBJLoader2Example.prototype = {
 
-				OBJLoader2Example.prototype.initGL = function () {
+				constructor: OBJLoader2Example,
 
+				initGL: function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
 						antialias: true,
@@ -128,8 +125,8 @@
 					var directionalLight1 = new THREE.DirectionalLight( 0xC0C090 );
 					var directionalLight2 = new THREE.DirectionalLight( 0xC0C090 );
 
-					directionalLight1.position.set( - 100, - 50, 100 );
-					directionalLight2.position.set( 100, 50, - 100 );
+					directionalLight1.position.set( -100, -50, 100 );
+					directionalLight2.position.set( 100, 50, -100 );
 
 					this.scene.add( directionalLight1 );
 					this.scene.add( directionalLight2 );
@@ -137,103 +134,78 @@
 
 					var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
 					this.scene.add( helper );
+				},
 
-				};
-
-				OBJLoader2Example.prototype.initContent = function () {
-
+				initContent: function () {
 					var modelName = 'female02';
 					this._reportProgress( { detail: { text: 'Loading: ' + modelName } } );
 
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var callbackOnLoad = function ( event ) {
-
 						scope.scene.add( event.detail.loaderRootNode );
 						console.log( 'Loading complete: ' + event.detail.modelName );
 						scope._reportProgress( { detail: { text: '' } } );
-
 					};
 
 					var onLoadMtl = function ( materials ) {
-
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
 						objLoader.setLogging( true, true );
 						objLoader.load( 'models/obj/female02/female02.obj', callbackOnLoad, null, null, null, false );
-
 					};
 					objLoader.loadMtl( 'models/obj/female02/female02.mtl', null, onLoadMtl );
+				},
 
-				};
-
-				OBJLoader2Example.prototype._reportProgress = function ( event ) {
-
-					var output = Validator.verifyInput( event.detail.text, '' );
+				_reportProgress: function( event ) {
+					var output = THREE.LoaderSupport.Validator.verifyInput( event.detail.text, '' );
 					console.log( 'Progress: ' + output );
 					document.getElementById( 'feedback' ).innerHTML = output;
+				},
 
-				};
-
-				OBJLoader2Example.prototype.resizeDisplayGL = function () {
-
+				resizeDisplayGL: function () {
 					this.controls.handleResize();
 
 					this.recalcAspectRatio();
 					this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
 
 					this.updateCamera();
+				},
 
-				};
-
-				OBJLoader2Example.prototype.recalcAspectRatio = function () {
-
+				recalcAspectRatio: function () {
 					this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
+				},
 
-				};
-
-				OBJLoader2Example.prototype.resetCamera = function () {
-
+				resetCamera: function () {
 					this.camera.position.copy( this.cameraDefaults.posCamera );
 					this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
 
 					this.updateCamera();
+				},
 
-				};
-
-				OBJLoader2Example.prototype.updateCamera = function () {
-
+				updateCamera: function () {
 					this.camera.aspect = this.aspectRatio;
 					this.camera.lookAt( this.cameraTarget );
 					this.camera.updateProjectionMatrix();
+				},
 
-				};
-
-				OBJLoader2Example.prototype.render = function () {
-
+				render: function () {
 					if ( ! this.renderer.autoClear ) this.renderer.clear();
 					this.controls.update();
 					this.renderer.render( this.scene, this.camera );
+				}
 
-				};
-
-				return OBJLoader2Example;
-
-			} )();
+			};
 
 			var app = new OBJLoader2Example( document.getElementById( 'example' ) );
 
 			var resizeWindow = function () {
-
 				app.resizeDisplayGL();
-
 			};
 
 			var render = function () {
-
 				requestAnimationFrame( render );
 				app.render();
-
 			};
 
 			window.addEventListener( 'resize', resizeWindow, false );

+ 210 - 268
examples/webgl_loader_obj2_meshspray.html

@@ -82,46 +82,42 @@
 
 			'use strict';
 
-			var MeshSpray = ( function () {
+			var MeshSpray = {};
 
-				var Validator = THREE.LoaderSupport.Validator;
-
-				function MeshSpray( manager ) {
-
-					this.manager = Validator.verifyInput( manager, THREE.DefaultLoadingManager );
-					this.logging = {
-						enabled: true,
-						debug: false
-					};
+			MeshSpray.Loader = function ( manager ) {
+				this.manager = THREE.LoaderSupport.Validator.verifyInput( manager, THREE.DefaultLoadingManager );
+				this.logging = {
+					enabled: true,
+					debug: false
+				};
 
-					this.instanceNo = 0;
-					this.loaderRootNode = new THREE.Group();
+				this.instanceNo = 0;
+				this.loaderRootNode = new THREE.Group();
 
-					this.meshBuilder = new THREE.LoaderSupport.MeshBuilder();
-					this.callbacks = new THREE.LoaderSupport.Callbacks();
-					this.workerSupport = null;
+				this.meshBuilder = new THREE.LoaderSupport.MeshBuilder();
+				this.callbacks = new THREE.LoaderSupport.Callbacks();
+				this.workerSupport = null;
+			};
 
-				}
+			MeshSpray.Loader.prototype = {
 
-				MeshSpray.prototype.setLogging = function ( enabled, debug ) {
+				constructor: MeshSpray.Loader,
 
+				setLogging: function ( enabled, debug ) {
 					this.logging.enabled = enabled === true;
 					this.logging.debug = debug === true;
 					this.meshBuilder.setLogging( this.logging.enabled, this.logging.debug );
+				},
 
-				};
-
-				MeshSpray.prototype.setStreamMeshesTo = function ( streamMeshesTo ) {
+				setStreamMeshesTo: function ( streamMeshesTo ) {
+					this.loaderRootNode = THREE.LoaderSupport.Validator.verifyInput( streamMeshesTo, this.loaderRootNode );
+				},
 
-					this.loaderRootNode = Validator.verifyInput( streamMeshesTo, this.loaderRootNode );
-
-				};
-
-				MeshSpray.prototype.setForceWorkerDataCopy = function () {
+				setForceWorkerDataCopy: function ( forceWorkerDataCopy ) {
 					// nothing to do here
-				};
+				},
 
-				MeshSpray.prototype.run = function ( prepData, workerSupportExternal ) {
+				run: function ( prepData, workerSupportExternal ) {
 
 					if ( THREE.LoaderSupport.Validator.isValid( workerSupportExternal ) ) {
 
@@ -142,19 +138,14 @@
 
 					var scope = this;
 					var scopeBuilderFunc = function ( payload ) {
-
 						var meshes = scope.meshBuilder.processPayload( payload );
 						var mesh;
 						for ( var i in meshes ) {
-
 							mesh = meshes[ i ];
 							scope.loaderRootNode.add( mesh );
-
 						}
-
 					};
-					var scopeFuncComplete = function () {
-
+					var scopeFuncComplete = function ( message ) {
 						var callback = scope.callbacks.onLoad;
 						if ( THREE.LoaderSupport.Validator.isValid( callback ) ) callback(
 							{
@@ -166,24 +157,22 @@
 							}
 						);
 						if ( scope.logging.enabled ) console.timeEnd( 'MeshSpray' + scope.instanceNo );
-
 					};
 
-					var buildCode = function ( funcBuildObject, funcBuildSingleton ) {
-
+					var buildCode = function ( codeSerializer ) {
 						var workerCode = '';
 						workerCode += '/**\n';
 						workerCode += '  * This code was constructed by MeshSpray buildCode.\n';
 						workerCode += '  */\n\n';
 						workerCode += 'THREE.LoaderSupport = {};\n\n';
-						workerCode += funcBuildObject( 'THREE.LoaderSupport.Validator', THREE.LoaderSupport.Validator );
-						workerCode += funcBuildSingleton( 'Parser', Parser );
+						workerCode += 'MeshSpray = {};\n\n';
+						workerCode += codeSerializer.serializeObject( 'THREE.LoaderSupport.Validator', THREE.LoaderSupport.Validator );
+						workerCode += codeSerializer.serializeClass( 'MeshSpray.Parser', MeshSpray.Parser );
 
 						return workerCode;
-
 					};
 					var libs2Load = [ 'build/three.min.js' ];
-					this.workerSupport.validate( buildCode, 'Parser', libs2Load, '../' );
+					this.workerSupport.validate( buildCode, 'MeshSpray.Parser', libs2Load, '../../' );
 					this.workerSupport.setCallbacks( scopeBuilderFunc, scopeFuncComplete );
 					this.workerSupport.run(
 						{
@@ -205,12 +194,10 @@
 							}
 						}
 					);
+				},
 
-				};
-
-				MeshSpray.prototype._applyPrepData = function ( prepData ) {
-
-					if ( Validator.isValid( prepData ) ) {
+				_applyPrepData: function ( prepData ) {
+					if ( THREE.LoaderSupport.Validator.isValid( prepData ) ) {
 
 						this.setLogging( prepData.logging.enabled, prepData.logging.debug );
 						this.setStreamMeshesTo( prepData.streamMeshesTo );
@@ -218,219 +205,201 @@
 						this._setCallbacks( prepData.getCallbacks() );
 
 					}
+				},
 
-				};
-
-				MeshSpray.prototype._setCallbacks = function ( callbacks ) {
-
-					if ( Validator.isValid( callbacks.onProgress ) ) this.callbacks.setCallbackOnProgress( callbacks.onProgress );
-					if ( Validator.isValid( callbacks.onMeshAlter ) ) this.callbacks.setCallbackOnMeshAlter( callbacks.onMeshAlter );
-					if ( Validator.isValid( callbacks.onLoad ) ) this.callbacks.setCallbackOnLoad( callbacks.onLoad );
-					if ( Validator.isValid( callbacks.onLoadMaterials ) ) this.callbacks.setCallbackOnLoadMaterials( callbacks.onLoadMaterials );
+				_setCallbacks: function ( callbacks ) {
+					if ( THREE.LoaderSupport.Validator.isValid( callbacks.onProgress ) ) this.callbacks.setCallbackOnProgress( callbacks.onProgress );
+					if ( THREE.LoaderSupport.Validator.isValid( callbacks.onReportError ) ) this.callbacks.setCallbackOnReportError( callbacks.onReportError );
+					if ( THREE.LoaderSupport.Validator.isValid( callbacks.onMeshAlter ) ) this.callbacks.setCallbackOnMeshAlter( callbacks.onMeshAlter );
+					if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoad ) ) this.callbacks.setCallbackOnLoad( callbacks.onLoad );
+					if ( THREE.LoaderSupport.Validator.isValid( callbacks.onLoadMaterials ) ) this.callbacks.setCallbackOnLoadMaterials( callbacks.onLoadMaterials );
 
 					this.meshBuilder._setCallbacks( this.callbacks );
+				}
+			};
 
-				};
-
-				var Parser = ( function () {
-
-					function Parser() {
-
-						this.sizeFactor = 0.5;
-						this.localOffsetFactor = 1.0;
-						this.globalObjectCount = 0;
-						this.debug = false;
-						this.dimension = 200;
-						this.quantity = 1;
-						this.callbackMeshBuilder = null;
-						this.callbackProgress = null;
-						this.serializedMaterials = null;
-						this.logging = {
-							enabled: true,
-							debug: false
-						};
-
-					}
-
-					Parser.prototype.setLogging = function ( enabled, debug ) {
-
-						this.logging.enabled = enabled === true;
-						this.logging.debug = debug === true;
-
-					};
-
-					Parser.prototype.parse = function () {
-
-						var baseTriangle = [ 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 0.0, - 1.0, 1.0 ];
-						var vertices = [];
-						var colors = [];
-						var normals = [];
-						var uvs = [];
-
-						var dimensionHalf = this.dimension / 2;
-						var fixedOffsetX;
-						var fixedOffsetY;
-						var fixedOffsetZ;
-						var s, t;
-						// complete triangle
-						// local coords offset
-						var localOffsetFactor = this.localOffsetFactor;
-
-						for ( var i = 0; i < this.quantity; i ++ ) {
 
-							var sizeVaring = this.sizeFactor * Math.random();
+			MeshSpray.Parser = function () {
+				this.sizeFactor = 0.5;
+				this.localOffsetFactor = 1.0;
+				this.globalObjectCount = 0;
+				this.debug = false;
+				this.dimension = 200;
+				this.quantity = 1;
+				this.callbackMeshBuilder = null;
+				this.callbackProgress = null;
+				this.serializedMaterials = null;
+				this.logging = {
+					enabled: true,
+					debug: false
+				};
+			};
 
-							s = 2 * Math.PI * Math.random();
-							t = Math.PI * Math.random();
+			MeshSpray.Parser.prototype = {
 
-							fixedOffsetX = dimensionHalf * Math.random() * Math.cos( s ) * Math.sin( t );
-							fixedOffsetY = dimensionHalf * Math.random() * Math.sin( s ) * Math.sin( t );
-							fixedOffsetZ = dimensionHalf * Math.random() * Math.cos( t );
-							for ( var j = 0; j < baseTriangle.length; j += 3 ) {
+				constructor: MeshSpray.Parser,
 
-								vertices.push( baseTriangle[ j ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetX );
-								vertices.push( baseTriangle[ j + 1 ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetY );
-								vertices.push( baseTriangle[ j + 2 ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetZ );
-								colors.push( Math.random() );
-								colors.push( Math.random() );
-								colors.push( Math.random() );
+				setLogging: function ( enabled, debug ) {
+					this.logging.enabled = enabled === true;
+					this.logging.debug = debug === true;
+				},
+
+				parse: function () {
+					var baseTriangle = [ 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 1.0 ];
+					var vertices = [];
+					var colors = [];
+					var normals = [];
+					var uvs = [];
+
+					var dimensionHalf = this.dimension / 2;
+					var fixedOffsetX;
+					var fixedOffsetY;
+					var fixedOffsetZ;
+					var s, t;
+					// complete triangle
+					var sizeVaring = this.sizeFactor * Math.random();
+					// local coords offset
+					var localOffsetFactor = this.localOffsetFactor;
+
+					for ( var i = 0; i < this.quantity; i++ ) {
+						sizeVaring = this.sizeFactor * Math.random();
 
-							}
+						s = 2 * Math.PI * Math.random();
+						t = Math.PI * Math.random();
 
+						fixedOffsetX = dimensionHalf * Math.random() * Math.cos( s ) * Math.sin( t );
+						fixedOffsetY = dimensionHalf * Math.random() * Math.sin( s ) * Math.sin( t );
+						fixedOffsetZ = dimensionHalf * Math.random() * Math.cos( t );
+						for ( var j = 0; j < baseTriangle.length; j += 3 ) {
+							vertices.push( baseTriangle[ j ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetX );
+							vertices.push( baseTriangle[ j + 1 ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetY );
+							vertices.push( baseTriangle[ j + 2 ] * sizeVaring + localOffsetFactor * Math.random() + fixedOffsetZ );
+							colors.push( Math.random() );
+							colors.push( Math.random() );
+							colors.push( Math.random() );
 						}
+					}
 
-						var absoluteVertexCount = vertices.length;
-						var absoluteColorCount = colors.length;
-						var absoluteNormalCount = 0;
-						var absoluteUvCount = 0;
+					var absoluteVertexCount = vertices.length;
+					var absoluteColorCount = colors.length;
+					var absoluteNormalCount = 0;
+					var absoluteUvCount = 0;
 
-						var vertexFA = new Float32Array( absoluteVertexCount );
-						var colorFA = ( absoluteColorCount > 0 ) ? new Float32Array( absoluteColorCount ) : null;
-						var normalFA = ( absoluteNormalCount > 0 ) ? new Float32Array( absoluteNormalCount ) : null;
-						var uvFA = ( absoluteUvCount > 0 ) ? new Float32Array( absoluteUvCount ) : null;
+					var vertexFA = new Float32Array( absoluteVertexCount );
+					var colorFA = ( absoluteColorCount > 0 ) ? new Float32Array( absoluteColorCount ) : null;
+					var normalFA = ( absoluteNormalCount > 0 ) ? new Float32Array( absoluteNormalCount ) : null;
+					var uvFA = ( absoluteUvCount > 0 ) ? new Float32Array( absoluteUvCount ) : null;
 
-						vertexFA.set( vertices, 0 );
-						if ( colorFA ) {
+					vertexFA.set( vertices, 0 );
+					if ( colorFA ) {
 
-							colorFA.set( colors, 0 );
+						colorFA.set( colors, 0 );
 
-						}
+					}
 
-						if ( normalFA ) {
+					if ( normalFA ) {
 
-							normalFA.set( normals, 0 );
+						normalFA.set( normals, 0 );
 
-						}
-						if ( uvFA ) {
+					}
+					if ( uvFA ) {
 
-							uvFA.set( uvs, 0 );
+						uvFA.set( uvs, 0 );
 
+					}
+
+					/*
+					 * This demonstrates the usage of embedded three.js in the worker blob and
+					 * the serialization of materials back to the Builder outside the worker.
+					 *
+					 * This is not the most effective way, but outlining possibilities
+					 */
+					var materialName = 'defaultVertexColorMaterial_double';
+					var defaultVertexColorMaterialJson = this.serializedMaterials[ 'defaultVertexColorMaterial' ];
+					var loader = new THREE.MaterialLoader();
+
+					var defaultVertexColorMaterialDouble = loader.parse( defaultVertexColorMaterialJson );
+					defaultVertexColorMaterialDouble.name = materialName;
+					defaultVertexColorMaterialDouble.side = THREE.DoubleSide;
+
+					var newSerializedMaterials = {};
+					newSerializedMaterials[ materialName ] = defaultVertexColorMaterialDouble.toJSON();
+					var payload = {
+						cmd: 'materialData',
+						materials: {
+							serializedMaterials: newSerializedMaterials
 						}
+					};
+					this.callbackMeshBuilder( payload );
 
-						/*
-						 * This demonstrates the usage of embedded three.js in the worker blob and
-						 * the serialization of materials back to the Builder outside the worker.
-						 *
-						 * This is not the most effective way, but outlining possibilities
-						 */
-						var materialName = 'defaultVertexColorMaterial_double';
-						var defaultVertexColorMaterialJson = this.serializedMaterials[ 'defaultVertexColorMaterial' ];
-						var loader = new THREE.MaterialLoader();
-
-						var defaultVertexColorMaterialDouble = loader.parse( defaultVertexColorMaterialJson );
-						defaultVertexColorMaterialDouble.name = materialName;
-						defaultVertexColorMaterialDouble.side = THREE.DoubleSide;
-
-						var newSerializedMaterials = {};
-						newSerializedMaterials[ materialName ] = defaultVertexColorMaterialDouble.toJSON();
-						var payload = {
-							cmd: 'materialData',
+					this.globalObjectCount++;
+					this.callbackMeshBuilder(
+						{
+							cmd: 'meshData',
+							progress: {
+								numericalValue: 1.0
+							},
+							params: {
+								meshName: 'Gen' + this.globalObjectCount
+							},
 							materials: {
-								serializedMaterials: newSerializedMaterials
-							}
-						};
-						this.callbackMeshBuilder( payload );
-
-						this.globalObjectCount ++;
-						this.callbackMeshBuilder(
-							{
-								cmd: 'meshData',
-								progress: {
-									numericalValue: 1.0
-								},
-								params: {
-									meshName: 'Gen' + this.globalObjectCount
-								},
-								materials: {
-									multiMaterial: false,
-									materialNames: [ materialName ],
-									materialGroups: []
-								},
-								buffers: {
-									vertices: vertexFA,
-									colors: colorFA,
-									normals: normalFA,
-									uvs: uvFA
-								}
+								multiMaterial: false,
+								materialNames: [ materialName ],
+								materialGroups: []
 							},
-							[ vertexFA.buffer ],
-							colorFA !== null ? [ colorFA.buffer ] : null,
-							normalFA !== null ? [ normalFA.buffer ] : null,
-							uvFA !== null ? [ uvFA.buffer ] : null
-						);
-
-						if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.globalObjectCount );
-
-					};
-
-					return Parser;
-
-				} )();
-
+							buffers: {
+								vertices: vertexFA,
+								colors: colorFA,
+								normals: normalFA,
+								uvs: uvFA
+							}
+						},
+						[ vertexFA.buffer ],
+						colorFA !== null ? [ colorFA.buffer ] : null,
+						normalFA !== null ? [ normalFA.buffer ] : null,
+						uvFA !== null ? [ uvFA.buffer ] : null
+					);
 
-				Parser.prototype.setSerializedMaterials = function ( serializedMaterials ) {
+					if ( this.logging.enabled ) console.info( 'Global output object count: ' + this.globalObjectCount );
+				},
 
+				setSerializedMaterials: function ( serializedMaterials ) {
 					if ( THREE.LoaderSupport.Validator.isValid( serializedMaterials ) ) {
 
 						this.serializedMaterials = serializedMaterials;
 
 					}
+				}
+			};
 
+			var MeshSprayApp = function ( elementToBindTo ) {
+				this.renderer = null;
+				this.canvas = elementToBindTo;
+				this.aspectRatio = 1;
+				this.recalcAspectRatio();
+
+				this.scene = null;
+				this.cameraDefaults = {
+					posCamera: new THREE.Vector3( 500.0, 500.0, 1000.0 ),
+					posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
+					near: 0.1,
+					far: 10000,
+					fov: 45
 				};
+				this.camera = null;
+				this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
-				return MeshSpray;
-
-			} )();
-
-			var MeshSprayApp = ( function () {
+				this.controls = null;
 
-				function MeshSprayApp( elementToBindTo ) {
-
-					this.renderer = null;
-					this.canvas = elementToBindTo;
-					this.aspectRatio = 1;
-					this.recalcAspectRatio();
-
-					this.scene = null;
-					this.cameraDefaults = {
-						posCamera: new THREE.Vector3( 500.0, 500.0, 1000.0 ),
-						posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
-						near: 0.1,
-						far: 10000,
-						fov: 45
-					};
-					this.camera = null;
-					this.cameraTarget = this.cameraDefaults.posCameraTarget;
-
-					this.controls = null;
-
-					this.cube = null;
-					this.pivot = null;
+				this.cube = null;
+				this.pivot = null;
+			};
 
-				}
+			MeshSprayApp.prototype = {
 
-				MeshSprayApp.prototype.initGL = function () {
+				constructor: MeshSprayApp,
 
+				initGL: function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
 						antialias: true,
@@ -448,8 +417,8 @@
 					var directionalLight1 = new THREE.DirectionalLight( 0xC0C090 );
 					var directionalLight2 = new THREE.DirectionalLight( 0xC0C090 );
 
-					directionalLight1.position.set( - 100, - 50, 100 );
-					directionalLight2.position.set( 100, 50, - 100 );
+					directionalLight1.position.set( -100, -50, 100 );
+					directionalLight2.position.set( 100, 50, -100 );
 
 					this.scene.add( directionalLight1 );
 					this.scene.add( directionalLight2 );
@@ -458,7 +427,7 @@
 					var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
 					this.scene.add( helper );
 
-					var geometry = new THREE.BoxBufferGeometry( 10, 10, 10 );
+					var geometry = new THREE.BoxGeometry( 10, 10, 10 );
 					var material = new THREE.MeshNormalMaterial();
 					this.cube = new THREE.Mesh( geometry, material );
 					this.cube.position.set( 0, 0, 0 );
@@ -467,31 +436,24 @@
 					this.pivot = new THREE.Object3D();
 					this.pivot.name = 'Pivot';
 					this.scene.add( this.pivot );
+				},
 
-				};
-
-				MeshSprayApp.prototype.initContent = function () {
-
+				initContent: function () {
 					var maxQueueSize = 1024;
 					var maxWebWorkers = 4;
 					var radius = 640;
-					var workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray );
+					var workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray.Loader );
 					workerDirector.setLogging( false, false );
 					workerDirector.setCrossOrigin( 'anonymous' );
 
 					var callbackOnLoad = function ( event ) {
-
 						console.info( 'Worker #' + event.detail.instanceNo + ': Completed loading. (#' + workerDirector.objectsCompleted + ')' );
-
 					};
-					var reportProgress = function ( event ) {
-
+					var reportProgress = function( event ) {
 						document.getElementById( 'feedback' ).innerHTML = event.detail.text;
 						console.info( event.detail.text );
-
 					};
 					var callbackMeshAlter = function ( event ) {
-
 						var override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, true );
 
 						event.detail.side = THREE.DoubleSide;
@@ -500,9 +462,9 @@
 						override.addMesh( mesh );
 
 						return override;
-
 					};
 
+
 					var callbacks = new THREE.LoaderSupport.Callbacks();
 					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 					callbacks.setCallbackOnLoad( callbackOnLoad );
@@ -513,8 +475,7 @@
 					var pivot;
 					var s, t, r, x, y, z;
 					var globalObjectCount = 0;
-					for ( var i = 0; i < maxQueueSize; i ++ ) {
-
+					for ( var i = 0; i < maxQueueSize; i++ ) {
 						prepData = new THREE.LoaderSupport.PrepData( 'Triangles_' + i );
 
 						pivot = new THREE.Object3D();
@@ -531,51 +492,40 @@
 
 						prepData.quantity = 8192;
 						prepData.dimension = Math.max( Math.random() * 500, 100 );
-						prepData.globalObjectCount = globalObjectCount ++;
+						prepData.globalObjectCount = globalObjectCount++;
 
 						workerDirector.enqueueForRun( prepData );
-
 					}
 					workerDirector.processQueue();
+				},
 
-				};
-
-				MeshSprayApp.prototype.resizeDisplayGL = function () {
-
+				resizeDisplayGL: function () {
 					this.controls.handleResize();
 
 					this.recalcAspectRatio();
 					this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
 
 					this.updateCamera();
+				},
 
-				};
-
-				MeshSprayApp.prototype.recalcAspectRatio = function () {
-
+				recalcAspectRatio: function () {
 					this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
+				},
 
-				};
-
-				MeshSprayApp.prototype.resetCamera = function () {
-
+				resetCamera: function () {
 					this.camera.position.copy( this.cameraDefaults.posCamera );
 					this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
 
 					this.updateCamera();
+				},
 
-				};
-
-				MeshSprayApp.prototype.updateCamera = function () {
-
+				updateCamera: function () {
 					this.camera.aspect = this.aspectRatio;
 					this.camera.lookAt( this.cameraTarget );
 					this.camera.updateProjectionMatrix();
+				},
 
-				};
-
-				MeshSprayApp.prototype.render = function () {
-
+				render: function () {
 					if ( ! this.renderer.autoClear ) this.renderer.clear();
 
 					this.controls.update();
@@ -584,27 +534,19 @@
 					this.cube.rotation.y += 0.05;
 
 					this.renderer.render( this.scene, this.camera );
-
-				};
-
-				return MeshSprayApp;
-
-			} )();
+				}
+			};
 
 			var app = new MeshSprayApp( document.getElementById( 'example' ) );
 
 			// init three.js example application
 			var resizeWindow = function () {
-
 				app.resizeDisplayGL();
-
 			};
 
 			var render = function () {
-
 				requestAnimationFrame( render );
 				app.render();
-
 			};
 
 			window.addEventListener( 'resize', resizeWindow, false );

+ 80 - 149
examples/webgl_loader_obj2_options.html

@@ -83,40 +83,37 @@
 
 			'use strict';
 
-			var WWOBJLoader2Example = ( function () {
-
-				var Validator = THREE.LoaderSupport.Validator;
-
-				function WWOBJLoader2Example( elementToBindTo ) {
-
-					this.renderer = null;
-					this.canvas = elementToBindTo;
-					this.aspectRatio = 1;
-					this.recalcAspectRatio();
-
-					this.scene = null;
-					this.cameraDefaults = {
-						posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
-						posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
-						near: 0.1,
-						far: 10000,
-						fov: 45
-					};
-					this.camera = null;
-					this.cameraTarget = this.cameraDefaults.posCameraTarget;
+			var WWOBJLoader2Example = function ( elementToBindTo ) {
+				this.renderer = null;
+				this.canvas = elementToBindTo;
+				this.aspectRatio = 1;
+				this.recalcAspectRatio();
+
+				this.scene = null;
+				this.cameraDefaults = {
+					posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
+					posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
+					near: 0.1,
+					far: 10000,
+					fov: 45
+				};
+				this.camera = null;
+				this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
-					this.controls = null;
+				this.controls = null;
 
-					this.flatShading = false;
-					this.doubleSide = false;
+				this.flatShading = false;
+				this.doubleSide = false;
 
-					this.cube = null;
-					this.pivot = null;
+				this.cube = null;
+				this.pivot = null;
+			};
 
-				}
+			WWOBJLoader2Example.prototype = {
 
-				WWOBJLoader2Example.prototype.initGL = function () {
+				constructor: WWOBJLoader2Example,
 
+				initGL: function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
 						antialias: true,
@@ -134,8 +131,8 @@
 					var directionalLight1 = new THREE.DirectionalLight( 0xC0C090 );
 					var directionalLight2 = new THREE.DirectionalLight( 0xC0C090 );
 
-					directionalLight1.position.set( - 100, - 50, 100 );
-					directionalLight2.position.set( 100, 50, - 100 );
+					directionalLight1.position.set( -100, -50, 100 );
+					directionalLight2.position.set( 100, 50, -100 );
 
 					this.scene.add( directionalLight1 );
 					this.scene.add( directionalLight2 );
@@ -144,7 +141,7 @@
 					var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
 					this.scene.add( helper );
 
-					var geometry = new THREE.BoxBufferGeometry( 10, 10, 10 );
+					var geometry = new THREE.BoxGeometry( 10, 10, 10 );
 					var material = new THREE.MeshNormalMaterial();
 					this.cube = new THREE.Mesh( geometry, material );
 					this.cube.position.set( 0, 0, 0 );
@@ -153,18 +150,15 @@
 					this.pivot = new THREE.Object3D();
 					this.pivot.name = 'Pivot';
 					this.scene.add( this.pivot );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.useParseSync = function () {
-
+				useParseSync: function () {
 					var modelName = 'female02';
 					this._reportProgress( { detail: { text: 'Loading: ' + modelName } } );
 
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var onLoadMtl = function ( materials ) {
-
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
 
@@ -173,7 +167,6 @@
 						fileLoader.setResponseType( 'arraybuffer' );
 						fileLoader.load( 'models/obj/female02/female02.obj',
 							function ( content ) {
-
 								var local = new THREE.Object3D();
 								local.name = 'Pivot_female02';
 								local.position.set( 75, 0, 0 );
@@ -181,30 +174,27 @@
 								local.add( objLoader.parse( content ) );
 
 								scope._reportProgress( { detail: { text: 'Loading complete: ' + modelName } } );
-
 							}
 						);
-
 					};
-					objLoader.loadMtl( 'models/obj/female02/female02.mtl', null, onLoadMtl );
-
-				};
-
-				WWOBJLoader2Example.prototype.useParseAsync = function () {
+					var onError = function ( event ) {
+						console.error( 'Error occurred: ' + event );
+					};
+					objLoader.loadMtl( 'models/obj/female02/female02.mtl', null, onLoadMtl, null, onError );
+				},
 
-					var modelName = 'female02_vertex';
+				useParseAsync: function () {
+					var modelName = 'female02_vertex' ;
 					this._reportProgress( { detail: { text: 'Loading: ' + modelName } } );
 
 					var callbackOnLoad = function ( event ) {
-
 						var local = new THREE.Object3D();
 						local.name = 'Pivot_female02_vertex';
-						local.position.set( - 75, 0, 0 );
+						local.position.set( -75, 0, 0 );
 						scope.pivot.add( local );
 						local.add( event.detail.loaderRootNode );
 
 						scope._reportProgress( { detail: { text: 'Loading complete: ' + event.detail.modelName } } );
-
 					};
 
 					var scope = this;
@@ -217,70 +207,58 @@
 					var filename = 'models/obj/female02/female02_vertex_colors.obj';
 					fileLoader.load( filename,
 						function ( content ) {
-
 							objLoader.parseAsync( content, callbackOnLoad );
 							scope._reportProgress( { detail: { text: 'File loading complete: ' + filename } } );
+						}
+					);
+				},
 
-						} );
-
-				};
-
-				WWOBJLoader2Example.prototype.useLoadSync = function () {
-
+				useLoadSync: function () {
 					var modelName = 'male02';
 					this._reportProgress( { detail: { text: 'Loading: ' + modelName } } );
 
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var callbackOnLoad = function ( event ) {
-
 						var local = new THREE.Object3D();
 						local.name = 'Pivot_male02';
-						local.position.set( 0, 0, - 75 );
+						local.position.set( 0, 0, -75 );
 						scope.pivot.add( local );
 						local.add( event.detail.loaderRootNode );
 
 						scope._reportProgress( { detail: { text: 'Loading complete: ' + event.detail.modelName } } );
-
 					};
 
 					var onLoadMtl = function ( materials ) {
-
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
 						objLoader.setUseIndices( true );
 						objLoader.load( 'models/obj/male02/male02.obj', callbackOnLoad, null, null, null, false );
-
 					};
 					objLoader.loadMtl( 'models/obj/male02/male02.mtl', null, onLoadMtl );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.useLoadAsync = function () {
-
+				useLoadAsync: function () {
 					var modelName = 'WaltHead';
 					this._reportProgress( { detail: { text: 'Loading: ' + modelName } } );
 
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var callbackOnLoad = function ( event ) {
-
 						objLoader.workerSupport.setTerminateRequested( true );
 
 						var local = new THREE.Object3D();
 						local.name = 'Pivot_WaltHead';
-						local.position.set( - 125, 50, 0 );
+						local.position.set( -125, 50, 0 );
 						var scale = 0.5;
 						local.scale.set( scale, scale, scale );
 						scope.pivot.add( local );
 						local.add( event.detail.loaderRootNode );
 
 						scope._reportProgress( { detail: { text: 'Loading complete: ' + event.detail.modelName } } );
-
 					};
 
 					var onLoadMtl = function ( materials ) {
-
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
 						objLoader.terminateWorkerOnLoad = false;
@@ -288,16 +266,12 @@
 
 					};
 					objLoader.loadMtl( 'models/obj/walt/WaltHead.mtl', null, onLoadMtl );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.useRunSync = function () {
-
+				useRunSync: function () {
 					var scope = this;
 					var callbackOnLoad = function ( event ) {
-
 						scope._reportProgress( { detail: { text: 'Loading complete: ' + event.detail.modelName } } );
-
 					};
 
 					var prepData = new THREE.LoaderSupport.PrepData( 'cerberus' );
@@ -313,16 +287,12 @@
 
 					var objLoader = new THREE.OBJLoader2();
 					objLoader.run( prepData );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.useRunAsyncMeshAlter = function () {
-
+				useRunAsyncMeshAlter: function () {
 					var scope = this;
 					var callbackOnLoad = function ( event ) {
-
 						scope._reportProgress( { detail: { text: 'Loading complete: ' + event.detail.modelName } } );
-
 					};
 
 					var prepData = new THREE.LoaderSupport.PrepData( 'vive-controller' );
@@ -335,7 +305,6 @@
 					prepData.useAsync = true;
 					var callbacks = prepData.getCallbacks();
 					var callbackMeshAlter = function ( event ) {
-
 						var override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, true );
 
 						var mesh = new THREE.Mesh( event.detail.bufferGeometry, event.detail.material );
@@ -349,7 +318,6 @@
 						override.addMesh( helper );
 
 						return override;
-
 					};
 					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 					callbacks.setCallbackOnProgress( this._reportProgress );
@@ -357,59 +325,45 @@
 
 					var objLoader = new THREE.OBJLoader2();
 					objLoader.run( prepData );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.finalize = function () {
-
+				finalize: function () {
 					this._reportProgress( { detail: { text: '' } } );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype._reportProgress = function ( event ) {
-
-					var output = Validator.verifyInput( event.detail.text, '' );
+				_reportProgress: function( event ) {
+					var output = THREE.LoaderSupport.Validator.verifyInput( event.detail.text, '' );
 					console.log( 'Progress: ' + output );
 					document.getElementById( 'feedback' ).innerHTML = output;
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.resizeDisplayGL = function () {
-
+				resizeDisplayGL: function () {
 					this.controls.handleResize();
 
 					this.recalcAspectRatio();
 					this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
 
 					this.updateCamera();
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.recalcAspectRatio = function () {
-
+				recalcAspectRatio: function () {
 					this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.resetCamera = function () {
-
+				resetCamera: function () {
 					this.camera.position.copy( this.cameraDefaults.posCamera );
 					this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
 
 					this.updateCamera();
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.updateCamera = function () {
-
+				updateCamera: function () {
 					this.camera.aspect = this.aspectRatio;
 					this.camera.lookAt( this.cameraTarget );
 					this.camera.updateProjectionMatrix();
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.render = function () {
-
+				render: function () {
 					if ( ! this.renderer.autoClear ) this.renderer.clear();
 
 					this.controls.update();
@@ -418,53 +372,39 @@
 					this.cube.rotation.y += 0.05;
 
 					this.renderer.render( this.scene, this.camera );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.alterShading = function () {
-
+				alterShading: function () {
 					var scope = this;
 					scope.flatShading = ! scope.flatShading;
-					console.log( scope.flatShading ? 'Enabling flat shading' : 'Enabling smooth shading' );
+					console.log( scope.flatShading ? 'Enabling flat shading' : 'Enabling smooth shading');
 
 					scope.traversalFunction = function ( material ) {
-
 						material.flatShading = scope.flatShading;
 						material.needsUpdate = true;
-
 					};
 					var scopeTraverse = function ( object3d ) {
-
 						scope.traverseScene( object3d );
-
 					};
 					scope.pivot.traverse( scopeTraverse );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.alterDouble = function () {
-
+				alterDouble: function () {
 					var scope = this;
 					scope.doubleSide = ! scope.doubleSide;
 					console.log( scope.doubleSide ? 'Enabling DoubleSide materials' : 'Enabling FrontSide materials' );
 
 					scope.traversalFunction = function ( material ) {
-
 						material.side = scope.doubleSide ? THREE.DoubleSide : THREE.FrontSide;
-
 					};
 
 					var scopeTraverse = function ( object3d ) {
-
 						scope.traverseScene( object3d );
-
 					};
 					scope.pivot.traverse( scopeTraverse );
+				},
 
-				};
-
-				WWOBJLoader2Example.prototype.traverseScene = function ( object3d ) {
-
+				traverseScene: function ( object3d ) {
 					if ( object3d.material instanceof THREE.MultiMaterial ) {
 
 						var materials = object3d.material.materials;
@@ -479,12 +419,10 @@
 						this.traversalFunction( object3d.material );
 
 					}
+				}
 
-				};
-
-				return WWOBJLoader2Example;
+			};
 
-			} )();
 
 			var app = new WWOBJLoader2Example( document.getElementById( 'example' ) );
 
@@ -502,35 +440,27 @@
 
 			var folderOptions = gui.addFolder( 'WWOBJLoader2 Options' );
 			var controlFlat = folderOptions.add( wwObjLoader2Control, 'flatShading' ).name( 'Flat Shading' );
-			controlFlat.onChange( function ( value ) {
-
+			controlFlat.onChange( function( value ) {
 				console.log( 'Setting flatShading to: ' + value );
 				app.alterShading();
-
-			} );
+			});
 
 			var controlDouble = folderOptions.add( wwObjLoader2Control, 'doubleSide' ).name( 'Double Side Materials' );
-			controlDouble.onChange( function ( value ) {
-
+			controlDouble.onChange( function( value ) {
 				console.log( 'Setting doubleSide to: ' + value );
 				app.alterDouble();
-
-			} );
+			});
 			folderOptions.open();
 
 
 			// init three.js example application
 			var resizeWindow = function () {
-
 				app.resizeDisplayGL();
-
 			};
 
 			var render = function () {
-
 				requestAnimationFrame( render );
 				app.render();
-
 			};
 			window.addEventListener( 'resize', resizeWindow, false );
 
@@ -541,6 +471,7 @@
 			// kick render loop
 			render();
 
+
 			// Load a file with OBJLoader.parse synchronously
 			app.useParseSync();
 
@@ -563,4 +494,4 @@
 
 		</script>
 	</body>
-</html>
+</html>

+ 85 - 131
examples/webgl_loader_obj2_run_director.html

@@ -47,13 +47,13 @@
 				background-color: #000000;
 			}
 			#feedback {
-				position: absolute;
-				color: darkorange;
-				text-align: left;
-				bottom: 0%;
-				left: 0%;
-				width: auto;
-				padding: 0px 0px 4px 4px;
+			    position: absolute;
+			    color: darkorange;
+			    text-align: left;
+			    bottom: 0%;
+			    left: 0%;
+			    width: auto;
+			    padding: 0px 0px 4px 4px;
 			}
 			#dat {
 				user-select: none;
@@ -89,49 +89,46 @@
 
 			'use strict';
 
-			var WWParallels = ( function () {
-
-				var Validator = THREE.LoaderSupport.Validator;
-
-				function WWParallels( elementToBindTo ) {
-
-					this.renderer = null;
-					this.canvas = elementToBindTo;
-					this.aspectRatio = 1;
-					this.recalcAspectRatio();
-
-					this.scene = null;
-					this.cameraDefaults = {
-						posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
-						posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
-						near: 0.1,
-						far: 10000,
-						fov: 45
-					};
-					this.camera = null;
-					this.cameraTarget = this.cameraDefaults.posCameraTarget;
+			var WWParallels = function ( elementToBindTo ) {
+				this.renderer = null;
+				this.canvas = elementToBindTo;
+				this.aspectRatio = 1;
+				this.recalcAspectRatio();
+
+				this.scene = null;
+				this.cameraDefaults = {
+					posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
+					posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
+					near: 0.1,
+					far: 10000,
+					fov: 45
+				};
+				this.camera = null;
+				this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
-					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2 );
-					this.logging = {
-						enabled: false,
-						debug: false
-					};
-					this.workerDirector.setLogging( this.logging.enabled, this.logging.debug );
-					this.workerDirector.setCrossOrigin( 'anonymous' );
-					this.workerDirector.setForceWorkerDataCopy( true );
+				this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2 );
+				this.logging = {
+					enabled: false,
+					debug: false
+				};
+				this.workerDirector.setLogging( this.logging.enabled, this.logging.debug );
+				this.workerDirector.setCrossOrigin( 'anonymous' );
+				this.workerDirector.setForceWorkerDataCopy( true );
 
-					this.controls = null;
-					this.cube = null;
+				this.controls = null;
+				this.cube = null;
 
-					this.allAssets = [];
-					this.feedbackArray = null;
+				this.allAssets = [];
+				this.feedbackArray = null;
 
-					this.running = false;
+				this.running = false;
+			};
 
-				}
+			WWParallels.prototype = {
 
-				WWParallels.prototype.initGL = function () {
+				constructor: WWParallels,
 
+				initGL: function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
 						antialias: true,
@@ -149,8 +146,8 @@
 					var directionalLight1 = new THREE.DirectionalLight( 0xC0C090 );
 					var directionalLight2 = new THREE.DirectionalLight( 0xC0C090 );
 
-					directionalLight1.position.set( - 100, - 50, 100 );
-					directionalLight2.position.set( 100, 50, - 100 );
+					directionalLight1.position.set( -100, -50, 100 );
+					directionalLight2.position.set( 100, 50, -100 );
 
 					this.scene.add( directionalLight1 );
 					this.scene.add( directionalLight2 );
@@ -161,45 +158,35 @@
 					this.cube = new THREE.Mesh( geometry, material );
 					this.cube.position.set( 0, 0, 0 );
 					this.scene.add( this.cube );
+				},
 
-				};
-
-				WWParallels.prototype.resizeDisplayGL = function () {
-
+				resizeDisplayGL: function () {
 					this.controls.handleResize();
 
 					this.recalcAspectRatio();
 					this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
 
 					this.updateCamera();
+				},
 
-				};
-
-				WWParallels.prototype.recalcAspectRatio = function () {
-
+				recalcAspectRatio: function () {
 					this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
+				},
 
-				};
-
-				WWParallels.prototype.resetCamera = function () {
-
+				resetCamera: function () {
 					this.camera.position.copy( this.cameraDefaults.posCamera );
 					this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
 
 					this.updateCamera();
+				},
 
-				};
-
-				WWParallels.prototype.updateCamera = function () {
-
+				updateCamera: function () {
 					this.camera.aspect = this.aspectRatio;
 					this.camera.lookAt( this.cameraTarget );
 					this.camera.updateProjectionMatrix();
+				},
 
-				};
-
-				WWParallels.prototype.render = function () {
-
+				render: function () {
 					if ( ! this.renderer.autoClear ) this.renderer.clear();
 
 					this.controls.update();
@@ -208,22 +195,18 @@
 					this.cube.rotation.y += 0.05;
 
 					this.renderer.render( this.scene, this.camera );
+				},
 
-				};
-
-				WWParallels.prototype._reportProgress = function ( content ) {
-
+				_reportProgress: function( content ) {
 					var output = content;
-					if ( Validator.isValid( content ) && Validator.isValid( content.detail ) ) output = content.detail.text;
+					if ( THREE.LoaderSupport.Validator.isValid( content ) && THREE.LoaderSupport.Validator.isValid( content.detail ) ) output = content.detail.text;
 
-					output = Validator.verifyInput( output, '' );
-					if ( this.logging.enabled ) console.info( 'Progress:\n\t' + output.replace( /\<br\>/g, '\n\t' ) );
+					output = THREE.LoaderSupport.Validator.verifyInput( output, '' );
+					if ( this.logging.enabled ) console.info( 'Progress:\n\t' + output.replace(/\<br\>/g, '\n\t' ) );
 					document.getElementById( 'feedback' ).innerHTML = output;
+				},
 
-				};
-
-				WWParallels.prototype.enqueueAllAssests = function ( maxQueueSize, maxWebWorkers, streamMeshes ) {
-
+				enqueueAllAssests: function ( maxQueueSize, maxWebWorkers, streamMeshes ) {
 					if ( this.running ) {
 
 						return;
@@ -240,7 +223,7 @@
 					scope.reportDonwload = [];
 
 					var i;
-					for ( i = 0; i < maxWebWorkers; i ++ ) {
+					for ( i = 0; i < maxWebWorkers; i++ ) {
 
 						scope.feedbackArray[ i ] = 'Worker #' + i + ': Awaiting feedback';
 						scope.reportDonwload[ i ] = true;
@@ -249,7 +232,6 @@
 					scope._reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 
 					var callbackOnLoad = function ( event ) {
-
 						var instanceNo = event.detail.instanceNo;
 						scope.reportDonwload[ instanceNo ] = false;
 						scope.allAssets.push( event.detail.loaderRootNode );
@@ -260,33 +242,35 @@
 						scope._reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 
 						if ( scope.workerDirector.objectsCompleted + 1 === maxQueueSize ) scope.running = false;
-
 					};
 
 					var callbackReportProgress = function ( event ) {
-
 						var	instanceNo = event.detail.instanceNo;
 						var text = event.detail.text;
 
 						if ( scope.reportDonwload[ instanceNo ] ) {
-
 							var msg = 'Worker #' + instanceNo + ': ' + text;
 							if ( scope.logging.enabled ) console.info( msg );
 
 							scope.feedbackArray[ instanceNo ] = msg;
 							scope._reportProgress( scope.feedbackArray.join( '\<br\>' ) );
-
 						}
+					};
+
+					var callbackReportError = function ( supportDesc, errorMessage ) {
+
+						console.error( 'LoaderWorkerDirector reported an error: ' );
+						console.error( errorMessage );
+						return true;
 
 					};
 
 					var callbackMeshAlter = function ( event, override ) {
-
-						if ( ! Validator.isValid( override ) ) override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, false );
+						if ( ! THREE.LoaderSupport.Validator.isValid( override ) ) override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, false );
 
 						var material = event.detail.material;
 						var meshName = event.detail.meshName;
-						if ( Validator.isValid( material ) && material.name === 'defaultMaterial' || meshName === 'Mesh_Mesh_head_geo.001_lambert2SG.001' ) {
+						if ( THREE.LoaderSupport.Validator.isValid( material ) && material.name === 'defaultMaterial' || meshName === 'Mesh_Mesh_head_geo.001_lambert2SG.001' ) {
 
 							var materialOverride = material;
 							materialOverride.color = new THREE.Color( Math.random(), Math.random(), Math.random() );
@@ -298,18 +282,16 @@
 
 						}
 						return override;
-
 					};
 
 					var callbackOnLoadMaterials = function ( materials ) {
-
 						console.log( 'Materials loaded' );
 						return materials;
-
 					};
 
 					var callbacks = new THREE.LoaderSupport.Callbacks();
 					callbacks.setCallbackOnProgress( callbackReportProgress );
+					callbacks.setCallbackOnReportError( callbackReportError );
 					callbacks.setCallbackOnLoad( callbackOnLoad );
 					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 					callbacks.setCallbackOnLoadMaterials( callbackOnLoadMaterials );
@@ -317,8 +299,8 @@
 					this.workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
 					if ( this.logging.enabled ) console.info( 'Configuring WWManager with queue size ' + this.workerDirector.getMaxQueueSize() + ' and ' + this.workerDirector.getMaxWebWorkers() + ' workers.' );
 
-					var modelPrepDatas = [];
 					var prepData;
+					var modelPrepDatas = [];
 					prepData = new THREE.LoaderSupport.PrepData( 'male02' );
 					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/male02/male02.obj', 'OBJ ' ) );
 					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/male02/male02.mtl', 'MTL' ) );
@@ -350,18 +332,18 @@
 					modelPrepDatas.push( prepData );
 
 					var pivot;
-					var distributionBase = - 500;
+					var distributionBase = -500;
 					var distributionMax = 1000;
 					var modelPrepDataIndex = 0;
 					var modelPrepData;
 					var scale;
-					for ( i = 0; i < maxQueueSize; i ++ ) {
+					for ( i = 0; i < maxQueueSize; i++ ) {
 
 						modelPrepDataIndex = Math.floor( Math.random() * modelPrepDatas.length );
 
 						modelPrepData = modelPrepDatas[ modelPrepDataIndex ];
 						modelPrepData.useAsync = true;
-						scale = Validator.verifyInput( modelPrepData.scale, 0 );
+						scale = THREE.LoaderSupport.Validator.verifyInput( modelPrepData.scale, 0 );
 						modelPrepData = modelPrepData.clone();
 
 						pivot = new THREE.Object3D();
@@ -375,14 +357,11 @@
 						modelPrepData.streamMeshesTo = pivot;
 
 						this.workerDirector.enqueueForRun( modelPrepData );
-
 					}
 					this.workerDirector.processQueue();
+				},
 
-				};
-
-				WWParallels.prototype.clearAllAssests = function () {
-
+				clearAllAssests: function () {
 					var storedObject3d;
 					for ( var asset in this.allAssets ) {
 
@@ -407,41 +386,30 @@
 										if ( materials.hasOwnProperty( name ) ) materials[ name ].dispose();
 
 									}
-
 								}
-
 							}
-
 							if ( object3d.hasOwnProperty( 'texture' ) )	object3d.texture.dispose();
-
 						};
-						if ( Validator.isValid( storedObject3d ) ) {
+						if ( THREE.LoaderSupport.Validator.isValid( storedObject3d ) ) {
 
 							if ( this.pivot !== storedObject3d ) scope.scene.remove( storedObject3d );
 							storedObject3d.traverse( remover );
 							storedObject3d = null;
 
 						}
-
 					}
 					this.allAssets = [];
+				},
 
-				};
-
-				WWParallels.prototype.terminateManager = function () {
-
+				terminateManager: function () {
 					this.workerDirector.tearDown();
 					this.running = false;
+				},
 
-				};
-
-				WWParallels.prototype.terminateManagerAndClearScene = function () {
-
+				terminateManagerAndClearScene: function () {
 					var scope = this;
-					var scopedClearAllAssests = function () {
-
+					var scopedClearAllAssests = function (){
 						scope.clearAllAssests();
-
 					};
 					if ( this.workerDirector.isRunning() ) {
 
@@ -450,16 +418,12 @@
 					} else {
 
 						scopedClearAllAssests();
-
 					}
 
 					this.running = false;
+				}
 
-				};
-
-				return WWParallels;
-
-			} )();
+			};
 
 
 			var app = new WWParallels( document.getElementById( 'example' ) );
@@ -469,19 +433,13 @@
 				workerCount: 4,
 				streamMeshes: true,
 				run: function () {
-
 					app.enqueueAllAssests( this.queueLength, this.workerCount, this.streamMeshes );
-
 				},
 				terminate: function () {
-
 					app.terminateManager();
-
 				},
 				clearAllAssests: function () {
-
 					app.terminateManagerAndClearScene();
-
 				}
 			};
 			var gui = new dat.GUI( {
@@ -490,7 +448,7 @@
 			} );
 
 			var menuDiv = document.getElementById( 'dat' );
-			menuDiv.appendChild( gui.domElement );
+			menuDiv.appendChild(gui.domElement);
 			var folderQueue = gui.addFolder( 'Web Worker Director Queue Control' );
 			folderQueue.add( wwParallelsControl, 'queueLength' ).min( 1 ).max( 1024 ).step( 1 );
 			folderQueue.add( wwParallelsControl, 'workerCount' ).min( 1 ).max( 16 ).step( 1 );
@@ -503,16 +461,12 @@
 			folderWWControl.add( wwParallelsControl, 'clearAllAssests' ).name( 'Clear Scene' );
 
 			var resizeWindow = function () {
-
 				app.resizeDisplayGL();
-
 			};
 
 			var render = function () {
-
 				requestAnimationFrame( render );
 				app.render();
-
 			};
 
 			window.addEventListener( 'resize', resizeWindow, false );

Some files were not shown because too many files changed in this diff