2
0
Эх сурвалжийг харах

#11746 First version of OBJLoader2 V2.0.0 intended for review

Kai Salmen 8 жил өмнө
parent
commit
d7f8f698e4

+ 3 - 2
examples/files.js

@@ -104,8 +104,8 @@ var files = {
 		"webgl_loader_obj",
 		"webgl_loader_obj_mtl",
 		"webgl_loader_obj2",
-		"webgl_loader_obj2_ww",
-		"webgl_loader_obj2_ww_parallels",
+		"webgl_loader_obj2_options",
+		"webgl_loader_obj2_run_director",
 		"webgl_loader_nrrd",
 		"webgl_loader_pcd",
 		"webgl_loader_pdb",
@@ -124,6 +124,7 @@ var files = {
 		"webgl_loader_utf8",
 		"webgl_loader_vrml",
 		"webgl_loader_vtk",
+		"webgl_loader_ww_meshspray",
 		"webgl_loader_x",
 		"webgl_lod",
 		"webgl_marchingcubes",

+ 1100 - 0
examples/js/loaders/LoaderSupport.js

@@ -0,0 +1,1100 @@
+/**
+  * @author Kai Salmen / https://kaisalmen.de
+  * Development repository: https://github.com/kaisalmen/WWOBJLoader
+  */
+
+'use strict';
+
+if ( THREE.LoaderSupport === undefined ) { THREE.LoaderSupport = {} }
+
+/**
+ * Validation functions
+ * @class
+ */
+THREE.LoaderSupport.Validator = {
+	/**
+	 * If given input is null or undefined, false is returned otherwise true.
+	 *
+	 * @param input Anything
+	 * @returns {boolean}
+	 */
+	isValid: function( input ) {
+		return ( input !== null && input !== undefined );
+	},
+	/**
+	 * If given input is null or undefined, the defaultValue is returned otherwise the given input.
+	 *
+	 * @param input Anything
+	 * @param defaultValue Anything
+	 * @returns {*}
+	 */
+	verifyInput: function( input, defaultValue ) {
+		return ( input === null || input === undefined ) ? defaultValue : input;
+	}
+};
+
+
+/**
+ * Callbacks utilized by functions working with WWLoader implementations
+ * @class
+ */
+THREE.LoaderSupport.Callbacks = (function () {
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function Callbacks() {
+		this.onProgress = null;
+		this.onMeshAlter = null;
+		this.onLoad = null;
+	}
+
+	/**
+	 * Register callback function that is invoked by internal function "announceProgress" to print feedback.
+	 * @memberOf THREE.LoaderSupport.Callbacks
+	 *
+	 * @param {callback} callbackOnProgress Callback function for described functionality
+	 */
+	Callbacks.prototype.setCallbackOnProgress = function ( callbackOnProgress ) {
+		this.onProgress = Validator.verifyInput( callbackOnProgress, this.onProgress );
+	};
+
+	/**
+	 * Register callback function that is called every time a mesh was loaded.
+	 * Use {@link THREE.LoaderSupport.LoadedMeshUserOverride} for alteration instructions (geometry, material or disregard mesh).
+	 * @memberOf THREE.LoaderSupport.Callbacks
+	 *
+	 * @param {callback} callbackOnMeshAlter Callback function for described functionality
+	 */
+	Callbacks.prototype.setCallbackOnMeshAlter = function ( callbackOnMeshAlter ) {
+		this.onMeshAlter = Validator.verifyInput( callbackOnMeshAlter, this.onMeshAlter );
+	};
+
+	/**
+	 * Register callback function that is called once loading of the complete model is completed.
+	 * @memberOf THREE.LoaderSupport.Callbacks
+	 *
+	 * @param {callback} callbackOnLoad Callback function for described functionality
+	 */
+	Callbacks.prototype.setCallbackOnLoad = function ( callbackOnLoad ) {
+		this.onLoad = Validator.verifyInput( callbackOnLoad, this.onLoad );
+	};
+
+	return Callbacks;
+})();
+
+
+/**
+ * Global callback definition
+ * @class
+ */
+THREE.LoaderSupport.Builder = (function () {
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function Builder() {
+		this.callbacks = new THREE.LoaderSupport.Callbacks();
+		this.materials = [];
+		this.materialNames = [];
+		this._createDefaultMaterials();
+	}
+
+	Builder.prototype._createDefaultMaterials = function () {
+		var defaultMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } );
+		defaultMaterial.name = 'defaultMaterial';
+		if ( ! Validator.isValid( this.materials[ defaultMaterial ] ) ) {
+			this.materials[ defaultMaterial.name ] = defaultMaterial;
+		}
+		this.materialNames.push( defaultMaterial.name );
+
+		var vertexColorMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } );
+		vertexColorMaterial.name = 'vertexColorMaterial';
+		vertexColorMaterial.vertexColors = THREE.VertexColors;
+		if ( ! Validator.isValid( this.materials[ vertexColorMaterial.name ] ) ) {
+			this.materials[ vertexColorMaterial.name ] = vertexColorMaterial;
+		}
+		this.materialNames.push( vertexColorMaterial.name );
+	};
+
+	/**
+	 * Set materials loaded by any supplier of an Array of {@link THREE.Material}.
+	 * @memberOf THREE.LoaderSupport.Builder
+	 *
+	 * @param {THREE.Material[]} materials Array of {@link THREE.Material}
+	 */
+	Builder.prototype.setMaterials = function ( materials ) {
+		if ( Validator.isValid( materials ) && Object.keys( materials ).length > 0 ) {
+
+			var materialName;
+			for ( materialName in materials ) {
+
+				if ( materials.hasOwnProperty( materialName ) && ! this.materials.hasOwnProperty( materialName) ) {
+					this.materials[ materialName ] = materials[ materialName ];
+				}
+
+			}
+
+			// always reset list of names as they are an array
+			this.materialNames = [];
+			for ( materialName in materials ) this.materialNames.push( materialName );
+
+		}
+	};
+
+	Builder.prototype._setCallbacks = function ( callbackOnProgress, callbackOnMeshAlter, callbackOnLoad ) {
+		this.callbacks.setCallbackOnProgress( callbackOnProgress );
+		this.callbacks.setCallbackOnMeshAlter( callbackOnMeshAlter );
+		this.callbacks.setCallbackOnLoad( callbackOnLoad );
+	};
+
+	/**
+	 * Builds one or multiple meshes from the data described in the payload (buffers, params, material info,
+	 *
+	 * @param {Object} payload buffers, params, materials
+	 * @returns {THREE.Mesh[]} mesh Array of {@link THREE.Mesh}
+	 */
+	Builder.prototype.buildMeshes = function ( payload ) {
+		var meshName = payload.params.meshName;
+
+		var bufferGeometry = new THREE.BufferGeometry();
+		bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( payload.buffers.vertices ), 3 ) );
+		if ( Validator.isValid( payload.buffers.indices ) ) {
+
+			bufferGeometry.setIndex( new THREE.BufferAttribute( new Uint32Array( payload.buffers.indices ), 1 ));
+
+		}
+		var haveVertexColors = Validator.isValid( payload.buffers.colors );
+		if ( haveVertexColors ) {
+
+			bufferGeometry.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( payload.buffers.colors ), 3 ) );
+
+		}
+		if ( Validator.isValid( payload.buffers.normals ) ) {
+
+			bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.buffers.normals ), 3 ) );
+
+		} else {
+
+			bufferGeometry.computeVertexNormals();
+
+		}
+		if ( Validator.isValid( payload.buffers.uvs ) ) {
+
+			bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.buffers.uvs ), 2 ) );
+
+		}
+
+		var materialDescriptions = payload.materials.materialDescriptions;
+		var materialDescription;
+		var material;
+		var materialName;
+		var createMultiMaterial = payload.materials.multiMaterial;
+		var multiMaterials = [];
+
+		var key;
+		for ( key in materialDescriptions ) {
+
+			materialDescription = materialDescriptions[ key ];
+			material = this.materials[ materialDescription.name ];
+			material = Validator.verifyInput( material, this.materials[ 'defaultMaterial' ] );
+			if ( haveVertexColors ) {
+
+				if ( material.hasOwnProperty( 'vertexColors' ) ) {
+
+					materialName = material.name + '_vertexColor';
+					var materialClone = this.materials[ materialName ];
+					if ( ! Validator.isValid( materialClone ) ) {
+
+						materialClone = material.clone();
+						materialClone.name = materialName;
+						materialClone.vertexColors = THREE.VertexColors;
+						this.materials[ materialName ] = materialClone;
+
+					}
+					material = materialClone;
+
+				} else {
+
+					material = this.materials[ 'vertexColorMaterial' ];
+				}
+
+			}
+
+			if ( materialDescription.flat ) {
+
+				materialName = material.name + '_flat';
+				var materialClone = this.materials[ materialName ];
+				if ( ! Validator.isValid( materialClone ) ) {
+
+					materialClone = material.clone();
+					materialClone.name = materialName;
+					materialClone.flatShading = true;
+					this.materials[ materialName ] = materialClone;
+
+				}
+				material = materialClone;
+
+			}
+			if ( createMultiMaterial ) multiMaterials.push( material );
+
+		}
+		if ( createMultiMaterial ) {
+
+			material = multiMaterials;
+			var materialGroups = payload.materials.materialGroups;
+			var materialGroup;
+			for ( key in materialGroups ) {
+
+				materialGroup = materialGroups[ key ];
+				bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index );
+
+			}
+
+		}
+
+		var meshes = [];
+		var mesh;
+		var callbackOnMeshAlter = this.callbacks.onMeshAlter;
+		var callbackOnMeshAlterResult;
+		var useOrgMesh = true;
+		if ( Validator.isValid( callbackOnMeshAlter ) ) {
+
+			callbackOnMeshAlterResult = callbackOnMeshAlter( meshName, bufferGeometry, material );
+			if ( Validator.isValid( callbackOnMeshAlterResult ) ) {
+
+				if ( ! callbackOnMeshAlterResult.isDisregardMesh() && callbackOnMeshAlterResult.providesAlteredMeshes() ) {
+
+					for ( var i in callbackOnMeshAlterResult.meshes ) {
+
+						meshes.push( callbackOnMeshAlterResult.meshes[ i ] );
+
+					}
+
+				}
+				useOrgMesh = false;
+
+			}
+
+		}
+		if ( useOrgMesh ) {
+
+			mesh = new THREE.Mesh( bufferGeometry, material );
+			mesh.name = meshName;
+			meshes.push( mesh );
+
+		}
+
+		var progressMessage;
+		if ( Validator.isValid( meshes ) && meshes.length > 0 ) {
+
+			var meshNames = [];
+			for ( var i in meshes ) {
+
+				mesh = meshes[ i ];
+				meshNames[ i ] = mesh.name;
+
+			}
+			progressMessage = 'Adding mesh(es) (' + meshNames.length + ': ' + meshNames + ') from input mesh: ' + meshName;
+
+		} else {
+
+			progressMessage = 'Not adding mesh: ' + meshName;
+
+		}
+		var callbackOnProgress = this.callbacks.onProgress;
+		if ( Validator.isValid( callbackOnProgress ) ) callbackOnProgress( progressMessage );
+
+		return meshes;
+	};
+
+	return Builder;
+})();
+
+
+/**
+ * Global callback definition
+ * @class
+ */
+THREE.LoaderSupport.Commons = (function () {
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function Commons( manager ) {
+		this.manager = Validator.verifyInput( manager, THREE.DefaultLoadingManager );
+
+		this.modelName = '';
+		this.instanceNo = 0;
+		this.path = '';
+		this.debug = false;
+
+		this.loaderRootNode = new THREE.Group();
+		this.builder = new THREE.LoaderSupport.Builder();
+		this.callbacks = new THREE.LoaderSupport.Callbacks();
+	};
+
+	Commons.prototype._applyPrepData = function ( prepData ) {
+		if ( Validator.isValid( prepData ) ) {
+
+			this.setModelName( prepData.modelName );
+			this.setStreamMeshesTo( prepData.streamMeshesTo );
+			this.builder.setMaterials( prepData.materials );
+
+			this._setCallbacks( prepData.getCallbacks().onProgress, prepData.getCallbacks().onMeshAlter, prepData.getCallbacks().onLoad );
+		}
+	};
+
+	Commons.prototype._setCallbacks = function ( callbackOnProgress, callbackOnMeshAlter, callbackOnLoad ) {
+		this.callbacks.setCallbackOnProgress( callbackOnProgress );
+		this.callbacks.setCallbackOnMeshAlter( callbackOnMeshAlter );
+		this.callbacks.setCallbackOnLoad( callbackOnLoad );
+
+		this.builder._setCallbacks( callbackOnProgress, callbackOnMeshAlter, callbackOnLoad );
+	};
+
+	Commons.prototype.setModelName = function ( modelName ) {
+		this.modelName = Validator.verifyInput( modelName, this.modelName );
+	};
+
+	/**
+	 * The URL of the base path
+	 * @param {string} path
+	 */
+	Commons.prototype.setPath = function ( path ) {
+		this.path = Validator.verifyInput( path, this.path );
+	};
+
+	/**
+	 * Allows to set debug mode.
+	 * @memberOf THREE.LoaderSupport.Commons
+	 *
+	 * @param {boolean} enabled
+	 */
+	Commons.prototype.setDebug = function ( enabled ) {
+		this.debug = enabled;
+	};
+
+	/**
+	 * Set the node where the loaded objects will be attached directly.
+	 * @memberOf THREE.LoaderSupport.Commons
+	 *
+	 * @param {THREE.Object3D} streamMeshesTo Attached scenegraph object where meshes will be attached live
+	 */
+	Commons.prototype.setStreamMeshesTo = function ( streamMeshesTo ) {
+		this.loaderRootNode = Validator.verifyInput( streamMeshesTo, this.loaderRootNode );
+	};
+
+	/**
+	 * Set materials loaded by MTLLoader or any other supplier of an Array of {@link THREE.Material}.
+	 * @memberOf THREE.LoaderSupport.Commons
+	 *
+	 * @param {THREE.Material[]} materials Array of {@link THREE.Material}
+	 */
+	Commons.prototype.setMaterials = function ( materials ) {
+		this.builder.setMaterials( materials );
+	};
+
+	/**
+	 * Announce feedback which is give to the registered callbacks and logged if debug is enabled
+	 * @memberOf THREE.LoaderSupport.Commons
+	 *
+	 * @param baseText
+	 * @param text
+	 */
+	Commons.prototype.onProgress = function ( baseText, text ) {
+		var content = Validator.isValid( baseText ) ? baseText: '';
+		content = Validator.isValid( text ) ? content + ' ' + text : content;
+
+		if ( Validator.isValid( this.callbacks.onProgress ) ) this.callbacks.onProgress( content, this.modelName, this.instanceNo );
+
+		if ( this.debug ) console.log( content );
+	};
+
+	return Commons;
+})();
+
+
+/**
+ * Object to return by {@link THREE.LoaderSupport.Commons}.callbacks.meshLoaded.
+ * Used to disregard a certain mesh or to return one to many created meshes.
+ * @class
+ *
+ * @param {boolean} disregardMesh=false Tell implementation to completely disregard this mesh
+ */
+THREE.LoaderSupport.LoadedMeshUserOverride = (function () {
+
+	function LoadedMeshUserOverride( disregardMesh, alteredMesh ) {
+		this.disregardMesh = disregardMesh === true;
+		this.alteredMesh = alteredMesh === true;
+		this.meshes = [];
+	}
+
+	/**
+	 * Add a mesh created within callback.
+	 *
+	 * @memberOf THREE.OBJLoader2.LoadedMeshUserOverride
+	 *
+	 * @param {THREE.Mesh} mesh
+	 */
+	LoadedMeshUserOverride.prototype.addMesh = function ( mesh ) {
+		this.meshes.push( mesh );
+	};
+
+	/**
+	 * Answers if mesh shall be disregarded completely.
+	 *
+	 * @returns {boolean}
+	 */
+	LoadedMeshUserOverride.prototype.isDisregardMesh = function () {
+		return this.disregardMesh;
+	};
+
+	/**
+	 * Answers if new mesh(es) were created.
+	 *
+	 * @returns {boolean}
+	 */
+	LoadedMeshUserOverride.prototype.providesAlteredMeshes = function () {
+		return this.alteredMesh;
+	};
+
+	return LoadedMeshUserOverride;
+})();
+
+
+/**
+ * A resource description used by {@link THREE.LoaderSupport.PrepData} and others.
+ * @class
+ *
+ * @param {string} url URL to the file
+ * @param {string} extension The file extension (type)
+ */
+THREE.LoaderSupport.ResourceDescriptor = (function () {
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function ResourceDescriptor( url, extension ) {
+		var urlParts = url.split( '/' );
+
+		if ( urlParts.length < 2 ) {
+
+			this.path = null;
+			this.name = this.name = url;
+			this.url = url;
+
+		} else {
+
+			this.path = Validator.verifyInput( urlParts.slice( 0, urlParts.length - 1).join( '/' ) + '/', null );
+			this.name = Validator.verifyInput( urlParts[ urlParts.length - 1 ], null );
+			this.url = url;
+
+		}
+		this.extension = Validator.verifyInput( extension, "default" );
+		this.extension = this.extension.trim();
+		this.content = null;
+	}
+
+	/**
+	 * Set the content of this resource (String)
+	 * @memberOf THREE.LoaderSupport.ResourceDescriptor
+	 *
+	 * @param {Object} content The file content as arraybuffer or text
+	 */
+	ResourceDescriptor.prototype.setContent = function ( content ) {
+		this.content = Validator.verifyInput( content, null );
+	};
+
+	return ResourceDescriptor;
+})();
+
+
+/**
+ * Base class for configuration of prepareRun when using {@link THREE.LoaderSupport.WorkerSupport}.
+ * @class
+ */
+THREE.LoaderSupport.PrepData = (function () {
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function PrepData( modelName ) {
+		this.modelName = Validator.verifyInput( modelName, '' );
+		this.resources = [];
+		this.streamMeshesTo = null;
+		this.materialPerSmoothingGroup = false;
+		this.callbacks = new THREE.LoaderSupport.Callbacks();
+		this.crossOrigin;
+		this.useAsync = false;
+	}
+
+	/**
+	 * {@link THREE.Object3D} where meshes will be attached.
+	 * @memberOf THREE.LoaderSupport.PrepData
+	 *
+	 * @param {THREE.Object3D} streamMeshesTo Scene graph object
+	 */
+	PrepData.prototype.setStreamMeshesTo = function ( streamMeshesTo ) {
+		this.streamMeshesTo = Validator.verifyInput( streamMeshesTo, null );
+	};
+
+	/**
+	 * Tells whether a material shall be created per smoothing group
+	 * @memberOf THREE.LoaderSupport.PrepData
+	 *
+	 * @param {boolean} materialPerSmoothingGroup=false Default is false
+	 */
+	PrepData.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
+		this.materialPerSmoothingGroup = materialPerSmoothingGroup;
+	};
+
+	/**
+	 * Returns all callbacks as {@link THREE.LoaderSupport.Callbacks}
+	 * @memberOf THREE.LoaderSupport.PrepData
+	 *
+	 * @returns {THREE.LoaderSupport.Callbacks}
+	 */
+	PrepData.prototype.getCallbacks = function () {
+		return this.callbacks;
+	};
+
+	/**
+	 * Sets the CORS string to be used.
+	 * @memberOf THREE.LoaderSupport.PrepData
+	 *
+	 * @param {string} crossOrigin CORS value
+	 */
+	PrepData.prototype.setCrossOrigin = function ( crossOrigin ) {
+		this.crossOrigin = crossOrigin;
+	};
+
+	/**
+	 * Add a resource description
+	 * @memberOf THREE.LoaderSupport.PrepData
+	 *
+	 * @param {THREE.LoaderSupport.ResourceDescriptor} The resource description
+	 */
+	PrepData.prototype.addResource = function ( resource ) {
+		this.resources.push( resource );
+	};
+
+	/**
+	 *
+	 * @param {boolean} useAsync
+	 */
+	PrepData.prototype.setUseAsync = function ( useAsync ) {
+		this.useAsync = useAsync === true;
+	};
+
+	/**
+	 * Clones this object and returns it afterwards.
+	 *
+	 * @returns {@link THREE.LoaderSupport.PrepData}
+	 */
+	PrepData.prototype.clone = function () {
+		var clone = new THREE.LoaderSupport.PrepData( this.modelName );
+		clone.resources = this.resources;
+		clone.streamMeshesTo = this.streamMeshesTo;
+		clone.materialPerSmoothingGroup = this.materialPerSmoothingGroup;
+		clone.callbacks = this.callbacks;
+		clone.crossOrigin = this.crossOrigin;
+		clone.useAsync = this.useAsync;
+		return clone;
+	};
+
+	return PrepData;
+})();
+
+THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
+
+	function WorkerRunnerRefImpl() {
+		var scope = this;
+		var scopedRunner = function( event ) {
+			scope.run( event.data );
+		};
+		self.addEventListener( 'message', scopedRunner, false );
+	}
+
+	WorkerRunnerRefImpl.prototype.applyProperties = function ( parser, params ) {
+		var property, funcName, values;
+		for ( property in params ) {
+			funcName = 'set' + property.substring( 0, 1 ).toLocaleUpperCase() + property.substring( 1 );
+			values = params[ property ];
+			if ( parser.hasOwnProperty( property ) ) {
+
+				if ( typeof parser[ funcName ] === 'function' ) {
+
+					parser[ funcName ]( values );
+
+				} else {
+
+					parser[ property ] = values;
+
+				}
+			}
+		}
+	};
+
+	WorkerRunnerRefImpl.prototype.run = function ( payload ) {
+		if ( payload.cmd === 'run' ) {
+
+			console.log( 'WorkerRunner: Starting Run...' );
+
+			var callbacks = {
+				callbackBuilder: function ( payload ) {
+					self.postMessage( payload );
+				},
+				callbackProgress: function ( message ) {
+					console.log( 'WorkerRunner: progress: ' + message );
+				}
+			};
+
+			// Parser is expected to be named as such
+			var parser = new Parser();
+			this.applyProperties( parser, payload.params );
+			this.applyProperties( parser, payload.materials );
+			this.applyProperties( parser, callbacks );
+			parser.parse( payload.buffers.input );
+
+			console.log( 'WorkerRunner: Run complete!' );
+
+			callbacks.callbackBuilder( {
+				cmd: 'complete',
+				msg: 'WorkerRunner completed run.'
+			} );
+
+		} else {
+
+			console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
+
+		}
+	};
+
+	return WorkerRunnerRefImpl;
+})();
+
+THREE.LoaderSupport.WorkerSupport = (function () {
+
+	var WORKER_SUPPORT_VERSION = '1.0.0-dev';
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	function WorkerSupport() {
+		console.log( "Using THREE.LoaderSupport.WorkerSupport version: " + WORKER_SUPPORT_VERSION );
+
+		// check worker support first
+		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
+		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
+		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
+
+		this.worker = null;
+		this.workerCode = null;
+		this.running = false;
+		this.terminateRequested = false;
+
+		this.callbacks = {
+			builder: null,
+			onLoad: null
+		};
+	}
+
+	WorkerSupport.prototype.validate = function ( functionCodeBuilder, forceWorkerReload, runnerImpl ) {
+		this.running = false;
+		if ( forceWorkerReload ) {
+
+			this.worker = null;
+			this.workerCode = null;
+			this.callbacks.builder = null;
+			this.callbacks.onLoad = null;
+
+		}
+
+		if ( ! Validator.isValid( this.worker ) ) {
+
+			console.log( 'WorkerSupport: Building worker code...' );
+			console.time( 'buildWebWorkerCode' );
+
+			var workerRunner;
+			if ( Validator.isValid( runnerImpl ) ) {
+
+				console.log( 'WorkerSupport: Using "' + runnerImpl.name + '" as Runncer class for worker.');
+				workerRunner = runnerImpl;
+
+			} else {
+
+				console.log( 'WorkerSupport: Using DEFAULT "THREE.LoaderSupport.WorkerRunnerRefImpl" as Runncer class for worker.');
+				workerRunner = THREE.LoaderSupport.WorkerRunnerRefImpl;
+
+			}
+			this.workerCode = functionCodeBuilder( buildObject, buildSingelton );
+			this.workerCode += buildSingelton( workerRunner.name, workerRunner.name, workerRunner );
+			this.workerCode += 'new ' + workerRunner.name + '();\n\n';
+
+			var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } );
+			this.worker = new Worker( window.URL.createObjectURL( blob ) );
+			console.timeEnd( 'buildWebWorkerCode' );
+
+			var scope = this;
+			var receiveWorkerMessage = function ( e ) {
+				var payload = e.data;
+
+				switch ( payload.cmd ) {
+					case 'meshData':
+						scope.callbacks.builder( payload );
+						break;
+
+					case 'complete':
+						scope.callbacks.onLoad( payload.msg );
+						scope.running = false;
+
+						if ( scope.terminateRequested ) {
+							console.log( 'WorkerSupport: Run is complete. Terminating application on request!' );
+							if ( Validator.isValid( scope.worker ) ) {
+								scope.worker.terminate();
+							}
+							scope.worker = null;
+							scope.workerCode = null;
+						}
+						break;
+
+					default:
+						console.error( 'WorkerSupport: Received unknown command: ' + payload.cmd );
+						break;
+
+				}
+			};
+			this.worker.addEventListener( 'message', receiveWorkerMessage, false );
+
+		}
+	};
+
+	WorkerSupport.prototype.setCallbacks = function ( builder, onLoad ) {
+		this.callbacks = {
+			builder: builder,
+			onLoad: onLoad
+		};
+	};
+
+	var buildObject = function ( fullName, object ) {
+		var objectString = fullName + ' = {\n';
+		var part;
+		for ( var name in object ) {
+
+			part = object[ name ];
+			if ( typeof( part ) === 'string' || part instanceof String ) {
+
+				part = part.replace( '\n', '\\n' );
+				part = part.replace( '\r', '\\r' );
+				objectString += '\t' + name + ': "' + part + '",\n';
+
+			} else if ( part instanceof Array ) {
+
+				objectString += '\t' + name + ': [' + part + '],\n';
+
+			} else if ( Number.isInteger( part ) ) {
+
+				objectString += '\t' + name + ': ' + part + ',\n';
+
+			} else if ( typeof part === 'function' ) {
+
+				objectString += '\t' + name + ': ' + part + ',\n';
+
+			}
+
+		}
+		objectString += '}\n\n';
+
+		return objectString;
+	};
+
+	var buildSingelton = function ( fullName, internalName, object ) {
+		var objectString = fullName + ' = (function () {\n\n';
+		objectString += '\t' + object.prototype.constructor.toString() + '\n\n';
+
+		var funcString;
+		var objectPart;
+		for ( var name in object.prototype ) {
+
+			objectPart = object.prototype[ name ];
+			if ( typeof objectPart === 'function' ) {
+
+				funcString = objectPart.toString();
+				objectString += '\t' + internalName + '.prototype.' + name + ' = ' + funcString + ';\n\n';
+
+			}
+
+		}
+		objectString += '\treturn ' + internalName + ';\n';
+		objectString += '})();\n\n';
+
+		return objectString;
+	};
+
+	WorkerSupport.prototype.setTerminateRequested = function ( terminateRequested ) {
+		this.terminateRequested = terminateRequested === true;
+	};
+
+	WorkerSupport.prototype.run = function ( messageObject ) {
+		if ( ! Validator.isValid( this.callbacks.builder ) ) throw 'Unable to run as no "builder" callback is set.';
+		if ( ! Validator.isValid( this.callbacks.onLoad ) ) throw 'Unable to run as no "onLoad" callback is set.';
+		if ( Validator.isValid( this.worker ) ) {
+			this.running = true;
+			this.worker.postMessage( messageObject );
+		}
+	};
+
+	return WorkerSupport;
+})();
+
+/**
+ * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).
+ * Workflow:
+ *   prepareWorkers
+ *   enqueueForRun
+ *   processQueue
+ *   deregister
+ *
+ * @class
+ */
+THREE.LoaderSupport.WorkerDirector = (function () {
+
+	var LOADER_WORKER_DIRECTOR_VERSION = '1.0.0-dev';
+
+	var Validator = THREE.LoaderSupport.Validator;
+
+	var MAX_WEB_WORKER = 16;
+	var MAX_QUEUE_SIZE = 8192;
+
+	function WorkerDirector( classDef ) {
+		console.log( "Using THREE.LoaderSupport.WorkerDirector version: " + LOADER_WORKER_DIRECTOR_VERSION );
+
+		this.maxQueueSize = MAX_QUEUE_SIZE ;
+		this.maxWebWorkers = MAX_WEB_WORKER;
+		this.crossOrigin = null;
+
+		if ( ! Validator.isValid( classDef ) ) throw 'Provided invalid classDef: ' + classDef;
+
+		this.workerDescription = {
+			classDef: classDef,
+			globalCallbacks: {},
+			workerSupports: []
+		};
+		this.objectsCompleted = 0;
+		this.instructionQueue = [];
+	}
+
+	/**
+	 * Returns the maximum length of the instruction queue.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @returns {number}
+	 */
+	WorkerDirector.prototype.getMaxQueueSize = function () {
+		return this.maxQueueSize;
+	};
+
+	/**
+	 * Returns the maximum number of workers.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @returns {number}
+	 */
+	WorkerDirector.prototype.getMaxWebWorkers = function () {
+		return this.maxWebWorkers;
+	};
+
+	/**
+	 * Sets the CORS string to be used.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @param {string} crossOrigin CORS value
+	 */
+	WorkerDirector.prototype.setCrossOrigin = function ( crossOrigin ) {
+		this.crossOrigin = crossOrigin;
+	};
+
+	/**
+	 * Create or destroy workers according limits. Set the name and register callbacks for dynamically created web workers.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @param {THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks} globalCallbacks  Register global callbacks used by all web workers
+	 * @param {number} maxQueueSize Set the maximum size of the instruction queue (1-1024)
+	 * @param {number} maxWebWorkers Set the maximum amount of workers (1-16)
+	 */
+	WorkerDirector.prototype.prepareWorkers = function ( globalCallbacks, maxQueueSize, maxWebWorkers ) {
+		if ( Validator.isValid( globalCallbacks ) ) this.workerDescription.globalCallbacks = globalCallbacks;
+		this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE );
+		this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER );
+		this.objectsCompleted = 0;
+		this.instructionQueue = [];
+
+		var start = this.workerDescription.workerSupports.length;
+		var i;
+		if ( start < this.maxWebWorkers ) {
+
+			for ( i = start; i < this.maxWebWorkers; i++ ) {
+
+				this.workerDescription.workerSupports[ i ] = {
+					workerSupport: new THREE.LoaderSupport.WorkerSupport(),
+					loader: null
+				};
+
+			}
+
+		} else {
+
+			for ( i = start - 1; i >= this.maxWebWorkers; i-- ) {
+
+				this.workerDescription.workerSupports[ i ].workerSupport.setRequestTerminate( true );
+				this.workerDescription.workerSupports.pop();
+
+			}
+		}
+	};
+
+	/**
+	 * Store run instructions in internal instructionQueue.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @param {Object} prepData Either {@link THREE.LoaderSupport.PrepData}
+	 */
+	WorkerDirector.prototype.enqueueForRun = function ( prepData ) {
+		if ( this.instructionQueue.length < this.maxQueueSize ) {
+			this.instructionQueue.push( prepData );
+		}
+	};
+
+	/**
+	 * Process the instructionQueue until it is depleted.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 */
+	WorkerDirector.prototype.processQueue = function () {
+		if ( this.instructionQueue.length === 0 ) return;
+
+		var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
+		for ( var i = 0; i < length; i++ ) {
+
+			this._kickWorkerRun( this.instructionQueue[ 0 ], i );
+			this.instructionQueue.shift();
+
+		}
+	};
+
+	WorkerDirector.prototype._kickWorkerRun = function( prepData, workerInstanceNo ) {
+		var scope = this;
+		var directorOnLoad = function ( sceneGraphBaseNode, modelName, instanceNo ) {
+			scope.objectsCompleted++;
+
+			var nextPrepData = scope.instructionQueue[ 0 ];
+			if ( Validator.isValid( nextPrepData ) ) {
+
+				scope.instructionQueue.shift();
+				console.log( '\nAssigning next item from queue to worker (queue length: ' + scope.instructionQueue.length + ')\n\n' );
+				scope._kickWorkerRun( nextPrepData, instanceNo );
+
+			} else if ( scope.instructionQueue.length === 0 ) {
+
+				scope.deregister();
+
+			}
+		};
+
+		var prepDataCallbacks = prepData.getCallbacks();
+		var globalCallbacks = this.workerDescription.globalCallbacks;
+		var wrapperOnLoad = function ( sceneGraphBaseNode, modelName, instanceNo ) {
+			if ( Validator.isValid( globalCallbacks.onLoad ) ) {
+
+				globalCallbacks.onLoad( sceneGraphBaseNode, modelName, instanceNo );
+
+			}
+
+			if ( Validator.isValid( prepDataCallbacks.onLoad ) ) {
+
+				prepDataCallbacks.onLoad( sceneGraphBaseNode, modelName, instanceNo );
+
+			}
+			directorOnLoad( sceneGraphBaseNode, modelName, instanceNo );
+		};
+
+		var wrapperOnProgress = function ( content, modelName, instanceNo ) {
+			if ( Validator.isValid( globalCallbacks.onProgress ) ) {
+
+				globalCallbacks.onProgress( content, modelName, instanceNo );
+			}
+
+			if ( Validator.isValid( prepDataCallbacks.onProgress ) ) {
+
+				prepDataCallbacks.onProgress( content, modelName, instanceNo );
+
+			}
+		};
+
+		var wrapperOnMeshAlter = function ( meshName, bufferGeometry, material ) {
+			if ( Validator.isValid( globalCallbacks.onMeshAlter ) ) {
+
+				globalCallbacks.onMeshAlter( meshName, bufferGeometry, material );
+			}
+
+			if ( Validator.isValid( prepDataCallbacks.onMeshAlter ) ) {
+
+				prepDataCallbacks.onMeshAlter( meshName, bufferGeometry, material );
+
+			}
+		};
+
+		var supportTuple = this.workerDescription.workerSupports[ workerInstanceNo ];
+		supportTuple.loader = this._buildLoader( workerInstanceNo );
+
+		var updatedCallbacks = new THREE.LoaderSupport.Callbacks();
+		updatedCallbacks.setCallbackOnLoad( wrapperOnLoad );
+		updatedCallbacks.setCallbackOnProgress( wrapperOnProgress );
+		updatedCallbacks.setCallbackOnMeshAlter( wrapperOnMeshAlter );
+		prepData.callbacks = updatedCallbacks;
+
+		supportTuple.loader.run( prepData, supportTuple.workerSupport );
+	};
+
+	WorkerDirector.prototype._buildLoader = function ( instanceNo ) {
+		var classDef = this.workerDescription.classDef;
+		var loader = Object.create( classDef.prototype );
+		this.workerDescription.classDef.call( loader );
+
+		// verify that all required functions are implemented
+		if ( ! loader.hasOwnProperty( 'instanceNo' ) ) throw classDef.name + ' has no property "instanceNo".';
+		loader.instanceNo = instanceNo;
+
+		if ( ! loader.hasOwnProperty( 'workerSupport' ) ) {
+
+			throw classDef.name + ' has no property "workerSupport".';
+
+		} else if ( ! classDef.workerSupport instanceof THREE.LoaderSupport.WorkerSupport ) {
+
+			throw classDef.name + '.workerSupport is not of type "THREE.LoaderSupport.WorkerSupport".';
+
+		}
+		if ( typeof loader.run !== 'function'  ) throw classDef.name + ' has no function "run".';
+
+		return loader;
+	};
+
+	/**
+	 * Terminate all workers.
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 */
+	WorkerDirector.prototype.deregister = function () {
+		console.log( 'WorkerDirector received the deregister call. Terminating all workers!' );
+
+		for ( var i = 0, length = this.workerDescription.workerSupports.length; i < length; i++ ) {
+
+			var supportTuple = this.workerDescription.workerSupports[ i ];
+			supportTuple.workerSupport.setTerminateRequested( true );
+			console.log( 'Requested termination of worker.' );
+
+			var loaderCallbacks = supportTuple.loader.callbacks;
+			if ( Validator.isValid( loaderCallbacks.onProgress ) ) loaderCallbacks.onProgress( '' );
+
+		}
+
+		this.workerDescription.workerSupports = [];
+		this.instructionQueue = [];
+	};
+
+	return WorkerDirector;
+
+})();

+ 558 - 372
examples/js/loaders/OBJLoader2.js

@@ -8,68 +8,47 @@
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
 
 /**
- * Use this class to load OBJ data from files or to parse OBJ data from arraybuffer or text
+ * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
  * @class
  *
  * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
  */
 THREE.OBJLoader2 = (function () {
 
-	var OBJLOADER2_VERSION = '1.4.1';
+	var OBJLOADER2_VERSION = '2.0.0-dev';
+	var Validator = THREE.LoaderSupport.Validator;
+	var Commons = THREE.LoaderSupport.Commons;
+
+	OBJLoader2.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );
+	OBJLoader2.prototype.constructor = OBJLoader2;
 
 	function OBJLoader2( manager ) {
+		THREE.LoaderSupport.Commons.call( this, manager );
 		console.log( "Using THREE.OBJLoader2 version: " + OBJLOADER2_VERSION );
-		this.manager = Validator.verifyInput( manager, THREE.DefaultLoadingManager );
-
-		this.path = '';
-		this.fileLoader = new THREE.FileLoader( this.manager );
-
-		this.meshCreator = new MeshCreator();
-		this.parser = new Parser( this.meshCreator );
 
-		this.validated = false;
-	}
-
-	/**
-	 * Base path to use.
-	 * @memberOf THREE.OBJLoader2
-	 *
-	 * @param {string} path The basepath
-	 */
-	OBJLoader2.prototype.setPath = function ( path ) {
-		this.path = Validator.verifyInput( path, this.path );
-	};
-
-	/**
-	 * Set the node where the loaded objects will be attached.
-	 * @memberOf THREE.OBJLoader2
-	 *
-	 * @param {THREE.Object3D} sceneGraphBaseNode Scenegraph object where meshes will be attached
-	 */
-	OBJLoader2.prototype.setSceneGraphBaseNode = function ( sceneGraphBaseNode ) {
-		this.meshCreator.setSceneGraphBaseNode( sceneGraphBaseNode );
+		this.materialPerSmoothingGroup = false;
+		this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
+		this.workerSupport = null;
 	};
 
 	/**
-	 * Set materials loaded by MTLLoader or any other supplier of an Array of {@link THREE.Material}.
+	 * Tells whether a material shall be created per smoothing group
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {THREE.Material[]} materials  Array of {@link THREE.Material} from MTLLoader
+	 * @param {boolean} materialPerSmoothingGroup=false Default is false
 	 */
-	OBJLoader2.prototype.setMaterials = function ( materials ) {
-		this.meshCreator.setMaterials( materials );
+	OBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
+		this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
 	};
 
 	/**
-	 * Allows to set debug mode for the parser and the meshCreator.
+	 * Sets debug mode for the parser
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {boolean} parserDebug Internal Parser will produce debug output
-	 * @param {boolean} meshCreatorDebug Internal MeshCreator will produce debug output
+	 * @param {boolean} enabled
 	 */
-	OBJLoader2.prototype.setDebug = function ( parserDebug, meshCreatorDebug ) {
-		this.parser.setDebug( parserDebug );
-		this.meshCreator.setDebug( meshCreatorDebug );
+	OBJLoader2.prototype.setDebug = function ( enabled ) {
+		THREE.LoaderSupport.Commons.prototype.setDebug.call( this, enabled );
 	};
 
 	/**
@@ -80,92 +59,208 @@ THREE.OBJLoader2 = (function () {
 	 * @param {callback} onLoad Called after loading was successfully completed
 	 * @param {callback} onProgress Called to report progress of loading. The argument will be the XMLHttpRequest instance, which contains {integer total} and {integer loaded} bytes.
 	 * @param {callback} onError Called after an error occurred during loading
-	 * @param {boolean} [useArrayBuffer=true] Set this to false to force string based parsing
+	 * @param {callback} onMeshAlter Called after a new mesh raw data becomes available
+	 * @param {boolean} useAsync Set this to use async loading
 	 */
-	OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, useArrayBuffer ) {
-		this._validate();
+	OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
+		var scope = this;
+		if ( ! Validator.isValid( onProgress ) ) {
+			var refPercentComplete = 0;
+			var percentComplete = 0;
+			onProgress = function ( event ) {
+				if ( ! event.lengthComputable ) return;
+
+				percentComplete = Math.round( event.loaded / event.total * 100 );
+				if ( percentComplete > refPercentComplete ) {
+
+					refPercentComplete = percentComplete;
+					var output = 'Download of "' + url + '": ' + percentComplete + '%';
+					console.log( output );
+					scope.onProgress( output );
+
+				}
+			};
+		}
+
+		if ( ! Validator.isValid( onError ) ) {
+			onError = function ( event ) {
+				var output = 'Error occurred while downloading "' + url + '"';
+				console.error( output + ': ' + event );
+				scope.onProgress( output );
+			};
+		}
+
 		this.fileLoader.setPath( this.path );
-		this.fileLoader.setResponseType( useArrayBuffer !== false ? 'arraybuffer' : 'text' );
+		this.fileLoader.setResponseType( 'arraybuffer' );
+		this.fileLoader.load( url, function ( content ) {
+			if ( useAsync ) {
 
-		var scope = this;
-		scope.fileLoader.load( url, function ( content ) {
+				scope.parseAsync( content, onLoad );
+
+			} else {
+
+				scope._setCallbacks( null, onMeshAlter, null );
+				onLoad( scope.parse( content ), scope.modelName, scope.instanceNo );
 
-			// only use parseText if useArrayBuffer is explicitly set to false
-			onLoad( useArrayBuffer !== false ? scope.parse( content ) : scope.parseText( content ) );
+			}
 
 		}, onProgress, onError );
+
 	};
 
-	/**
-	 * Default parse function: Parses OBJ file content stored in arrayBuffer and returns the sceneGraphBaseNode
+    /**
+	 * Run the loader according the provided instructions.
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
+	 * @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution
+	 * @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport
 	 */
-	OBJLoader2.prototype.parse = function ( arrayBuffer ) {
-		// fast-fail on bad type
-		if ( ! ( arrayBuffer instanceof ArrayBuffer || arrayBuffer instanceof Uint8Array ) ) {
+	OBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {
+		this._applyPrepData( prepData );
+		var available = this._checkFiles( prepData.resources );
+        this.workerSupport = Validator.verifyInput( workerSupportExternal, this.workerSupport );
+
+		var scope = this;
+		var onMaterialsLoaded = function ( materials ) {
+			scope.builder.setMaterials( materials );
 
-			throw 'Provided input is not of type arraybuffer! Aborting...';
+			if ( Validator.isValid( available.obj.content ) ) {
 
-		}
-		console.log( 'Parsing arrayBuffer...' );
-		console.time( 'parseArrayBuffer' );
+				if ( prepData.useAsync ) {
+
+					scope.parseAsync( available.obj.content, scope.callbacks.onLoad );
 
-		this._validate();
-		this.parser.parseArrayBuffer( arrayBuffer );
-		var sceneGraphAttach = this._finalize();
+				} else {
 
-		console.timeEnd( 'parseArrayBuffer' );
+					scope.parse( available.obj.content );
 
-		return sceneGraphAttach;
+				}
+			} else {
+
+				scope.setPath( available.obj.path );
+				scope.load( available.obj.name, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync );
+
+			}
+		};
+
+		this._loadMtl( available.mtl, onMaterialsLoaded, prepData.crossOrigin );
+	};
+
+	OBJLoader2.prototype._applyPrepData = function ( prepData ) {
+		THREE.LoaderSupport.Commons.prototype._applyPrepData.call( this, prepData );
+
+		if ( Validator.isValid( prepData ) ) {
+
+			this.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup );
+		}
 	};
 
 	/**
-	 * Legacy parse function: Parses OBJ file content stored in string and returns the sceneGraphBaseNode
+	 * Parses OBJ content synchronously.
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {string} text OBJ data as string
+	 * @param content
 	 */
-	OBJLoader2.prototype.parseText = function ( text ) {
-		// fast-fail on bad type
-		if ( ! ( typeof( text ) === 'string' || text instanceof String ) ) {
+	OBJLoader2.prototype.parse = function ( content ) {
+		console.time( 'OBJLoader2 parse: ' + this.modelName );
 
-			throw 'Provided input is not of type String! Aborting...';
+		this.parser = new Parser();
+		this.parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
+		this.parser.setMaterialNames( this.builder.materialNames );
+		this.parser.setDebug( this.debug );
 
-		}
-		console.log( 'Parsing text...' );
-		console.time( 'parseText' );
+		var scope = this;
+		var onMeshLoaded = function ( payload ) {
+			var meshes = scope.builder.buildMeshes( payload );
+			var mesh;
+			for ( var i in meshes ) {
+				mesh = meshes[ i ];
+				scope.loaderRootNode.add( mesh );
+			}
+		};
+		this.parser.setCallbackBuilder( onMeshLoaded );
+		var onProgressScoped = function ( message ) {
+			scope.onProgress( message );
+		};
+		this.parser.setCallbackProgress( onProgressScoped );
 
-		this._validate();
-		this.parser.parseText( text );
-		var sceneGraphBaseNode = this._finalize();
+		if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
 
-		console.timeEnd( 'parseText' );
+			console.log( 'Parsing arrayBuffer...' );
+			this.parser.parse( content );
 
-		return sceneGraphBaseNode;
-	};
+		} else if ( typeof( content ) === 'string' || content instanceof String ) {
 
-	OBJLoader2.prototype._validate = function () {
-		if ( this.validated ) return;
+			console.log( 'Parsing text...' );
+			this.parser.parseText( content );
 
-		this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
-		this.parser.validate();
-		this.meshCreator.validate();
+		} else {
 
-		this.validated = true;
+			throw 'Provided content was neither of type String nor Uint8Array! Aborting...';
+
+		}
+		console.timeEnd( 'OBJLoader2 parse: ' + this.modelName );
+
+		return this.loaderRootNode;
 	};
 
-	OBJLoader2.prototype._finalize = function () {
-		console.log( 'Global output object count: ' + this.meshCreator.globalObjectCount );
+    /**
+     * Parses OBJ content asynchronously.
+	 * @memberOf THREE.OBJLoader2
+	 *
+     * @param {arraybuffer} content
+     * @param {callback} onLoad
+     */
+	OBJLoader2.prototype.parseAsync = function ( content, onLoad ) {
+		console.time( 'OBJLoader2 parseAsync: ' + this.modelName);
 
-		this.parser.finalize();
-		this.fileLoader = null;
-		var sceneGraphBaseNode = this.meshCreator.sceneGraphBaseNode;
-		this.meshCreator.finalize();
-		this.validated = false;
+		var scope = this;
+		var scopedOnLoad = function ( message ) {
+			onLoad( scope.loaderRootNode, scope.modelName, scope.instanceNo, message );
+			console.timeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
+		};
+		var scopedOnMeshLoaded = function ( payload ) {
+			var meshes = scope.builder.buildMeshes( payload );
+			var mesh;
+			for ( var i in meshes ) {
+				mesh = meshes[ i ];
+				scope.loaderRootNode.add( mesh );
+			}
+		};
 
-		return sceneGraphBaseNode;
+		this.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport() );
+		var buildCode = function ( funcBuildObject, funcBuildSingelton ) {
+			var workerCode = '';
+			workerCode += '/**\n';
+			workerCode += '  * This code was constructed by OBJLoader2 buildWorkerCode.\n';
+			workerCode += '  */\n\n';
+			workerCode += funcBuildSingelton( 'Commons', 'Commons', Commons );
+			workerCode += funcBuildObject( 'Consts', Consts );
+			workerCode += funcBuildObject( 'Validator', Validator );
+			workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
+			workerCode += funcBuildSingelton( 'RawObject', 'RawObject', RawObject );
+			workerCode += funcBuildSingelton( 'RawObjectDescription', 'RawObjectDescription', RawObjectDescription );
+
+			return workerCode;
+		};
+		this.workerSupport.validate( buildCode, false );
+        this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
+        this.workerSupport.run(
+            {
+                cmd: 'run',
+                params: {
+                    debug: this.debug,
+                    materialPerSmoothingGroup: this.materialPerSmoothingGroup
+                },
+                materials: {
+                    materialNames: this.builder.materialNames
+                },
+                buffers: {
+                    input: content
+                }
+            },
+            [ content.buffer ]
+        );
 	};
 
 	/**
@@ -192,52 +287,46 @@ THREE.OBJLoader2 = (function () {
 		LINE_USEMTL: 'usemtl'
 	};
 
-	var Validator = {
-		/**
-		 * If given input is null or undefined, false is returned otherwise true.
-		 *
-		 * @param input Anything
-		 * @returns {boolean}
-		 */
-		isValid: function( input ) {
-			return ( input !== null && input !== undefined );
-		},
-		/**
-		 * If given input is null or undefined, the defaultValue is returned otherwise the given input.
-		 *
-		 * @param input Anything
-		 * @param defaultValue Anything
-		 * @returns {*}
-		 */
-		verifyInput: function( input, defaultValue ) {
-			return ( input === null || input === undefined ) ? defaultValue : input;
-		}
-	};
-
-	OBJLoader2.prototype._getValidator = function () {
-		return Validator;
-	};
-
 	/**
 	 * Parse OBJ data either from ArrayBuffer or string
 	 * @class
 	 */
 	var Parser = (function () {
 
-		function Parser( meshCreator ) {
-			this.meshCreator = meshCreator;
-			this.rawObject = null;
+		function Parser() {
+			this.callbackProgress = null;
 			this.inputObjectCount = 1;
 			this.debug = false;
-		}
+			this.materialPerSmoothingGroup = false;
+			this.rawObject = new RawObject( this.materialPerSmoothingGroup );
+
+			// build mesh related
+			this.callbackBuilder = null;
+			this.materialNames = [];
+			this.outputObjectCount = 1;
+		};
 
 		Parser.prototype.setDebug = function ( debug ) {
 			if ( debug === true || debug === false ) this.debug = debug;
 		};
 
-		Parser.prototype.validate = function () {
-			this.rawObject = new RawObject();
-			this.inputObjectCount = 1;
+		Parser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
+			this.materialPerSmoothingGroup = materialPerSmoothingGroup;
+			this.rawObject.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
+		};
+
+		Parser.prototype.setMaterialNames = function ( materialNames ) {
+			this.materialNames = Validator.verifyInput( materialNames, this.materialNames );
+			this.materialNames = Validator.verifyInput( this.materialNames, [] );
+		};
+
+		Parser.prototype.setCallbackBuilder = function ( callbackBuilder ) {
+			this.callbackBuilder = callbackBuilder;
+			if ( ! Validator.isValid( this.callbackBuilder ) ) throw 'Unable to run as no "builder" callback is set.';
+		};
+
+		Parser.prototype.setCallbackProgress = function ( callbackProgress ) {
+			this.callbackProgress = callbackProgress;
 		};
 
 		/**
@@ -246,7 +335,8 @@ THREE.OBJLoader2 = (function () {
 		 *
 		 * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
 		 */
-		Parser.prototype.parseArrayBuffer = function ( arrayBuffer ) {
+		Parser.prototype.parse = function ( arrayBuffer ) {
+			console.time( 'OBJLoader2.Parser.parse' );
 			var arrayBufferView = new Uint8Array( arrayBuffer );
 			var length = arrayBufferView.byteLength;
 			var buffer = new Array( 128 );
@@ -286,6 +376,8 @@ THREE.OBJLoader2 = (function () {
 						break;
 				}
 			}
+			this.finalize();
+			console.timeEnd( 'OBJLoader2.Parser.parse' );
 		};
 
 		/**
@@ -295,6 +387,7 @@ THREE.OBJLoader2 = (function () {
 		 * @param {string} text OBJ data as string
 		 */
 		Parser.prototype.parseText = function ( text ) {
+			console.time( 'OBJLoader2.Parser.parseText' );
 			var length = text.length;
 			var buffer = new Array( 128 );
 			var bufferPointer = 0;
@@ -332,6 +425,8 @@ THREE.OBJLoader2 = (function () {
 						word += char;
 				}
 			}
+			this.finalize();
+			console.timeEnd( 'OBJLoader2.Parser.parseText' );
 		};
 
 		Parser.prototype.processLine = function ( buffer, bufferPointer, slashesCount, reachedFaces ) {
@@ -441,16 +536,26 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		Parser.prototype.processCompletedObject = function ( objectName, groupName ) {
-			this.rawObject.finalize( this.meshCreator, this.inputObjectCount, this.debug );
-			this.inputObjectCount++;
+			var result = this.rawObject.finalize( this.debug );
+			if ( Validator.isValid( result ) ) {
+
+				this.inputObjectCount++;
+				if ( this.debug ) this.createReport( this.inputObjectCount, true );
+				var message = this.buildMesh( result, this.inputObjectCount );
+				this.onProgress( message );
+
+			}
 			this.rawObject = this.rawObject.newInstanceFromObject( objectName, groupName );
 		};
 
 		Parser.prototype.processCompletedGroup = function ( groupName ) {
-			var notEmpty = this.rawObject.finalize( this.meshCreator, this.inputObjectCount, this.debug );
-			if ( notEmpty ) {
+			var result = this.rawObject.finalize();
+			if ( Validator.isValid( result ) ) {
 
-				this.inputObjectCount ++;
+				this.inputObjectCount++;
+				if ( this.debug ) this.createReport( this.inputObjectCount, true );
+				var message = this.buildMesh( result, this.inputObjectCount );
+				this.onProgress( message );
 				this.rawObject = this.rawObject.newInstanceFromGroup( groupName );
 
 			} else {
@@ -462,8 +567,180 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		Parser.prototype.finalize = function () {
-			this.rawObject.finalize( this.meshCreator, this.inputObjectCount, this.debug );
-			this.inputObjectCount++;
+			console.log( 'Global output object count: ' + this.outputObjectCount );
+			var result = Validator.isValid( this.rawObject ) ? this.rawObject.finalize() : null;
+			if ( Validator.isValid( result ) ) {
+
+				this.inputObjectCount++;
+				if ( this.debug ) this.createReport( this.inputObjectCount, true );
+				var message = this.buildMesh( result, this.inputObjectCount );
+				this.onProgress( message );
+
+			}
+		};
+
+		Parser.prototype.onProgress = function ( text ) {
+			if ( Validator.isValid( text ) && Validator.isValid( this.callbackProgress) ) this.callbackProgress( text );
+		};
+
+		/**
+		 * RawObjectDescriptions are transformed to too intermediate format that is forwarded to the Builder.
+		 * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
+		 *
+		 * @param result
+		 * @param inputObjectCount
+		 */
+		Parser.prototype.buildMesh = function ( result, inputObjectCount ) {
+			if ( this.debug ) console.log( 'OBJLoader.buildMesh:\nInput object no.: ' + inputObjectCount );
+
+			var rawObjectDescriptions = result.rawObjectDescriptions;
+
+			var vertexFA = new Float32Array( result.absoluteVertexCount );
+			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 rawObjectDescription;
+			var materialDescription;
+			var materialDescriptions = [];
+
+			var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
+			var materialIndex = 0;
+			var materialIndexMapping = [];
+			var selectedMaterialIndex;
+			var materialGroup;
+			var materialGroups = [];
+
+			var vertexFAOffset = 0;
+			var vertexGroupOffset = 0;
+			var vertexLength;
+			var indexUAOffset = 0;
+			var colorFAOffset = 0;
+			var normalFAOffset = 0;
+			var uvFAOffset = 0;
+
+			for ( var oodIndex in rawObjectDescriptions ) {
+				if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
+				rawObjectDescription = rawObjectDescriptions[ oodIndex ];
+
+				materialDescription = {
+					name: rawObjectDescription.materialName,
+					flat: false,
+					default: false
+				};
+				if ( this.materialNames[ materialDescription.name ] === null ) {
+
+					materialDescription.default = true;
+					console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' );
+
+				}
+				// Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
+				if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;
+
+				vertexLength = rawObjectDescription.vertices.length;
+				if ( createMultiMaterial ) {
+
+					// re-use material if already used before. Reduces materials array size and eliminates duplicates
+
+					selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
+					if ( ! selectedMaterialIndex ) {
+
+						selectedMaterialIndex = materialIndex;
+						materialIndexMapping[ materialDescription.name ] = materialIndex;
+						materialDescriptions.push( materialDescription );
+						materialIndex++;
+
+					}
+					materialGroup = {
+						start: vertexGroupOffset,
+						count: vertexLength / 3,
+						index: selectedMaterialIndex
+					};
+					materialGroups.push( materialGroup );
+					vertexGroupOffset += vertexLength / 3;
+
+				} else {
+
+					materialDescriptions.push( materialDescription );
+
+				}
+
+				vertexFA.set( rawObjectDescription.vertices, vertexFAOffset );
+				vertexFAOffset += vertexLength;
+
+				if ( indexUA ) {
+
+					indexUA.set( rawObjectDescription.indices, indexUAOffset );
+					indexUAOffset += rawObjectDescription.indices.length;
+
+				}
+
+				if ( colorFA ) {
+
+					colorFA.set( rawObjectDescription.colors, colorFAOffset );
+					colorFAOffset += rawObjectDescription.colors.length;
+
+				}
+
+				if ( normalFA ) {
+
+					normalFA.set( rawObjectDescription.normals, normalFAOffset );
+					normalFAOffset += rawObjectDescription.normals.length;
+
+				}
+				if ( uvFA ) {
+
+					uvFA.set( rawObjectDescription.uvs, uvFAOffset );
+					uvFAOffset += rawObjectDescription.uvs.length;
+
+				}
+				if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex );
+
+			}
+
+			this.outputObjectCount++;
+			this.callbackBuilder(
+				{
+					cmd: 'meshData',
+					params: {
+						meshName: rawObjectDescription.groupName !== '' ? rawObjectDescription.groupName : rawObjectDescription.objectName
+					},
+					materials: {
+						multiMaterial: createMultiMaterial,
+						materialDescriptions: materialDescriptions,
+						materialGroups: materialGroups
+					},
+					buffers: {
+						vertices: vertexFA,
+						indices: indexUA,
+						colors: colorFA,
+						normals: normalFA,
+						uvs: uvFA
+					}
+				},
+				[ vertexFA.buffer ],
+				Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
+				Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
+				Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
+				Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
+			);
+		};
+
+		Parser.prototype.printReport = function ( rawObjectDescription, selectedMaterialIndex ) {
+			var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n materialIndex: ' + selectedMaterialIndex : '';
+			console.log(
+				' Output Object no.: ' + this.outputObjectCount +
+				'\n objectName: ' + rawObjectDescription.objectName +
+				'\n groupName: ' + rawObjectDescription.groupName +
+				'\n materialName: ' + rawObjectDescription.materialName +
+				materialIndexLine +
+				'\n smoothingGroup: ' + rawObjectDescription.smoothingGroup +
+				'\n #vertices: ' + rawObjectDescription.vertices.length / 3 +
+				'\n #colors: ' + rawObjectDescription.colors.length / 3 +
+				'\n #uvs: ' + rawObjectDescription.uvs.length / 2 +
+				'\n #normals: ' + rawObjectDescription.normals.length / 3
+			);
 		};
 
 		return Parser;
@@ -476,7 +753,7 @@ THREE.OBJLoader2 = (function () {
 	 */
 	var RawObject = (function () {
 
-		function RawObject( objectName, groupName, activeMtlName ) {
+		function RawObject( materialPerSmoothingGroup, objectName, groupName, activeMtlName ) {
 			this.globalVertexOffset = 1;
 			this.globalUvOffset = 1;
 			this.globalNormalOffset = 1;
@@ -490,7 +767,9 @@ THREE.OBJLoader2 = (function () {
 			this.activeMtlName = Validator.verifyInput( activeMtlName, '' );
 			this.objectName = Validator.verifyInput( objectName, '' );
 			this.groupName = Validator.verifyInput( groupName, '' );
+			this.mtllibName = '';
 			this.activeSmoothingGroup = 1;
+			this.materialPerSmoothingGroup = materialPerSmoothingGroup;
 
 			this.mtlCount = 0;
 			this.smoothingGroupCount = 0;
@@ -502,12 +781,17 @@ THREE.OBJLoader2 = (function () {
 			this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse;
 		}
 
-		RawObject.prototype.buildIndex = function ( materialName, smoothingGroup) {
-			return materialName + '|' + smoothingGroup;
+		RawObject.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
+			this.materialPerSmoothingGroup = materialPerSmoothingGroup;
+		};
+
+		RawObject.prototype.buildIndex = function ( materialName, smoothingGroup ) {
+			var normalizedSmoothingGroup = this.materialPerSmoothingGroup ? smoothingGroup : ( smoothingGroup === 0 ) ? 0 : 1;
+			return materialName + '|' + normalizedSmoothingGroup;
 		};
 
 		RawObject.prototype.newInstanceFromObject = function ( objectName, groupName ) {
-			var newRawObject = new RawObject( objectName, groupName, this.activeMtlName );
+			var newRawObject = new RawObject( this.materialPerSmoothingGroup, objectName, groupName, this.activeMtlName );
 
 			// move indices forward
 			newRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3;
@@ -518,7 +802,7 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		RawObject.prototype.newInstanceFromGroup = function ( groupName ) {
-			var newRawObject = new RawObject( this.objectName, groupName, this.activeMtlName );
+			var newRawObject = new RawObject( this.materialPerSmoothingGroup, this.objectName, groupName, this.activeMtlName );
 
 			// keep current buffers and indices forward
 			newRawObject.vertices = this.vertices;
@@ -592,7 +876,7 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		RawObject.prototype.verifyIndex = function () {
-			var index = this.buildIndex( this.activeMtlName, ( this.activeSmoothingGroup === 0 ) ? 0 : 1 );
+			var index = this.buildIndex( this.activeMtlName, this.activeSmoothingGroup );
 			this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ];
 			if ( ! Validator.isValid( this.rawObjectDescriptionInUse ) ) {
 
@@ -604,56 +888,56 @@ THREE.OBJLoader2 = (function () {
 
 		RawObject.prototype.processFaces = function ( buffer, bufferPointer, slashesCount ) {
 			var bufferLength = bufferPointer - 1;
-			var i;
+			var i, length;
 
 			// "f vertex ..."
 			if ( slashesCount === 0 ) {
 
-				for ( i = 2; i < bufferLength - 1; i ++ ) {
+				for ( i = 2, length = bufferLength - 1; i < length; i ++ ) {
 
-					this.attachFace( buffer[ 1     ] );
-					this.attachFace( buffer[ i     ] );
-					this.attachFace( buffer[ i + 1 ] );
+					this.buildFace( buffer[ 1     ] );
+					this.buildFace( buffer[ i     ] );
+					this.buildFace( buffer[ i + 1 ] );
 
 				}
 
 			// "f vertex/uv ..."
 			} else if  ( bufferLength === slashesCount * 2 ) {
 
-				for ( i = 3; i < bufferLength - 2; i += 2 ) {
+				for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
 
-					this.attachFace( buffer[ 1     ], buffer[ 2     ] );
-					this.attachFace( buffer[ i     ], buffer[ i + 1 ] );
-					this.attachFace( buffer[ i + 2 ], buffer[ i + 3 ] );
+					this.buildFace( buffer[ 1     ], buffer[ 2     ] );
+					this.buildFace( buffer[ i     ], buffer[ i + 1 ] );
+					this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );
 
 				}
 
 			// "f vertex/uv/normal ..."
 			} else if  ( bufferLength * 2 === slashesCount * 3 ) {
 
-				for ( i = 4; i < bufferLength - 3; i += 3 ) {
+				for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {
 
-					this.attachFace( buffer[ 1     ], buffer[ 2     ], buffer[ 3     ] );
-					this.attachFace( buffer[ i     ], buffer[ i + 1 ], buffer[ i + 2 ] );
-					this.attachFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );
+					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 ] );
 
 				}
 
 			// "f vertex//normal ..."
 			} else {
 
-				for ( i = 3; i < bufferLength - 2; i += 2 ) {
+				for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {
 
-					this.attachFace( buffer[ 1     ], undefined, buffer[ 2     ] );
-					this.attachFace( buffer[ i     ], undefined, buffer[ i + 1 ] );
-					this.attachFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
+					this.buildFace( buffer[ 1     ], undefined, buffer[ 2     ] );
+					this.buildFace( buffer[ i     ], undefined, buffer[ i + 1 ] );
+					this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );
 
 				}
 
 			}
 		};
 
-		RawObject.prototype.attachFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
+		RawObject.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
 			var indexV = ( parseInt( faceIndexV ) - this.globalVertexOffset ) * 3;
 			var vertices = this.rawObjectDescriptionInUse.vertices;
 			vertices.push( this.vertices[ indexV ++ ] );
@@ -696,28 +980,30 @@ THREE.OBJLoader2 = (function () {
 		 * 1: "f vertex			vertex 			..."
 		 */
 		RawObject.prototype.buildLineVvt = function ( lineArray ) {
-			var length = lineArray.length;
-			for ( var i = 1; i < length; i ++ ) {
+			for ( var i = 1, length = lineArray.length; i < length; i ++ ) {
+
 				this.vertices.push( parseInt( lineArray[ i ] ) );
 				this.uvs.push( parseInt( lineArray[ i ] ) );
+
 			}
 		};
 
 		RawObject.prototype.buildLineV = function ( lineArray ) {
-			var length = lineArray.length;
-			for ( var i = 1; i < length; i++ ) {
+			for ( var i = 1, length = lineArray.length; i < length; i++ ) {
+
 				this.vertices.push( parseInt( lineArray[ i ] ) );
+
 			}
 		};
 
 		/**
 		 * Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts
 		 */
-		RawObject.prototype.finalize = function ( meshCreator, inputObjectCount, debug ) {
+		RawObject.prototype.finalize = function () {
 			var temp = [];
 			var rawObjectDescription;
-			var index = 0;
 			var absoluteVertexCount = 0;
+			var absoluteIndexCount = 0;
 			var absoluteColorCount = 0;
 			var absoluteNormalCount = 0;
 			var absoluteUvCount = 0;
@@ -727,8 +1013,9 @@ THREE.OBJLoader2 = (function () {
 				rawObjectDescription = this.rawObjectDescriptions[ name ];
 				if ( rawObjectDescription.vertices.length > 0 ) {
 
-					temp[ index++ ] = rawObjectDescription;
+					temp.push( rawObjectDescription );
 					absoluteVertexCount += rawObjectDescription.vertices.length;
+					absoluteIndexCount += rawObjectDescription.indices.length;
 					absoluteColorCount += rawObjectDescription.colors.length;
 					absoluteUvCount += rawObjectDescription.uvs.length;
 					absoluteNormalCount += rawObjectDescription.normals.length;
@@ -737,22 +1024,20 @@ THREE.OBJLoader2 = (function () {
 			}
 
 			// don not continue if no result
-			var notEmpty = false;
-			if ( index > 0 ) {
-
-				if ( debug ) this.createReport( inputObjectCount, true );
-				meshCreator.buildMesh(
-					temp,
-					inputObjectCount,
-					absoluteVertexCount,
-					absoluteColorCount,
-					absoluteNormalCount,
-					absoluteUvCount
-				);
-				notEmpty = true;
+			var result = null;
+			if ( temp.length > 0 ) {
+
+				result = {
+					rawObjectDescriptions: temp,
+					absoluteVertexCount: absoluteVertexCount,
+					absoluteIndexCount: absoluteIndexCount,
+					absoluteColorCount: absoluteColorCount,
+					absoluteNormalCount: absoluteNormalCount,
+					absoluteUvCount: absoluteUvCount
+				};
 
 			}
-			return notEmpty;
+			return result;
 		};
 
 		RawObject.prototype.createReport = function ( inputObjectCount, printDirectly ) {
@@ -760,6 +1045,7 @@ THREE.OBJLoader2 = (function () {
 				name: this.objectName ? this.objectName : 'groups',
 				mtllibName: this.mtllibName,
 				vertexCount: this.vertices.length / 3,
+				indexCount: this.indices.length,
 				normalCount: this.normals.length / 3,
 				uvCount: this.uvs.length / 2,
 				smoothingGroupCount: this.smoothingGroupCount,
@@ -771,6 +1057,7 @@ THREE.OBJLoader2 = (function () {
 				console.log( 'Input Object number: ' + inputObjectCount + ' Object name: ' + report.name );
 				console.log( 'Mtllib name: ' + report.mtllibName );
 				console.log( 'Vertex count: ' + report.vertexCount );
+				console.log( 'Index count: ' + report.indexCount );
 				console.log( 'Normal count: ' + report.normalCount );
 				console.log( 'UV count: ' + report.uvCount );
 				console.log( 'SmoothingGroup count: ' + report.smoothingGroupCount );
@@ -802,6 +1089,7 @@ THREE.OBJLoader2 = (function () {
 			this.materialName = materialName;
 			this.smoothingGroup = smoothingGroup;
 			this.vertices = [];
+			this.indices = [];
 			this.colors = [];
 			this.uvs = [];
 			this.normals = [];
@@ -810,242 +1098,140 @@ THREE.OBJLoader2 = (function () {
 		return RawObjectDescription;
 	})();
 
-	/**
-	 * MeshCreator is used to transform RawObjectDescriptions to THREE.Mesh
-	 *
-	 * @class
-	 */
-	var MeshCreator = (function () {
-
-		function MeshCreator() {
-			this.sceneGraphBaseNode = null;
-			this.materials = null;
-			this.debug = false;
-			this.globalObjectCount = 1;
-
-			this.validated = false;
-		}
-
-		MeshCreator.prototype.setSceneGraphBaseNode = function ( sceneGraphBaseNode ) {
-			this.sceneGraphBaseNode = Validator.verifyInput( sceneGraphBaseNode, this.sceneGraphBaseNode );
-			this.sceneGraphBaseNode = Validator.verifyInput( this.sceneGraphBaseNode, new THREE.Group() );
+	OBJLoader2.prototype._checkFiles = function ( resources ) {
+		var resource;
+		var result = {
+			mtl: null,
+			obj: null
 		};
+		for ( var index in resources ) {
 
-		MeshCreator.prototype.setMaterials = function ( materials ) {
-			this.materials = Validator.verifyInput( materials, this.materials );
-			this.materials = Validator.verifyInput( this.materials, { materials: [] } );
+			resource = resources[ index ];
+			if ( ! Validator.isValid( resource.name ) ) continue;
+			if ( Validator.isValid( resource.content ) ) {
 
-			var defaultMaterial = this.materials[ 'defaultMaterial' ];
-			if ( ! defaultMaterial ) {
+				if ( resource.extension === 'OBJ' ) {
 
-				defaultMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } );
-				defaultMaterial.name = 'defaultMaterial';
-				this.materials[ 'defaultMaterial' ] = defaultMaterial;
+					// fast-fail on bad type
+					if ( ! ( resource.content instanceof Uint8Array ) ) throw 'Provided content is not of type arraybuffer! Aborting...';
+					result.obj = resource;
 
-			}
-			var vertexColorMaterial = this.materials[ 'vertexColorMaterial' ];
-			if ( ! vertexColorMaterial ) {
+				} else if ( resource.extension === 'MTL' && Validator.isValid( resource.name ) ) {
 
-				vertexColorMaterial = new THREE.MeshBasicMaterial( { color: 0xDCF1FF } );
-				vertexColorMaterial.name = 'vertexColorMaterial';
-				vertexColorMaterial.vertexColors = THREE.VertexColors;
-				this.materials[ 'vertexColorMaterial' ] = vertexColorMaterial;
+					if ( ! ( typeof( resource.content ) === 'string' || resource.content instanceof String ) ) throw 'Provided  content is not of type String! Aborting...';
+					result.mtl = resource;
 
-			}
-		};
+				} else if ( resource.extension === "ZIP" ) {
+					// ignore
 
-		MeshCreator.prototype.setDebug = function ( debug ) {
-			if ( debug === true || debug === false ) this.debug = debug;
-		};
-
-		MeshCreator.prototype.validate = function () {
-			if ( this.validated ) return;
+				} else {
 
-			this.setSceneGraphBaseNode( null );
-			this.setMaterials( null );
-			this.setDebug( null );
-			this.globalObjectCount = 1;
-		};
+					throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
 
-		MeshCreator.prototype.finalize = function () {
-			this.sceneGraphBaseNode = null;
-			this.materials = null;
-			this.validated = false;
-		};
+				}
 
-		/**
-		 * This is an internal function, but due to its importance to Parser it is documented.
-		 * RawObjectDescriptions are transformed to THREE.Mesh.
-		 * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
-		 * This method shall be overridden by the web worker implementation
-		 *
-		 * @param {RawObjectDescription[]} rawObjectDescriptions Array of descriptive information and data (vertices, normals, uvs) about the parsed object(s)
-		 * @param {number} inputObjectCount Number of objects already retrieved from OBJ
-		 * @param {number} absoluteVertexCount Sum of all vertices of all rawObjectDescriptions
-		 * @param {number} absoluteColorCount Sum of all vertex colors of all rawObjectDescriptions
-		 * @param {number} absoluteNormalCount Sum of all normals of all rawObjectDescriptions
-		 * @param {number} absoluteUvCount Sum of all uvs of all rawObjectDescriptions
-		 */
-		MeshCreator.prototype.buildMesh = function ( rawObjectDescriptions, inputObjectCount, absoluteVertexCount,
-													 absoluteColorCount, absoluteNormalCount, absoluteUvCount ) {
+			} else {
 
-			if ( this.debug ) console.log( 'MeshCreator.buildRawMeshData:\nInput object no.: ' + inputObjectCount );
+				// fast-fail on bad type
+				if ( ! ( typeof( resource.name ) === 'string' || resource.name instanceof String ) ) throw 'Provided file is not properly defined! Aborting...';
+				if ( resource.extension === 'OBJ' ) {
 
-			var bufferGeometry = new THREE.BufferGeometry();
-			var vertexBA = new THREE.BufferAttribute( new Float32Array( absoluteVertexCount ), 3 );
-			bufferGeometry.addAttribute( 'position', vertexBA );
+					result.obj = resource;
 
-			var colorBA;
-			if ( absoluteColorCount > 0 ) {
+				} else if ( resource.extension === 'MTL' ) {
 
-				colorBA = new THREE.BufferAttribute( new Float32Array( absoluteColorCount ), 3 );
-				bufferGeometry.addAttribute( 'color', colorBA );
+					result.mtl = resource;
 
-			}
+				} else if ( resource.extension === "ZIP" ) {
+					// ignore
 
-			var normalBA;
-			if ( absoluteNormalCount > 0 ) {
+				} else {
 
-				normalBA = new THREE.BufferAttribute( new Float32Array( absoluteNormalCount ), 3 );
-				bufferGeometry.addAttribute( 'normal', normalBA );
-
-			}
-			var uvBA;
-			if ( absoluteUvCount > 0 ) {
-
-				uvBA = new THREE.BufferAttribute( new Float32Array( absoluteUvCount ), 2 );
-				bufferGeometry.addAttribute( 'uv', uvBA );
+					throw 'Unidentified resource "' + resource.name + '": ' + resource.url;
 
+				}
 			}
+		}
 
-			var rawObjectDescription;
-			var material;
-			var materialName;
-			var createMultiMaterial = rawObjectDescriptions.length > 1;
-			var materials = [];
-			var materialIndex = 0;
-			var materialIndexMapping = [];
-			var selectedMaterialIndex;
-
-			var vertexBAOffset = 0;
-			var vertexGroupOffset = 0;
-			var vertexLength;
-			var colorBAOffset = 0;
-			var normalBAOffset = 0;
-			var uvBAOffset = 0;
-
-			if ( this.debug ) {
-				console.log( createMultiMaterial ? 'Creating Multi-Material' : 'Creating Material' + ' for object no.: ' + this.globalObjectCount );
-			}
+		return result;
+	};
 
-			for ( var oodIndex in rawObjectDescriptions ) {
-				rawObjectDescription = rawObjectDescriptions[ oodIndex ];
+	/**
+	 * Utility method for loading an mtl file according resource description.
+	 * @memberOf THREE.OBJLoader2
+	 *
+	 * @param {string} url URL to the file
+	 * @param {string} name The name of the object
+	 * @param {Object} content The file content as arraybuffer or text
+	 * @param {function} callbackOnLoad
+	 * @param {string} [crossOrigin] CORS value
+	 */
+	OBJLoader2.prototype.loadMtl = function ( url, name, content, callbackOnLoad, crossOrigin ) {
+		var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
+		resource.setContent( content );
+		this._loadMtl( resource, callbackOnLoad, crossOrigin );
+	};
 
-				materialName = rawObjectDescription.materialName;
-				material = colorBA ? this.materials[ 'vertexColorMaterial' ] : this.materials[ materialName ];
-				if ( ! material ) {
+	/**
+	 * Utility method for loading an mtl file according resource description.
+	 * @memberOf THREE.OBJLoader2
+	 *
+	 * @param {THREE.LoaderSupport.ResourceDescriptor} resource
+	 * @param {function} callbackOnLoad
+	 * @param {string} [crossOrigin] CORS value
+	 */
+	OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
+		if ( Validator.isValid( resource ) ) console.time( 'Loading MTL: ' + resource.name );
 
-					material = this.materials[ 'defaultMaterial' ];
-					if ( ! material ) console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName +
-													'" was defined without material! Assigning "defaultMaterial".' );
+		var materials = [];
+		var processMaterials = function ( materialCreator ) {
+			var materialCreatorMaterials = [];
+			if ( Validator.isValid( materialCreator ) ) {
 
-				}
-				// clone material in case flat shading is needed due to smoothingGroup 0
-				if ( rawObjectDescription.smoothingGroup === 0 ) {
+				materialCreator.preload();
+				materialCreatorMaterials = materialCreator.materials;
+				for ( var materialName in materialCreatorMaterials ) {
 
-					materialName = material.name + '_flat';
-					var materialClone = this.materials[ materialName ];
-					if ( ! materialClone ) {
+					if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
 
-						materialClone = material.clone();
-						materialClone.name = materialName;
-						materialClone.flatShading = true;
-						this.materials[ materialName ] = name;
+						materials[ materialName ] = materialCreatorMaterials[ materialName ];
 
 					}
-
 				}
+			}
 
-				vertexLength = rawObjectDescription.vertices.length;
-				if ( createMultiMaterial ) {
-
-					// re-use material if already used before. Reduces materials array size and eliminates duplicates
-					selectedMaterialIndex = materialIndexMapping[ materialName ];
-					if ( ! selectedMaterialIndex ) {
-
-						selectedMaterialIndex = materialIndex;
-						materialIndexMapping[ materialName ] = materialIndex;
-						materials.push( material );
-						materialIndex++;
-
-					}
+			if ( Validator.isValid( resource ) ) console.timeEnd( 'Loading MTL: ' + resource.name );
+			callbackOnLoad( materials );
+		};
 
-					bufferGeometry.addGroup( vertexGroupOffset, vertexLength / 3, selectedMaterialIndex );
-					vertexGroupOffset += vertexLength / 3;
-				}
+		var mtlLoader = new THREE.MTLLoader();
+		crossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );
+		mtlLoader.setCrossOrigin( crossOrigin );
 
-				vertexBA.set( rawObjectDescription.vertices, vertexBAOffset );
-				vertexBAOffset += vertexLength;
+		// fast-fail
+		if ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {
 
-				if ( colorBA ) {
+			processMaterials();
 
-					colorBA.set( rawObjectDescription.colors, colorBAOffset );
-					colorBAOffset += rawObjectDescription.colors.length;
+		} else {
 
-				}
+			mtlLoader.setPath( resource.path );
+			if ( Validator.isValid( resource.content ) ) {
 
-				if ( normalBA ) {
+				processMaterials( Validator.isValid( resource.content ) ? mtlLoader.parse( resource.content ) : null );
 
-					normalBA.set( rawObjectDescription.normals, normalBAOffset );
-					normalBAOffset += rawObjectDescription.normals.length;
+			} else if ( Validator.isValid( resource.url ) ) {
 
-				}
-				if ( uvBA ) {
+				var onError = function ( event ) {
+					var output = 'Error occurred while downloading "' + resource.url + '"';
+					console.error( output + ': ' + event );
+					throw output;
+				};
 
-					uvBA.set( rawObjectDescription.uvs, uvBAOffset );
-					uvBAOffset += rawObjectDescription.uvs.length;
-
-				}
-				if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex );
+				mtlLoader.load( resource.name, processMaterials, undefined, onError );
 
 			}
-			if ( ! normalBA ) bufferGeometry.computeVertexNormals();
-
-			if ( createMultiMaterial ) material = materials;
-			var mesh = new THREE.Mesh( bufferGeometry, material );
-			mesh.name = rawObjectDescription.groupName !== '' ? rawObjectDescription.groupName : rawObjectDescription.objectName;
-			this.sceneGraphBaseNode.add( mesh );
-
-			this.globalObjectCount++;
-		};
-
-		MeshCreator.prototype.printReport = function ( rawObjectDescription, selectedMaterialIndex ) {
-			var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n materialIndex: ' + selectedMaterialIndex : '';
-			console.log(
-				' Output Object no.: ' + this.globalObjectCount +
-				'\n objectName: ' + rawObjectDescription.objectName +
-				'\n groupName: ' + rawObjectDescription.groupName +
-				'\n materialName: ' + rawObjectDescription.materialName +
-				materialIndexLine +
-				'\n smoothingGroup: ' + rawObjectDescription.smoothingGroup +
-				'\n #vertices: ' + rawObjectDescription.vertices.length / 3 +
-				'\n #colors: ' + rawObjectDescription.colors.length / 3 +
-				'\n #uvs: ' + rawObjectDescription.uvs.length / 2 +
-				'\n #normals: ' + rawObjectDescription.normals.length / 3
-			);
-		};
-
-		return MeshCreator;
-	})();
-
-	OBJLoader2.prototype._buildWebWorkerCode = function ( funcBuildObject, funcBuildSingelton ) {
-		var workerCode = '';
-		workerCode += funcBuildObject( 'Consts', Consts );
-		workerCode += funcBuildObject( 'Validator', Validator );
-		workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
-		workerCode += funcBuildSingelton( 'RawObject', 'RawObject', RawObject );
-		workerCode += funcBuildSingelton( 'RawObjectDescription', 'RawObjectDescription', RawObjectDescription );
-		return workerCode;
+		}
 	};
 
 	return OBJLoader2;

+ 0 - 1455
examples/js/loaders/WWOBJLoader2.js

@@ -1,1455 +0,0 @@
-/**
-  * @author Kai Salmen / https://kaisalmen.de
-  * Development repository: https://github.com/kaisalmen/WWOBJLoader
-  */
-
-'use strict';
-
-if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
-
-/**
- * OBJ data will be loaded by dynamically created web worker.
- * First feed instructions with: prepareRun
- * Then: Execute with: run
- * @class
- */
-THREE.OBJLoader2.WWOBJLoader2 = (function () {
-
-	var WWOBJLOADER2_VERSION = '1.4.1';
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	function WWOBJLoader2() {
-		this._init();
-	}
-
-	WWOBJLoader2.prototype._init = function () {
-		console.log( "Using THREE.OBJLoader2.WWOBJLoader2 version: " + WWOBJLOADER2_VERSION );
-
-		// check worker support first
-		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
-		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
-		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
-
-		this.instanceNo = 0;
-		this.worker = null;
-		this.workerCode = null;
-		this.debug = false;
-
-		this.sceneGraphBaseNode = null;
-		this.streamMeshes = true;
-		this.meshStore = null;
-		this.modelName = '';
-		this.validated = false;
-		this.running = false;
-		this.requestTerminate = false;
-
-		this.clearAllCallbacks();
-
-		this.manager = THREE.DefaultLoadingManager;
-		this.fileLoader = new THREE.FileLoader( this.manager );
-		this.mtlLoader = null;
-		this.crossOrigin = null;
-
-		this.dataAvailable = false;
-		this.objAsArrayBuffer = null;
-		this.fileObj = null;
-		this.pathObj = null;
-
-		this.fileMtl = null;
-		this.mtlAsString = null;
-		this.texturePath = null;
-
-		this.materials = [];
-		this.counter = 0;
-	};
-
-	/**
-	 * Enable or disable debug logging.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {boolean} enabled True or false
-	 */
-	WWOBJLoader2.prototype.setDebug = function ( enabled ) {
-		this.debug = enabled;
-	};
-
-	/**
-	 * Sets the CORS string to be used.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {string} crossOrigin CORS value
-	 */
-	WWOBJLoader2.prototype.setCrossOrigin = function ( crossOrigin ) {
-		this.crossOrigin = crossOrigin;
-	};
-
-	/**
-	 * Register callback function that is invoked by internal function "_announceProgress" to print feedback.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {callback} callbackProgress Callback function for described functionality
-	 */
-	WWOBJLoader2.prototype.registerCallbackProgress = function ( callbackProgress ) {
-		if ( Validator.isValid( callbackProgress ) ) this.callbacks.progress.push( callbackProgress );
-	};
-
-	/**
-	 * Register callback function that is called once loading of the complete model is completed.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {callback} callbackCompletedLoading Callback function for described functionality
-	 */
-	WWOBJLoader2.prototype.registerCallbackCompletedLoading = function ( callbackCompletedLoading ) {
-		if ( Validator.isValid( callbackCompletedLoading ) ) this.callbacks.completedLoading.push( callbackCompletedLoading );
-	};
-
-	/**
-	 * Register callback function that is called once materials have been loaded. It allows to alter and return materials.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {callback} callbackMaterialsLoaded Callback function for described functionality
-	 */
-	WWOBJLoader2.prototype.registerCallbackMaterialsLoaded = function ( callbackMaterialsLoaded ) {
-		if ( Validator.isValid( callbackMaterialsLoaded ) ) this.callbacks.materialsLoaded.push( callbackMaterialsLoaded );
-	};
-
-	/**
-	 * Register callback function that is called every time a mesh was loaded.
-	 * Use {@link THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride} for alteration instructions (geometry, material or disregard mesh).
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {callback} callbackMeshLoaded Callback function for described functionality
-	 */
-	WWOBJLoader2.prototype.registerCallbackMeshLoaded = function ( callbackMeshLoaded ) {
-		if ( Validator.isValid( callbackMeshLoaded ) ) this.callbacks.meshLoaded.push( callbackMeshLoaded );
-	};
-
-	/**
-	 * Register callback function that is called to report an error that prevented loading.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {callback} callbackErrorWhileLoading Callback function for described functionality
-	 */
-	WWOBJLoader2.prototype.registerCallbackErrorWhileLoading = function ( callbackErrorWhileLoading ) {
-		if ( Validator.isValid( callbackErrorWhileLoading ) ) this.callbacks.errorWhileLoading.push( callbackErrorWhileLoading );
-	};
-
-	/**
-	 * Clears all registered callbacks.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 */
-	WWOBJLoader2.prototype.clearAllCallbacks = function () {
-		this.callbacks = {
-			progress: [],
-			completedLoading: [],
-			errorWhileLoading: [],
-			materialsLoaded: [],
-			meshLoaded: []
-		};
-	};
-
-	/**
-	 * Call requestTerminate to terminate the web worker and free local resource after execution.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {boolean} requestTerminate True or false
-	 */
-	WWOBJLoader2.prototype.setRequestTerminate = function ( requestTerminate ) {
-		this.requestTerminate = requestTerminate === true;
-	};
-
-	WWOBJLoader2.prototype._validate = function () {
-		if ( this.validated ) return;
-		if ( ! Validator.isValid( this.worker ) ) {
-
-			this._buildWebWorkerCode();
-			var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } );
-			this.worker = new Worker( window.URL.createObjectURL( blob ) );
-
-			var scope = this;
-			var scopeFunction = function ( e ) {
-				scope._receiveWorkerMessage( e );
-			};
-			this.worker.addEventListener( 'message', scopeFunction, false );
-
-		}
-
-		this.sceneGraphBaseNode = null;
-		this.streamMeshes = true;
-		this.meshStore = [];
-		this.modelName = '';
-		this.validated = true;
-		this.running = true;
-		this.requestTerminate = false;
-
-		this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
-		this.mtlLoader = Validator.verifyInput( this.mtlLoader, new THREE.MTLLoader() );
-		if ( Validator.isValid( this.crossOrigin ) ) this.mtlLoader.setCrossOrigin( this.crossOrigin );
-
-		this.dataAvailable = false;
-		this.fileObj = null;
-		this.pathObj = null;
-		this.fileMtl = null;
-		this.texturePath = null;
-
-		this.objAsArrayBuffer = null;
-		this.mtlAsString = null;
-
-		this.materials = [];
-		var defaultMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } );
-		defaultMaterial.name = 'defaultMaterial';
-		this.materials[ defaultMaterial.name ] = defaultMaterial;
-
-		var vertexColorMaterial = new THREE.MeshBasicMaterial( { color: 0xDCF1FF } );
-		vertexColorMaterial.name = 'vertexColorMaterial';
-		vertexColorMaterial.vertexColors = THREE.VertexColors;
-		this.materials[ 'vertexColorMaterial' ] = vertexColorMaterial;
-
-		this.counter = 0;
-	};
-
-	/**
-	 * Set all parameters for required for execution of "run".
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 *
-	 * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
-	 */
-	WWOBJLoader2.prototype.prepareRun = function ( params ) {
-		this._validate();
-		this.dataAvailable = params.dataAvailable;
-		this.modelName = params.modelName;
-		console.time( 'WWOBJLoader2' );
-		if ( this.dataAvailable ) {
-
-			// fast-fail on bad type
-			if ( ! ( params.objAsArrayBuffer instanceof Uint8Array ) ) {
-				throw 'Provided input is not of type arraybuffer! Aborting...';
-			}
-
-			this.worker.postMessage( {
-				cmd: 'init',
-				debug: this.debug
-			} );
-
-			this.objAsArrayBuffer = params.objAsArrayBuffer;
-			this.mtlAsString = params.mtlAsString;
-
-		} else {
-
-			// fast-fail on bad type
-			if ( ! ( typeof( params.fileObj ) === 'string' || params.fileObj instanceof String ) ) {
-				throw 'Provided file is not properly defined! Aborting...';
-			}
-
-			this.worker.postMessage( {
-				cmd: 'init',
-				debug: this.debug
-			} );
-
-			this.fileObj = params.fileObj;
-			this.pathObj = params.pathObj;
-			this.fileMtl = params.fileMtl;
-
-		}
-		this.setRequestTerminate( params.requestTerminate );
-		this.pathTexture = params.pathTexture;
-		this.sceneGraphBaseNode = params.sceneGraphBaseNode;
-		this.streamMeshes = params.streamMeshes;
-		if ( ! this.streamMeshes ) this.meshStore = [];
-	};
-
-	/**
-	 * Run the loader according the preparation instruction provided in "prepareRun".
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
-	 */
-	WWOBJLoader2.prototype.run = function () {
-		var scope = this;
-		var processLoadedMaterials = function ( materialCreator ) {
-			var materialCreatorMaterials = [];
-			var materialNames = [];
-			if ( Validator.isValid( materialCreator ) ) {
-
-				materialCreator.preload();
-				materialCreatorMaterials = materialCreator.materials;
-				for ( var materialName in materialCreatorMaterials ) {
-
-					if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {
-
-						materialNames.push( materialName );
-						scope.materials[ materialName ] = materialCreatorMaterials[ materialName ];
-
-					}
-
-				}
-
-			}
-			scope.worker.postMessage( {
-				cmd: 'setMaterials',
-				materialNames: materialNames
-			} );
-
-			var materialsFromCallback;
-			var callbackMaterialsLoaded;
-			for ( var index in scope.callbacks.materialsLoaded ) {
-
-				callbackMaterialsLoaded = scope.callbacks.materialsLoaded[ index ];
-				materialsFromCallback = callbackMaterialsLoaded( scope.materials );
-				if ( Validator.isValid( materialsFromCallback ) ) scope.materials = materialsFromCallback;
-
-			}
-			if ( scope.dataAvailable && scope.objAsArrayBuffer ) {
-
-				scope.worker.postMessage({
-					cmd: 'run',
-					objAsArrayBuffer: scope.objAsArrayBuffer
-				}, [ scope.objAsArrayBuffer.buffer ] );
-
-			} else {
-
-				var refPercentComplete = 0;
-				var percentComplete = 0;
-				var onLoad = function ( objAsArrayBuffer ) {
-
-					scope._announceProgress( 'Running web worker!' );
-					scope.objAsArrayBuffer = new Uint8Array( objAsArrayBuffer );
-					scope.worker.postMessage( {
-						cmd: 'run',
-						objAsArrayBuffer: scope.objAsArrayBuffer
-					}, [ scope.objAsArrayBuffer.buffer ] );
-
-				};
-
-				var onProgress = function ( event ) {
-					if ( ! event.lengthComputable ) return;
-
-					percentComplete = Math.round( event.loaded / event.total * 100 );
-					if ( percentComplete > refPercentComplete ) {
-
-						refPercentComplete = percentComplete;
-						var output = 'Download of "' + scope.fileObj + '": ' + percentComplete + '%';
-						console.log( output );
-						scope._announceProgress( output );
-
-					}
-				};
-
-				var onError = function ( event ) {
-					var output = 'Error occurred while downloading "' + scope.fileObj + '"';
-					console.error( output + ': ' + event );
-					scope._announceProgress( output );
-					scope._finalize( 'error' );
-
-				};
-
-				scope.fileLoader.setPath( scope.pathObj );
-				scope.fileLoader.setResponseType( 'arraybuffer' );
-				scope.fileLoader.load( scope.fileObj, onLoad, onProgress, onError );
-			}
-			console.timeEnd( 'Loading MTL textures' );
-		};
-
-
-		this.mtlLoader.setPath( this.pathTexture );
-		if ( this.dataAvailable ) {
-
-			processLoadedMaterials( Validator.isValid( this.mtlAsString ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
-
-		} else {
-
-			if ( Validator.isValid( this.fileMtl ) ) {
-
-				var onError = function ( event ) {
-					var output = 'Error occurred while downloading "' + scope.fileMtl + '"';
-					console.error( output + ': ' + event );
-					scope._announceProgress( output );
-					scope._finalize( 'error' );
-				};
-
-				this.mtlLoader.load( this.fileMtl, processLoadedMaterials, undefined, onError );
-
-			} else {
-
-				processLoadedMaterials();
-
-			}
-
-		}
-	};
-
-	WWOBJLoader2.prototype._receiveWorkerMessage = function ( event ) {
-		var payload = event.data;
-
-		switch ( payload.cmd ) {
-			case 'objData':
-
-				this.counter++;
-				var meshName = payload.meshName;
-
-				var bufferGeometry = new THREE.BufferGeometry();
-				bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( payload.vertices ), 3 ) );
-				var haveVertexColors = Validator.isValid( payload.colors );
-				if ( haveVertexColors ) {
-
-					bufferGeometry.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( payload.colors ), 3 ) );
-
-				}
-				if ( Validator.isValid( payload.normals ) ) {
-
-					bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.normals ), 3 ) );
-
-				} else {
-
-					bufferGeometry.computeVertexNormals();
-
-				}
-				if ( Validator.isValid( payload.uvs ) ) {
-
-					bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.uvs ), 2 ) );
-
-				}
-
-				var materialDescriptions = payload.materialDescriptions;
-				var materialDescription;
-				var material;
-				var materialName;
-				var createMultiMaterial = payload.multiMaterial;
-				var multiMaterials = [];
-
-				var key;
-				for ( key in materialDescriptions ) {
-
-					materialDescription = materialDescriptions[ key ];
-					material = haveVertexColors ? this.materials[ 'vertexColorMaterial' ] : this.materials[ materialDescription.name ];
-					if ( ! material ) material = this.materials[ 'defaultMaterial' ];
-
-					if ( materialDescription.default ) {
-
-						material = this.materials[ 'defaultMaterial' ];
-
-					} else if ( materialDescription.flat ) {
-
-						materialName = material.name + '_flat';
-						var materialClone = this.materials[ materialName ];
-						if ( ! materialClone ) {
-
-							materialClone = material.clone();
-							materialClone.name = materialName;
-							materialClone.flatShading = true;
-							this.materials[ materialName ] = name;
-
-						}
-
-					}
-
-					if ( materialDescription.vertexColors ) material.vertexColors = THREE.VertexColors;
-					if ( createMultiMaterial ) multiMaterials.push( material );
-
-				}
-				if ( createMultiMaterial ) {
-
-					material = multiMaterials;
-					var materialGroups = payload.materialGroups;
-					var materialGroup;
-					for ( key in materialGroups ) {
-
-						materialGroup = materialGroups[ key ];
-						bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index );
-
-					}
-
-				}
-
-				var callbackMeshLoaded;
-				var callbackMeshLoadedResult;
-				var disregardMesh = false;
-				for ( var index in this.callbacks.meshLoaded ) {
-
-					callbackMeshLoaded = this.callbacks.meshLoaded[ index ];
-					callbackMeshLoadedResult = callbackMeshLoaded( meshName, bufferGeometry, material );
-
-					if ( Validator.isValid( callbackMeshLoadedResult ) ) {
-
-						if ( callbackMeshLoadedResult.disregardMesh ) {
-
-							// if one callback disregards the mesh, then processing stops
-							disregardMesh = true;
-							break;
-
-						}
-						if ( callbackMeshLoadedResult.replaceBufferGeometry ) bufferGeometry = callbackMeshLoadedResult.bufferGeometry;
-						if ( callbackMeshLoadedResult.replaceMaterial ) material = callbackMeshLoadedResult.material;
-
-					}
-
-				}
-
-				if ( !disregardMesh ) {
-
-					var mesh = new THREE.Mesh( bufferGeometry, material );
-					mesh.name = meshName;
-
-					if ( this.streamMeshes ) {
-
-						this.sceneGraphBaseNode.add( mesh );
-
-					} else {
-
-						this.meshStore.push( mesh );
-
-					}
-					this._announceProgress( 'Adding mesh (' + this.counter + '):', meshName );
-
-				} else {
-
-					this._announceProgress( 'Removing mesh:', meshName );
-
-				}
-				break;
-
-			case 'complete':
-
-				if ( ! this.streamMeshes ) {
-
-					for ( var meshStoreKey in this.meshStore ) {
-
-						if ( this.meshStore.hasOwnProperty( meshStoreKey ) ) this.sceneGraphBaseNode.add( this.meshStore[ meshStoreKey ] );
-
-					}
-
-				}
-
-				console.timeEnd( 'WWOBJLoader2' );
-				if ( Validator.isValid( payload.msg ) ) {
-
-					this._announceProgress( payload.msg );
-
-				} else {
-
-					this._announceProgress( '' );
-
-				}
-
-				this._finalize( 'complete' );
-				break;
-
-			case 'report_progress':
-				this._announceProgress( '', payload.output );
-				break;
-
-			default:
-				console.error( 'Received unknown command: ' + payload.cmd );
-				break;
-
-		}
-	};
-
-	WWOBJLoader2.prototype._terminate = function () {
-		if ( Validator.isValid( this.worker ) ) {
-
-			if ( this.running ) throw 'Unable to gracefully terminate worker as it is currently running!';
-
-			this.worker.terminate();
-			this.worker = null;
-			this.workerCode = null;
-			this._finalize( 'terminate' );
-
-		}
-		this.fileLoader = null;
-		this.mtlLoader = null;
-	};
-
-	WWOBJLoader2.prototype._finalize = function ( reason, requestTerminate ) {
-		this.running = false;
-		var index;
-		var callback;
-
-		if ( reason === 'complete' ) {
-
-			for ( index in this.callbacks.completedLoading ) {
-
-				callback = this.callbacks.completedLoading[ index ];
-				callback( this.modelName, this.instanceNo, this.requestTerminate );
-
-			}
-
-		} else if ( reason === 'error' ) {
-
-			for ( index in this.callbacks.errorWhileLoading ) {
-
-				callback = this.callbacks.errorWhileLoading[ index ];
-				callback( this.modelName, this.instanceNo, this.requestTerminate );
-
-			}
-
-		}
-		this.validated = false;
-
-		this.setRequestTerminate( requestTerminate );
-
-		if ( this.requestTerminate ) {
-			this._terminate();
-		}
-	};
-
-	WWOBJLoader2.prototype._announceProgress = function ( baseText, text ) {
-		var output = Validator.isValid( baseText ) ? baseText: "";
-		output = Validator.isValid( text ) ? output + " " + text : output;
-
-		var callbackProgress;
-		for ( var index in this.callbacks.progress ) {
-
-			callbackProgress = this.callbacks.progress[ index ];
-			callbackProgress( output, this.instanceNo );
-
-		}
-
-		if ( this.debug ) console.log( output );
-	};
-
-	WWOBJLoader2.prototype._buildWebWorkerCode = function ( existingWorkerCode ) {
-		if ( Validator.isValid( existingWorkerCode ) ) this.workerCode = existingWorkerCode;
-		if ( ! Validator.isValid( this.workerCode ) ) {
-
-			console.time( 'buildWebWorkerCode' );
-			var wwDef = (function () {
-
-				function WWOBJLoader() {
-					this.wwMeshCreator = new WWMeshCreator();
-					this.parser = new Parser( this.wwMeshCreator );
-					this.validated = false;
-					this.cmdState = 'created';
-
-					this.debug = false;
-				}
-
-				/**
-				 * Allows to set debug mode for the parser and the meshCreatorDebug
-				 *
-				 * @param parserDebug
-				 * @param meshCreatorDebug
-				 */
-				WWOBJLoader.prototype.setDebug = function ( parserDebug, meshCreatorDebug ) {
-					this.parser.setDebug( parserDebug );
-					this.wwMeshCreator.setDebug( meshCreatorDebug );
-				};
-
-				/**
-				 * Validate status, then parse arrayBuffer, finalize and return objGroup
-				 *
-				 * @param arrayBuffer
-				 */
-				WWOBJLoader.prototype.parse = function ( arrayBuffer ) {
-					console.log( 'Parsing arrayBuffer...' );
-					console.time( 'parseArrayBuffer' );
-
-					this.validate();
-					this.parser.parseArrayBuffer( arrayBuffer );
-					var objGroup = this._finalize();
-
-					console.timeEnd( 'parseArrayBuffer' );
-
-					return objGroup;
-				};
-
-				WWOBJLoader.prototype.validate = function () {
-					if ( this.validated ) return;
-
-					this.parser.validate();
-					this.wwMeshCreator.validate();
-
-					this.validated = true;
-				};
-
-				WWOBJLoader.prototype._finalize = function () {
-					console.log( 'Global output object count: ' + this.wwMeshCreator.globalObjectCount );
-					this.parser.finalize();
-					this.wwMeshCreator.finalize();
-					this.validated = false;
-				};
-
-				WWOBJLoader.prototype.init = function ( payload ) {
-					this.cmdState = 'init';
-					this.setDebug( payload.debug, payload.debug );
-				};
-
-				WWOBJLoader.prototype.setMaterials = function ( payload ) {
-					this.cmdState = 'setMaterials';
-					this.wwMeshCreator.setMaterials( payload.materialNames );
-				};
-
-				WWOBJLoader.prototype.run = function ( payload ) {
-					this.cmdState = 'run';
-
-					this.parse( payload.objAsArrayBuffer );
-					console.log( 'OBJ loading complete!' );
-
-					this.cmdState = 'complete';
-					self.postMessage( {
-						cmd: this.cmdState,
-						msg: null
-					} );
-				};
-
-				return WWOBJLoader;
-			})();
-
-			var wwMeshCreatorDef = (function () {
-
-				function WWMeshCreator() {
-					this.materials = null;
-					this.debug = false;
-					this.globalObjectCount = 1;
-					this.validated = false;
-				}
-
-				WWMeshCreator.prototype.setMaterials = function ( materials ) {
-					this.materials = Validator.verifyInput( materials, this.materials );
-					this.materials = Validator.verifyInput( this.materials, { materials: [] } );
-				};
-
-				WWMeshCreator.prototype.setDebug = function ( debug ) {
-					if ( debug === true || debug === false ) this.debug = debug;
-				};
-
-				WWMeshCreator.prototype.validate = function () {
-					if ( this.validated ) return;
-
-					this.setMaterials( null );
-					this.setDebug( null );
-					this.globalObjectCount = 1;
-				};
-
-				WWMeshCreator.prototype.finalize = function () {
-					this.materials = null;
-					this.validated = false;
-				};
-
-				/**
-				 * RawObjectDescriptions are transformed to THREE.Mesh.
-				 * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
-				 *
-				 * @param rawObjectDescriptions
-				 * @param inputObjectCount
-				 * @param absoluteVertexCount
-				 * @param absoluteNormalCount
-				 * @param absoluteUvCount
-				 */
-				WWMeshCreator.prototype.buildMesh = function ( rawObjectDescriptions, inputObjectCount, absoluteVertexCount,
-															   absoluteColorCount, absoluteNormalCount, absoluteUvCount ) {
-					if ( this.debug ) console.log( 'OBJLoader.buildMesh:\nInput object no.: ' + inputObjectCount );
-
-					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 rawObjectDescription;
-					var materialDescription;
-					var materialDescriptions = [];
-
-					var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
-					var materialIndex = 0;
-					var materialIndexMapping = [];
-					var selectedMaterialIndex;
-					var materialGroup;
-					var materialGroups = [];
-
-					var vertexFAOffset = 0;
-					var vertexGroupOffset = 0;
-					var vertexLength;
-					var colorFAOffset = 0;
-					var normalFAOffset = 0;
-					var uvFAOffset = 0;
-
-					for ( var oodIndex in rawObjectDescriptions ) {
-						if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
-						rawObjectDescription = rawObjectDescriptions[ oodIndex ];
-
-						materialDescription = {
-							name: rawObjectDescription.materialName,
-							flat: false,
-							vertexColors: false,
-							default: false
-						};
-						if ( this.materials[ materialDescription.name ] === null ) {
-
-							materialDescription.default = true;
-							console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' );
-
-						}
-						// Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
-						if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;
-
-						vertexLength = rawObjectDescription.vertices.length;
-						if ( createMultiMaterial ) {
-
-							// re-use material if already used before. Reduces materials array size and eliminates duplicates
-
-							selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
-							if ( ! selectedMaterialIndex ) {
-
-								selectedMaterialIndex = materialIndex;
-								materialIndexMapping[ materialDescription.name ] = materialIndex;
-								materialDescriptions.push( materialDescription );
-								materialIndex++;
-
-							}
-							materialGroup = {
-								start: vertexGroupOffset,
-								count: vertexLength / 3,
-								index: selectedMaterialIndex
-							};
-							materialGroups.push( materialGroup );
-							vertexGroupOffset += vertexLength / 3;
-
-						} else {
-
-							materialDescriptions.push( materialDescription );
-
-						}
-
-						vertexFA.set( rawObjectDescription.vertices, vertexFAOffset );
-						vertexFAOffset += vertexLength;
-
-						if ( colorFA ) {
-
-							colorFA.set( rawObjectDescription.colors, colorFAOffset );
-							colorFAOffset += rawObjectDescription.colors.length;
-							materialDescription.vertexColors = true;
-
-						}
-
-						if ( normalFA ) {
-
-							normalFA.set( rawObjectDescription.normals, normalFAOffset );
-							normalFAOffset += rawObjectDescription.normals.length;
-
-						}
-						if ( uvFA ) {
-
-							uvFA.set( rawObjectDescription.uvs, uvFAOffset );
-							uvFAOffset += rawObjectDescription.uvs.length;
-
-						}
-						if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex );
-
-					}
-
-					self.postMessage(
-						{
-							cmd: 'objData',
-							meshName: rawObjectDescription.groupName !== '' ? rawObjectDescription.groupName : rawObjectDescription.objectName,
-							multiMaterial: createMultiMaterial,
-							materialDescriptions: materialDescriptions,
-							materialGroups: materialGroups,
-							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
-					);
-
-					this.globalObjectCount++;
-				};
-
-				WWMeshCreator.prototype.printReport = function ( rawObjectDescription, selectedMaterialIndex ) {
-					var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n materialIndex: ' + selectedMaterialIndex : '';
-					console.log(
-						' Output Object no.: ' + this.globalObjectCount +
-						'\n objectName: ' + rawObjectDescription.objectName +
-						'\n groupName: ' + rawObjectDescription.groupName +
-						'\n materialName: ' + rawObjectDescription.materialName +
-						materialIndexLine +
-						'\n smoothingGroup: ' + rawObjectDescription.smoothingGroup +
-						'\n #vertices: ' + rawObjectDescription.vertices.length / 3 +
-						'\n #colors: ' + rawObjectDescription.colors.length / 3 +
-						'\n #uvs: ' + rawObjectDescription.uvs.length / 2 +
-						'\n #normals: ' + rawObjectDescription.normals.length / 3
-					);
-				};
-
-				return WWMeshCreator;
-			})();
-
-			var wwObjLoaderRunnerDef = (function () {
-
-				function WWOBJLoaderRunner() {
-					self.addEventListener( 'message', this.runner, false );
-				}
-
-				WWOBJLoaderRunner.prototype.runner = function ( event ) {
-					var payload = event.data;
-
-					console.log( 'Command state before: ' + WWOBJLoaderRef.cmdState );
-
-					switch ( payload.cmd ) {
-						case 'init':
-
-							WWOBJLoaderRef.init( payload );
-							break;
-
-						case 'setMaterials':
-
-							WWOBJLoaderRef.setMaterials( payload );
-							break;
-
-						case 'run':
-
-							WWOBJLoaderRef.run( payload );
-							break;
-
-						default:
-
-							console.error( 'OBJLoader: Received unknown command: ' + payload.cmd );
-							break;
-
-					}
-
-					console.log( 'Command state after: ' + WWOBJLoaderRef.cmdState );
-				};
-
-				return WWOBJLoaderRunner;
-			})();
-
-			var buildObject = function ( fullName, object ) {
-				var objectString = fullName + ' = {\n';
-				var part;
-				for ( var name in object ) {
-
-					part = object[ name ];
-					if ( typeof( part ) === 'string' || part instanceof String ) {
-
-						part = part.replace( '\n', '\\n' );
-						part = part.replace( '\r', '\\r' );
-						objectString += '\t' + name + ': "' + part + '",\n';
-
-					} else if ( part instanceof Array ) {
-
-						objectString += '\t' + name + ': [' + part + '],\n';
-
-					} else if ( Number.isInteger( part ) ) {
-
-						objectString += '\t' + name + ': ' + part + ',\n';
-
-					} else if ( typeof part === 'function' ) {
-
-						objectString += '\t' + name + ': ' + part + ',\n';
-
-					}
-
-				}
-				objectString += '}\n\n';
-
-				return objectString;
-			};
-
-			var buildSingelton = function ( fullName, internalName, object ) {
-				var objectString = fullName + ' = (function () {\n\n';
-				objectString += '\t' + object.prototype.constructor.toString() + '\n\n';
-
-				var funcString;
-				var objectPart;
-				for ( var name in object.prototype ) {
-
-					objectPart = object.prototype[ name ];
-					if ( typeof objectPart === 'function' ) {
-
-						funcString = objectPart.toString();
-						objectString += '\t' + internalName + '.prototype.' + name + ' = ' + funcString + ';\n\n';
-
-					}
-
-				}
-				objectString += '\treturn ' + internalName + ';\n';
-				objectString += '})();\n\n';
-
-				return objectString;
-			};
-
-			this.workerCode = '';
-			this.workerCode += '/**\n';
-			this.workerCode += '  * This code was constructed by WWOBJLoader2._buildWebWorkerCode\n';
-			this.workerCode += '  */\n\n';
-
-			// parser re-construction
-			this.workerCode += THREE.OBJLoader2.prototype._buildWebWorkerCode( buildObject, buildSingelton );
-
-			// web worker construction
-			this.workerCode += buildSingelton( 'WWOBJLoader', 'WWOBJLoader', wwDef );
-			this.workerCode += buildSingelton( 'WWMeshCreator', 'WWMeshCreator', wwMeshCreatorDef );
-			this.workerCode += 'WWOBJLoaderRef = new WWOBJLoader();\n\n';
-			this.workerCode += buildSingelton( 'WWOBJLoaderRunner', 'WWOBJLoaderRunner', wwObjLoaderRunnerDef );
-			this.workerCode += 'new WWOBJLoaderRunner();\n\n';
-
-			console.timeEnd( 'buildWebWorkerCode' );
-		}
-
-		return this.workerCode;
-	};
-
-	return WWOBJLoader2;
-
-})();
-
-/**
- * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ from given ArrayBuffer and MTL from given String.
- *
- * @param {string} modelName Overall name of the model
- * @param {Uint8Array} objAsArrayBuffer OBJ file content as ArrayBuffer
- * @param {string} pathTexture Path to texture files
- * @param {string} mtlAsString MTL file content as string
- *
- * @returns {{modelName: string, dataAvailable: boolean, objAsArrayBuffer: null, pathTexture: null, mtlAsString: null, sceneGraphBaseNode: null, streamMeshes: boolean, requestTerminate: boolean}}
- * @constructor
- */
-THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer = function ( modelName, objAsArrayBuffer, pathTexture, mtlAsString ) {
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	return {
-
-		/**
-		 * {@link THREE.Object3D} where meshes will be attached.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer
-		 *
-		 * @param {THREE.Object3D} sceneGraphBaseNode Scene graph object
-		 */
-		setSceneGraphBaseNode: function ( sceneGraphBaseNode ) {
-			this.sceneGraphBaseNode = Validator.verifyInput( sceneGraphBaseNode, null );
-		},
-
-		/**
-		 * Singles meshes are directly integrated into scene when loaded or later.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer
-		 *
-		 * @param {boolean} streamMeshes=true Default is true
-		 */
-		setStreamMeshes: function ( streamMeshes ) {
-			this.streamMeshes = streamMeshes !== false;
-		},
-
-		/**
-		 * Request termination of web worker and free local resources after execution.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer
-		 *
-		 * @param {boolean} requestTerminate=false Default is false
-		 */
-		setRequestTerminate: function ( requestTerminate ) {
-			this.requestTerminate = requestTerminate === true;
-		},
-
-		/**
-		 * Returns all callbacks as {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks}
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer
-		 *
-		 * @returns {THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks}
-		 */
-		getCallbacks: function () {
-			return this.callbacks;
-		},
-		modelName: Validator.verifyInput( modelName, '' ),
-		dataAvailable: true,
-		objAsArrayBuffer: Validator.verifyInput( objAsArrayBuffer, null ),
-		pathTexture: Validator.verifyInput( pathTexture, null ),
-		mtlAsString: Validator.verifyInput( mtlAsString, null ),
-		sceneGraphBaseNode: null,
-		streamMeshes: true,
-		requestTerminate: false,
-		callbacks: new THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks()
-	};
-};
-
-/**
- * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ and MTL from files.
- *
- * @param {string} modelName Overall name of the model
- * @param {string} pathObj Path to OBJ file
- * @param {string} fileObj OBJ file name
- * @param {string} pathTexture Path to texture files
- * @param {string} fileMtl MTL file name
- *
- * @returns {{modelName: string, dataAvailable: boolean, pathObj: null, fileObj: null, pathTexture: null, fileMtl: null, sceneGraphBaseNode: null, streamMeshes: boolean,  requestTerminate: boolean}}
- * @constructor
- */
-THREE.OBJLoader2.WWOBJLoader2.PrepDataFile = function ( modelName, pathObj, fileObj, pathTexture, fileMtl ) {
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	return {
-
-		/**
-		 * {@link THREE.Object3D} where meshes will be attached.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataFile
-		 *
-		 * @param {THREE.Object3D} sceneGraphBaseNode Scene graph object
-		 */
-		setSceneGraphBaseNode: function ( sceneGraphBaseNode ) {
-			this.sceneGraphBaseNode = Validator.verifyInput( sceneGraphBaseNode, null );
-		},
-
-		/**
-		 * Singles meshes are directly integrated into scene when loaded or later.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataFile
-		 *
-		 * @param {boolean} streamMeshes=true Default is true
-		 */
-		setStreamMeshes: function ( streamMeshes ) {
-			this.streamMeshes = streamMeshes !== false;
-		},
-
-		/**
-		 * Request termination of web worker and free local resources after execution.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataFile
-		 *
-		 * @param {boolean} requestTerminate=false Default is false
-		 */
-		setRequestTerminate: function ( requestTerminate ) {
-			this.requestTerminate = requestTerminate === true;
-		},
-
-		/**
-		 * Returns all callbacks as {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks}
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataFile
-		 *
-		 * @returns {THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks}
-		 */
-		getCallbacks: function () {
-			return this.callbacks;
-		},
-		modelName: Validator.verifyInput( modelName, '' ),
-		dataAvailable: false,
-		pathObj: Validator.verifyInput( pathObj, null ),
-		fileObj: Validator.verifyInput( fileObj, null ),
-		pathTexture: Validator.verifyInput( pathTexture, null ),
-		fileMtl: Validator.verifyInput( fileMtl, null ),
-		sceneGraphBaseNode: null,
-		streamMeshes: true,
-		requestTerminate: false,
-		callbacks: new THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks()
-	};
-};
-
-/**
- * Callbacks utilized by functions working with {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
- *
- * @returns {{registerCallbackProgress: THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks.registerCallbackProgress, registerCallbackCompletedLoading: THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks.registerCallbackCompletedLoading, registerCallbackMaterialsLoaded: THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks.registerCallbackMaterialsLoaded, registerCallbackMeshLoaded: THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks.registerCallbackMeshLoaded, registerCallbackErrorWhileLoading: THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks.registerCallbackErrorWhileLoading, progress: null, completedLoading: null, errorWhileLoading: null, materialsLoaded: null, meshLoaded: null}}
- * @constructor
- */
-THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks = function () {
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	return {
-		/**
-		 * Register callback function that is invoked by internal function "_announceProgress" to print feedback.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks
-		 *
-		 * @param {callback} callbackProgress Callback function for described functionality
-		 */
-		registerCallbackProgress: function ( callbackProgress ) {
-			if ( Validator.isValid( callbackProgress ) ) this.progress = callbackProgress;
-		},
-
-		/**
-		 * Register callback function that is called once loading of the complete model is completed.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks
-		 *
-		 * @param {callback} callbackCompletedLoading Callback function for described functionality
-		 */
-		registerCallbackCompletedLoading: function ( callbackCompletedLoading ) {
-			if ( Validator.isValid( callbackCompletedLoading ) ) this.completedLoading = callbackCompletedLoading;
-		},
-
-		/**
-		 * Register callback function that is called once materials have been loaded. It allows to alter and return materials.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks
-		 *
-		 * @param {callback} callbackMaterialsLoaded Callback function for described functionality
-		 */
-		registerCallbackMaterialsLoaded: function ( callbackMaterialsLoaded ) {
-			if ( Validator.isValid( callbackMaterialsLoaded ) ) this.materialsLoaded = callbackMaterialsLoaded;
-		},
-
-		/**
-		 * Register callback function that is called every time a mesh was loaded.
-		 * Use {@link THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride} for alteration instructions (geometry, material or disregard mesh).
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks
-		 *
-		 * @param {callback} callbackMeshLoaded Callback function for described functionality
-		 */
-		registerCallbackMeshLoaded: function ( callbackMeshLoaded ) {
-			if ( Validator.isValid( callbackMeshLoaded ) ) this.meshLoaded = callbackMeshLoaded;
-		},
-
-		/**
-		 * Report if an error prevented loading.
-		 * @memberOf THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks
-		 *
-		 * @param {callback} callbackErrorWhileLoading Callback function for described functionality
-		 */
-		registerCallbackErrorWhileLoading: function ( callbackErrorWhileLoading ) {
-			if ( Validator.isValid( callbackErrorWhileLoading ) ) this.errorWhileLoading = callbackErrorWhileLoading;
-		},
-
-		progress: null,
-		completedLoading: null,
-		errorWhileLoading: null,
-		materialsLoaded: null,
-		meshLoaded: null
-	};
-};
-
-
-/**
- * Object to return by {@link THREE.OBJLoader2.WWOBJLoader2}.callbacks.meshLoaded. Used to adjust bufferGeometry or material or prevent complete loading of mesh
- *
- * @param {boolean} disregardMesh=false Tell WWOBJLoader2 to completely disregard this mesh
- * @param {THREE.BufferGeometry} bufferGeometry The {@link THREE.BufferGeometry} to be used
- * @param {THREE.Material} material The {@link THREE.Material} to be used
- *
- * @returns {{ disregardMesh: boolean, replaceBufferGeometry: boolean, bufferGeometry: THREE.BufferGeometry, replaceMaterial: boolean, material: THREE.Material}}
- * @constructor
- */
-THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride = function ( disregardMesh, bufferGeometry, material ) {
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	return {
-		disregardMesh: disregardMesh === true,
-		replaceBufferGeometry: Validator.isValid( bufferGeometry ),
-		bufferGeometry: Validator.verifyInput( bufferGeometry, null ),
-		replaceMaterial: Validator.isValid( material ),
-		material: Validator.verifyInput( material, null )
-	};
-};
-
-/**
- * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).
- * Workflow:
- *   prepareWorkers
- *   enqueueForRun
- *   processQueue
- *   deregister
- *
- * @class
- */
-THREE.OBJLoader2.WWOBJLoader2Director = (function () {
-
-	var Validator = THREE.OBJLoader2.prototype._getValidator();
-
-	var MAX_WEB_WORKER = 16;
-	var MAX_QUEUE_SIZE = 1024;
-
-	function WWOBJLoader2Director() {
-		this.maxQueueSize = MAX_QUEUE_SIZE ;
-		this.maxWebWorkers = MAX_WEB_WORKER;
-		this.crossOrigin = null;
-
-		this.workerDescription = {
-			prototypeDef: THREE.OBJLoader2.WWOBJLoader2.prototype,
-			globalCallbacks: {},
-			webWorkers: [],
-			codeBuffer: null
-		};
-		this.objectsCompleted = 0;
-		this.instructionQueue = [];
-	}
-
-	/**
-	 * Returns the maximum length of the instruction queue.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 *
-	 * @returns {number}
-	 */
-	WWOBJLoader2Director.prototype.getMaxQueueSize = function () {
-		return this.maxQueueSize;
-	};
-
-	/**
-	 * Returns the maximum number of workers.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 *
-	 * @returns {number}
-	 */
-	WWOBJLoader2Director.prototype.getMaxWebWorkers = function () {
-		return this.maxWebWorkers;
-	};
-
-	/**
-	 * Sets the CORS string to be used.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 *
-	 * @param {string} crossOrigin CORS value
-	 */
-	WWOBJLoader2Director.prototype.setCrossOrigin = function ( crossOrigin ) {
-		this.crossOrigin = crossOrigin;
-	};
-
-	/**
-	 * Create or destroy workers according limits. Set the name and register callbacks for dynamically created web workers.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 *
-	 * @param {THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks} globalCallbacks  Register global callbacks used by all web workers
-	 * @param {number} maxQueueSize Set the maximum size of the instruction queue (1-1024)
-	 * @param {number} maxWebWorkers Set the maximum amount of workers (1-16)
-	 */
-	WWOBJLoader2Director.prototype.prepareWorkers = function ( globalCallbacks, maxQueueSize, maxWebWorkers ) {
-		if ( Validator.isValid( globalCallbacks ) ) this.workerDescription.globalCallbacks = globalCallbacks;
-		this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE );
-		this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER );
-		this.objectsCompleted = 0;
-		this.instructionQueue = [];
-
-		var start = this.workerDescription.webWorkers.length;
-		if ( start < this.maxWebWorkers ) {
-
-			for ( i = start; i < this.maxWebWorkers; i ++ ) {
-
-				webWorker = this._buildWebWorker();
-				this.workerDescription.webWorkers[ i ] = webWorker;
-
-			}
-
-		} else {
-
-			for ( var webWorker, i = start - 1; i >= this.maxWebWorkers; i-- ) {
-
-				webWorker = this.workerDescription.webWorkers[ i ];
-				webWorker.setRequestTerminate( true );
-
-				this.workerDescription.webWorkers.pop();
-
-			}
-
-		}
-	};
-
-	/**
-	 * Store run instructions in internal instructionQueue.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 *
-	 * @param {Object} runParams Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
-	 */
-	WWOBJLoader2Director.prototype.enqueueForRun = function ( runParams ) {
-		if ( this.instructionQueue.length < this.maxQueueSize ) {
-			this.instructionQueue.push( runParams );
-		}
-	};
-
-	/**
-	 * Process the instructionQueue until it is depleted.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 */
-	WWOBJLoader2Director.prototype.processQueue = function () {
-		if ( this.instructionQueue.length === 0 ) return;
-
-		var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
-		for ( var i = 0; i < length; i++ ) {
-
-			this._kickWebWorkerRun( this.workerDescription.webWorkers[ i ], this.instructionQueue[ 0 ] );
-			this.instructionQueue.shift();
-
-		}
-	};
-
-	WWOBJLoader2Director.prototype._kickWebWorkerRun = function( worker, runParams ) {
-		worker.clearAllCallbacks();
-		var key;
-		var globalCallbacks = this.workerDescription.globalCallbacks;
-		var workerCallbacks = worker.callbacks;
-		var selectedGlobalCallback;
-		for ( key in globalCallbacks ) {
-
-			if ( workerCallbacks.hasOwnProperty( key ) && globalCallbacks.hasOwnProperty( key ) ) {
-
-				selectedGlobalCallback = globalCallbacks[ key ];
-				if ( Validator.isValid( selectedGlobalCallback ) ) workerCallbacks[ key ].push( selectedGlobalCallback );
-
-			}
-
-		}
-		// register per object callbacks
-		var runCallbacks = runParams.callbacks;
-		if ( Validator.isValid( runCallbacks ) ) {
-
-			for ( key in runCallbacks ) {
-
-				if ( workerCallbacks.hasOwnProperty( key ) && runCallbacks.hasOwnProperty( key ) && Validator.isValid( runCallbacks[ key ] ) ) {
-
-					workerCallbacks[ key ].push( runCallbacks[ key ] );
-
-				}
-
-			}
-
-		}
-
-		var scope = this;
-		var directorCompletedLoading = function ( modelName, instanceNo, requestTerminate ) {
-			scope.objectsCompleted++;
-			if ( ! requestTerminate ) {
-
-				var worker = scope.workerDescription.webWorkers[ instanceNo ];
-				var runParams = scope.instructionQueue[ 0 ];
-				if ( Validator.isValid( runParams ) ) {
-
-					console.log( '\nAssigning next item from queue to worker (queue length: ' + scope.instructionQueue.length + ')\n\n' );
-					scope._kickWebWorkerRun( worker, runParams );
-					scope.instructionQueue.shift();
-
-				}
-
-			}
-		};
-		worker.registerCallbackCompletedLoading( directorCompletedLoading );
-
-		worker.prepareRun( runParams );
-		worker.run();
-	};
-
-	WWOBJLoader2Director.prototype._buildWebWorker = function () {
-		var webWorker = Object.create( this.workerDescription.prototypeDef );
-		webWorker._init();
-		if ( Validator.isValid( this.crossOrigin ) ) webWorker.setCrossOrigin( this.crossOrigin );
-
-		// Ensure code string is built once and then it is just passed on to every new instance
-		if ( Validator.isValid( this.workerDescription.codeBuffer ) ) {
-
-			webWorker._buildWebWorkerCode( this.workerDescription.codeBuffer );
-
-		} else {
-
-			this.workerDescription.codeBuffer = webWorker._buildWebWorkerCode();
-
-		}
-
-		webWorker.instanceNo = this.workerDescription.webWorkers.length;
-		this.workerDescription.webWorkers.push( webWorker );
-		return webWorker;
-	};
-
-	/**
-	 * Terminate all workers.
-	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
-	 */
-	WWOBJLoader2Director.prototype.deregister = function () {
-		console.log( 'WWOBJLoader2Director received the unregister call. Terminating all workers!' );
-		for ( var i = 0, webWorker, length = this.workerDescription.webWorkers.length; i < length; i++ ) {
-
-			webWorker = this.workerDescription.webWorkers[ i ];
-			webWorker.setRequestTerminate( true );
-
-		}
-		this.workerDescription.globalCallbacks = {};
-		this.workerDescription.webWorkers = [];
-		this.workerDescription.codeBuffer = null;
-	};
-
-	return WWOBJLoader2Director;
-
-})();

+ 24 - 178
examples/webgl_loader_obj2.html

@@ -78,6 +78,7 @@
 		<script src="js/loaders/MTLLoader.js"></script>
 		<script src="js/libs/dat.gui.min.js"></script>
 
+		<script src="js/loaders/LoaderSupport.js"></script>
 		<script src="js/loaders/OBJLoader2.js"></script>
 		<script>
 
@@ -85,7 +86,7 @@
 
 			var OBJLoader2Example = (function () {
 
-				var Validator = THREE.OBJLoader2.prototype._getValidator();
+				var Validator = THREE.LoaderSupport.Validator;
 
 				function OBJLoader2Example( elementToBindTo ) {
 					this.renderer = null;
@@ -105,24 +106,17 @@
 					this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
 					this.controls = null;
-
-					this.flatShading = false;
-					this.doubleSide = false;
-
-					this.cube = null;
-					this.pivot = null;
-
-					this.feedbackArray = [];
 				}
 
 				OBJLoader2Example.prototype.initGL = function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
-						antialias: true
+						antialias: true,
+						autoClear: true
 					} );
+					this.renderer.setClearColor( 0x050505 );
 
 					this.scene = new THREE.Scene();
-					this.scene.background = new THREE.Color( 0x050505 );
 
 					this.camera = new THREE.PerspectiveCamera( this.cameraDefaults.fov, this.aspectRatio, this.cameraDefaults.near, this.cameraDefaults.far );
 					this.resetCamera();
@@ -141,70 +135,31 @@
 
 					var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
 					this.scene.add( helper );
-
-					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 );
-					this.scene.add( this.cube );
-
-					this.pivot = new THREE.Object3D();
-					this.pivot.name = 'Pivot';
-					this.scene.add( this.pivot );
 				};
 
-				OBJLoader2Example.prototype.initPostGL = function () {
-					return true;
-				};
+				OBJLoader2Example.prototype.initContent = function () {
+					var modelName = 'female02';
+					this._reportProgress( 'Loading: ' + modelName );
 
-				OBJLoader2Example.prototype.loadObj = function ( objDef ) {
-					this.scene.add( objDef.pivot );
 					var scope = this;
-					scope._reportProgress( 'Loading: ' + objDef.fileObj, objDef.instanceNo );
-
-					var mtlLoader = new THREE.MTLLoader();
-					mtlLoader.setPath( objDef.texturePath );
-					mtlLoader.setCrossOrigin( 'anonymous' );
-					mtlLoader.load( objDef.fileMtl, function( materials ) {
-
-						materials.preload();
-
-						scope.pivot.add( objDef.pivot );
-						var objLoader = new THREE.OBJLoader2();
-						objLoader.setSceneGraphBaseNode( objDef.pivot );
-						objLoader.setMaterials( materials.materials );
-						objLoader.setPath( objDef.path );
-						objLoader.setDebug( false, false );
-
-						var onSuccess = function ( object3d ) {
-							console.log( 'Loading complete. Meshes were attached to: ' + object3d.name );
-							scope._reportProgress( '', objDef.instanceNo );
-						};
-
-						var onProgress = function ( event ) {
-							if ( event.lengthComputable ) {
-
-								var percentComplete = event.loaded / event.total * 100;
-								var output = 'Download of "' + objDef.fileObj + '": ' + Math.round( percentComplete ) + '%';
-								scope._reportProgress( output, objDef.instanceNo );
-							}
-						};
-
-						var onError = function ( event ) {
-							var output = 'Error of type "' + event.type + '" occurred when trying to load: ' + event.src;
-							scope._reportProgress( output, objDef.instanceNo );
-						};
-
-						objLoader.load( objDef.fileObj, onSuccess, onProgress, onError );
+					var objLoader = new THREE.OBJLoader2();
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						scope.scene.add( loaderRootNode );
+						console.log( 'Loading complete: ' + modelName );
+						scope._reportProgress( '' );
+					};
 
-					});
+					var onLoadMtl = function ( materials ) {
+						objLoader.setModelName( modelName );
+						objLoader.setMaterials( materials );
+						objLoader.load( 'obj/female02/female02.obj', callbackOnLoad, null, null, null, false );
+					};
+					objLoader.loadMtl( 'obj/female02/female02.mtl', 'female02.mtl', null, onLoadMtl );
 				};
 
-				OBJLoader2Example.prototype._reportProgress = function( text, instanceNo ) {
-					this.feedbackArray[ instanceNo ] = text;
-					console.log( 'Progress: ' + text );
-
-					document.getElementById( 'feedback' ).innerHTML = Validator.isValid( this.feedbackArray ) && this.feedbackArray.length > 0 ? this.feedbackArray.join( '\<br\>' ) : '';
+				OBJLoader2Example.prototype._reportProgress = function( content ) {
+					console.log( 'Progress: ' + content );
+					document.getElementById( 'feedback' ).innerHTML = Validator.isValid( content ) ? content : '';
 				};
 
 				OBJLoader2Example.prototype.resizeDisplayGL = function () {
@@ -235,103 +190,16 @@
 
 				OBJLoader2Example.prototype.render = function () {
 					if ( ! this.renderer.autoClear ) this.renderer.clear();
-
 					this.controls.update();
-
-					this.cube.rotation.x += 0.05;
-					this.cube.rotation.y += 0.05;
-
 					this.renderer.render( this.scene, this.camera );
 				};
 
-				OBJLoader2Example.prototype.alterShading = function () {
-
-					var scope = this;
-					scope.flatShading = ! scope.flatShading;
-					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 );
-				};
-
-				OBJLoader2Example.prototype.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 );
-				};
-
-				OBJLoader2Example.prototype.traverseScene = function ( object3d ) {
-
-					if ( object3d.material instanceof THREE.MultiMaterial ) {
-
-						var materials = object3d.material.materials;
-						for ( var name in materials ) {
-
-							if ( materials.hasOwnProperty( name ) )	this.traversalFunction( materials[ name ] );
-
-						}
-
-					} else if ( object3d.material ) {
-
-						this.traversalFunction( object3d.material );
-
-					}
-
-				};
-
 				return OBJLoader2Example;
 
 			})();
 
 			var app = new OBJLoader2Example( document.getElementById( 'example' ) );
 
-			// Init dat.gui and controls
-			var OBJLoader2Control = function() {
-				this.flatShading = app.flatShading;
-				this.doubleSide = app.doubleSide;
-			};
-			var objLoader2Control = new OBJLoader2Control();
-
-			var gui = new dat.GUI( {
-				autoPlace: false,
-				width: 320
-			} );
-
-			var menuDiv = document.getElementById( 'dat' );
-			menuDiv.appendChild(gui.domElement);
-			var folderQueue = gui.addFolder( 'OBJLoader2 Options' );
-			var controlSmooth = folderQueue.add( objLoader2Control, 'flatShading' ).name( 'Flat Shading' );
-			controlSmooth.onChange( function( value ) {
-				console.log( 'Setting flatShading to: ' + value );
-				app.alterShading();
-			});
-
-			var controlDouble = folderQueue.add( objLoader2Control, 'doubleSide' ).name( 'Double Side Materials' );
-			controlDouble.onChange( function( value ) {
-				console.log( 'Setting doubleSide to: ' + value );
-				app.alterDouble();
-			});
-			folderQueue.open();
-
-
-
-			// init three.js example application
 			var resizeWindow = function () {
 				app.resizeDisplayGL();
 			};
@@ -346,29 +214,7 @@
 			console.log( 'Starting initialisation phase...' );
 			app.initGL();
 			app.resizeDisplayGL();
-			app.initPostGL();
-
-			var pivotA = new THREE.Object3D();
-			pivotA.name = 'PivotA';
-			pivotA.position.set( -50, 0, 0 );
-			app.loadObj( {
-				path: 'obj/female02/',
-				fileObj: 'female02_vertex_colors.obj',
-				pivot: pivotA,
-				instanceNo: 0
-			} );
-
-			var pivotB = new THREE.Object3D();
-			pivotB.name = 'PivotB';
-			pivotB.position.set( 50, 0, 0 );
-			app.loadObj( {
-				path: 'obj/female02/',
-				fileObj: 'female02.obj',
-				texturePath: 'obj/female02/',
-				fileMtl: 'female02.mtl',
-				pivot: pivotB,
-				instanceNo: 1
-			} );
+			app.initContent();
 
 			render();
 

+ 225 - 83
examples/webgl_loader_obj2_ww.html → examples/webgl_loader_obj2_options.html

@@ -82,15 +82,15 @@
 		<script src="js/loaders/MTLLoader.js"></script>
 		<script src="js/libs/dat.gui.min.js"></script>
 
+		<script src="js/loaders/LoaderSupport.js"></script>
 		<script src="js/loaders/OBJLoader2.js"></script>
-		<script src="js/loaders/WWOBJLoader2.js"></script>
 		<script>
 
 			'use strict';
 
 			var WWOBJLoader2Example = (function () {
 
-				var Validator = THREE.OBJLoader2.prototype._getValidator();
+				var Validator = THREE.LoaderSupport.Validator;
 
 				function WWOBJLoader2Example( elementToBindTo ) {
 					this.renderer = null;
@@ -113,14 +113,10 @@
 
 					this.flatShading = false;
 					this.doubleSide = false;
-					this.streamMeshes = true;
 
 					this.cube = null;
 					this.pivot = null;
 
-					this.wwObjLoader2 = new THREE.OBJLoader2.WWOBJLoader2();
-					this.wwObjLoader2.setCrossOrigin( 'anonymous' );
-
 					// Check for the various File API support.
 					this.fileApiAvailable = true;
 					if ( window.File && window.FileReader && window.FileList && window.Blob ) {
@@ -133,16 +129,18 @@
 						console.warn( 'File API is not supported! Disabling file loading.' );
 
 					}
+					this.streamMeshes = true;
 				}
 
 				WWOBJLoader2Example.prototype.initGL = function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
-						antialias: true
+						antialias: true,
+						autoClear: true
 					} );
+					this.renderer.setClearColor( 0x050505 );
 
 					this.scene = new THREE.Scene();
-					this.scene.background = new THREE.Color( 0x050505 );
 
 					this.camera = new THREE.PerspectiveCamera( this.cameraDefaults.fov, this.aspectRatio, this.cameraDefaults.near, this.cameraDefaults.far );
 					this.resetCamera();
@@ -168,46 +166,183 @@
 					this.cube.position.set( 0, 0, 0 );
 					this.scene.add( this.cube );
 
-					this.createPivot();
-				};
-
-				WWOBJLoader2Example.prototype.createPivot = function () {
 					this.pivot = new THREE.Object3D();
 					this.pivot.name = 'Pivot';
 					this.scene.add( this.pivot );
 				};
 
-				WWOBJLoader2Example.prototype.initPostGL = function () {
+				WWOBJLoader2Example.prototype.useParseSync = function () {
+					var modelName = 'female02';
+					this._reportProgress( 'Loading: ' + modelName );
+
 					var scope = this;
-					var materialsLoaded = function ( materials ) {
-						var count = Validator.isValid( materials ) ? materials.length : 0;
-						console.log( 'Loaded #' + count + ' materials.' );
+					var objLoader = new THREE.OBJLoader2();
+					var onLoadMtl = function ( materials ) {
+						objLoader.setModelName( modelName );
+						objLoader.setMaterials( materials );
+
+						var fileLoader = new THREE.FileLoader();
+						fileLoader.setResponseType( 'arraybuffer' );
+						fileLoader.load( 'obj/female02/female02.obj',
+							function ( content ) {
+								var local = new THREE.Object3D();
+								local.name = 'Pivot_female02';
+								local.position.set( 75, 0, 0 );
+								scope.pivot.add( local );
+								local.add( objLoader.parse( content ) );
+
+								scope._reportProgress( 'Loading complete: ' + modelName );
+							}
+						);
 					};
-					var meshLoaded = function ( name, bufferGeometry, material ) {
-						console.log( 'Loaded mesh: ' + name + ' Material name: ' + material.name );
+					objLoader.loadMtl( 'obj/female02/female02.mtl', 'female02.mtl', null, onLoadMtl );
+				};
+
+
+				WWOBJLoader2Example.prototype.useParseAsync = function () {
+					var modelName = 'female02_vertex' ;
+					this._reportProgress( 'Loading: ' + modelName );
+
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						var local = new THREE.Object3D();
+						local.name = 'Pivot_female02_vertex';
+						local.position.set( -75, 0, 0 );
+						scope.pivot.add( local );
+						local.add( loaderRootNode );
+
+						scope._reportProgress( 'Loading complete: ' + modelName );
 					};
-					var completedLoading = function () {
-						console.log( 'Loading complete!' );
-						scope._reportProgress( '' );
+
+					var scope = this;
+					var objLoader = new THREE.OBJLoader2();
+					objLoader.setModelName( modelName );
+
+					var fileLoader = new THREE.FileLoader();
+					fileLoader.setResponseType( 'arraybuffer' );
+					fileLoader.load( 'obj/female02/female02_vertex_colors.obj',
+						function ( content ) {
+							objLoader.parseAsync( content, callbackOnLoad );
+							scope._reportProgress( 'Loading complete: ' + modelName );
+						}
+					);
+				};
+
+				WWOBJLoader2Example.prototype.useLoadSync = function () {
+					var modelName = 'male02';
+					this._reportProgress( 'Loading: ' + modelName );
+
+					var scope = this;
+					var objLoader = new THREE.OBJLoader2();
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						var local = new THREE.Object3D();
+						local.name = 'Pivot_male02';
+						local.position.set( 0, 0, -75 );
+						scope.pivot.add( local );
+						local.add( loaderRootNode );
+
+						scope._reportProgress( 'Loading complete: ' + modelName );
+					};
+
+					var onLoadMtl = function ( materials ) {
+						objLoader.setModelName( modelName );
+						objLoader.setMaterials( materials );
+						objLoader.load( 'obj/male02/male02.obj', callbackOnLoad, null, null, null, false );
+					};
+					objLoader.loadMtl( 'obj/male02/male02.mtl', 'female02.mtl', null, onLoadMtl );
+				};
+
+				WWOBJLoader2Example.prototype.useLoadAsync = function () {
+					var modelName = 'WaltHead';
+					this._reportProgress( 'Loading: ' + modelName );
+
+					var scope = this;
+					var objLoader = new THREE.OBJLoader2();
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						var local = new THREE.Object3D();
+						local.name = 'Pivot_WaltHead';
+						local.position.set( -125, 50, 0 );
+						var scale = 0.5;
+						local.scale.set( scale, scale, scale );
+						scope.pivot.add( local );
+						local.add( loaderRootNode );
+
+						scope._reportProgress( 'Loading complete: ' + modelName );
+					};
+
+					var onLoadMtl = function ( materials ) {
+						objLoader.setModelName( modelName );
+						objLoader.setMaterials( materials );
+						objLoader.load( 'obj/walt/WaltHead.obj', callbackOnLoad, null, null, null, true );
+					};
+					objLoader.loadMtl( 'obj/walt//WaltHead.mtl', 'WaltHead.mtl', null, onLoadMtl );
+				};
+
+				WWOBJLoader2Example.prototype.useRunSync = function () {
+					var scope = this;
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						scope._reportProgress( 'Loading complete: ' + modelName );
 					};
-					this.wwObjLoader2.registerCallbackProgress( this._reportProgress );
-					this.wwObjLoader2.registerCallbackCompletedLoading( completedLoading );
-					this.wwObjLoader2.registerCallbackMaterialsLoaded( materialsLoaded );
-					this.wwObjLoader2.registerCallbackMeshLoaded( meshLoaded );
 
-					return true;
+					var prepData = new THREE.LoaderSupport.PrepData( 'cerberus' );
+					var local = new THREE.Object3D();
+					local.position.set( 0, 0, 100 );
+					local.scale.set( 50.0, 50.0, 50.0 );
+					this.pivot.add( local );
+					prepData.setStreamMeshesTo( local );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/cerberus/Cerberus.obj', 'OBJ' ) );
+					var callbacks = prepData.getCallbacks();
+					callbacks.setCallbackOnProgress( this._reportProgress );
+					callbacks.setCallbackOnLoad( callbackOnLoad );
+
+					var objLoader = new THREE.OBJLoader2();
+					objLoader.run( prepData );
 				};
 
-				WWOBJLoader2Example.prototype._reportProgress = function( text ) {
-					console.log( 'Progress: ' + text );
-					document.getElementById( 'feedback' ).innerHTML = Validator.isValid( text ) ? text : '';
+				WWOBJLoader2Example.prototype.useRunAsyncMeshAlter = function () {
+					var scope = this;
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
+						scope._reportProgress( 'Loading complete: ' + modelName );
+					};
+
+					var prepData = new THREE.LoaderSupport.PrepData( 'vive-controller' );
+					var local = new THREE.Object3D();
+					local.position.set( 125, 50, 0 );
+					local.name = 'Pivot_vive-controller';
+					this.pivot.add( local );
+					prepData.setStreamMeshesTo( local );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/vive-controller/vr_controller_vive_1_5.obj', 'OBJ' ) );
+					prepData.setUseAsync( true );
+					var callbacks = prepData.getCallbacks();
+					var callbackMeshAlter = function ( name, bufferGeometry, material ) {
+						var override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, true );
+
+						var mesh = new THREE.Mesh( bufferGeometry, material );
+						var scale = 200.0;
+						mesh.scale.set( scale, scale, scale );
+						mesh.name = name;
+						var helper = new THREE.VertexNormalsHelper( mesh, 2, 0x00ff00, 1 );
+						helper.name = 'VertexNormalsHelper';
+
+						override.addMesh( mesh );
+						override.addMesh( helper );
+
+						return override;
+					};
+					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
+					callbacks.setCallbackOnProgress( this._reportProgress );
+					callbacks.setCallbackOnLoad( callbackOnLoad );
+
+					var objLoader = new THREE.OBJLoader2();
+					objLoader.run( prepData );
 				};
 
-				WWOBJLoader2Example.prototype.loadFiles = function ( prepData ) {
-					prepData.setSceneGraphBaseNode( this.pivot );
-					prepData.setStreamMeshes( this.streamMeshes );
-					this.wwObjLoader2.prepareRun( prepData );
-					this.wwObjLoader2.run();
+				WWOBJLoader2Example.prototype.finalize = function () {
+					this._reportProgress( '' );
+				};
+
+				WWOBJLoader2Example.prototype._reportProgress = function( content, modelName, instanceNo ) {
+					console.log( 'Progress: ' + content );
+					document.getElementById( 'feedback' ).innerHTML = Validator.isValid( content ) ? content : '';
 				};
 
 				WWOBJLoader2Example.prototype._handleFileSelect = function ( event, pathTexture ) {
@@ -231,49 +366,52 @@
 						alert( 'Unable to load OBJ file from given files.' );
 					}
 
+					var scope = this;
+					var callbackOnLoad = function ( loderRootNode, modelName, instanceNo ) {
+						scope.scene.add( loderRootNode );
+						console.log( 'Loading complete: ' + modelName );
+						scope._reportProgress( '' );
+					};
+
 					var fileReader = new FileReader();
 					fileReader.onload = function( fileDataObj ) {
 
 						var uint8Array = new Uint8Array( fileDataObj.target.result );
-						if ( fileMtl === null ) {
-
-							app.loadFilesUser({
-								name: 'userObj',
-								objAsArrayBuffer: uint8Array,
-								pathTexture: pathTexture,
-								mtlAsString: null
-							})
-
-						} else {
 
-							fileReader.onload = function( fileDataMtl ) {
-
-								app.loadFilesUser({
-									name: 'userObj',
-									objAsArrayBuffer: uint8Array,
-									pathTexture: pathTexture,
-									mtlAsString: fileDataMtl.target.result
-								})
-							};
-							fileReader.readAsText( fileMtl );
-
-						}
+						var prepData = new THREE.LoaderSupport.PrepData( 'userObj' );
+						var resourceOBJ = new THREE.LoaderSupport.ResourceDescriptor( pathTexture + '/' + fileObj.name, 'OBJ' );
+						var userPivot = new THREE.Object3D();
+						userPivot.position.set(
+							-100 + 200 * Math.random(),
+							-100 + 200 * Math.random(),
+							-100 + 200 * Math.random()
+						);
+						prepData.setStreamMeshesTo( userPivot );
+						scope.pivot.add( prepData.streamMeshesTo );
+
+						resourceOBJ.setContent( uint8Array );
+						prepData.addResource( resourceOBJ );
+						prepData.setUseAsync( true );
+						var callbacks = prepData.getCallbacks();
+						callbacks.setCallbackOnProgress( scope._reportProgress );
+						callbacks.setCallbackOnLoad( callbackOnLoad );
+
+						fileReader.onload = function( fileDataMtl ) {
+
+							var resourceMTL = new THREE.LoaderSupport.ResourceDescriptor( pathTexture + '/' + fileMtl.name, 'MTL' );
+							resourceMTL.setContent( fileDataMtl.target.result );
+							prepData.addResource( resourceMTL );
+
+							var objLoader = new THREE.OBJLoader2();
+							objLoader.run( prepData );
+						};
+						fileReader.readAsText( fileMtl );
 
 					};
 					fileReader.readAsArrayBuffer( fileObj );
 
 				};
 
-				WWOBJLoader2Example.prototype.loadFilesUser = function ( objDef ) {
-					var prepData = new THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer(
-						objDef.name, objDef.objAsArrayBuffer, objDef.pathTexture, objDef.mtlAsString
-					);
-					prepData.setSceneGraphBaseNode( this.pivot );
-					prepData.setStreamMeshes( this.streamMeshes );
-					this.wwObjLoader2.prepareRun( prepData );
-					this.wwObjLoader2.run();
-				};
-
 				WWOBJLoader2Example.prototype.resizeDisplayGL = function () {
 					this.controls.handleResize();
 
@@ -312,7 +450,6 @@
 				};
 
 				WWOBJLoader2Example.prototype.alterShading = function () {
-
 					var scope = this;
 					scope.flatShading = ! scope.flatShading;
 					console.log( scope.flatShading ? 'Enabling flat shading' : 'Enabling smooth shading');
@@ -328,7 +465,6 @@
 				};
 
 				WWOBJLoader2Example.prototype.alterDouble = function () {
-
 					var scope = this;
 					scope.doubleSide = ! scope.doubleSide;
 					console.log( scope.doubleSide ? 'Enabling DoubleSide materials' : 'Enabling FrontSide materials');
@@ -344,7 +480,6 @@
 				};
 
 				WWOBJLoader2Example.prototype.traverseScene = function ( object3d ) {
-
 					if ( object3d.material instanceof THREE.MultiMaterial ) {
 
 						var materials = object3d.material.materials;
@@ -359,7 +494,6 @@
 						this.traversalFunction( object3d.material );
 
 					}
-
 				};
 
 				WWOBJLoader2Example.prototype.clearAllAssests = function () {
@@ -395,7 +529,6 @@
 
 					scope.scene.remove( scope.pivot );
 					scope.pivot.traverse( remover );
-					scope.createPivot();
 				};
 
 				return WWOBJLoader2Example;
@@ -467,7 +600,6 @@
 			folderOptions.open();
 
 
-
 			// init three.js example application
 			var resizeWindow = function () {
 				app.resizeDisplayGL();
@@ -477,26 +609,36 @@
 				requestAnimationFrame( render );
 				app.render();
 			};
-
 			window.addEventListener( 'resize', resizeWindow, false );
 
 			console.log( 'Starting initialisation phase...' );
 			app.initGL();
 			app.resizeDisplayGL();
-			app.initPostGL();
-
-			var prepData = new THREE.OBJLoader2.WWOBJLoader2.PrepDataFile(
-				'male02',
-				'obj/male02/',
-				'male02.obj',
-				'obj/male02/',
-				'male02.mtl'
-			);
-			app.loadFiles( prepData );
 
 			// kick render loop
 			render();
 
+
+			// Load a file with OBJLoader.parse synchronously
+			app.useParseSync();
+
+			// Load a file with OBJLoader.parseAsync asynchronously using a worker
+			app.useParseAsync();
+
+			// Load a file with OBJLoader.load synchronously
+			app.useLoadSync();
+
+			// Load a file with OBJLoader.load asynchronously
+			app.useLoadAsync();
+
+			// Load a file with OBJLoader.run synchronously
+			app.useRunSync();
+
+			// Load a file with OBJLoader.run asynchronously and add normals during onMeshAlter
+			app.useRunAsyncMeshAlter();
+
+			app.finalize();
+
 		</script>
 	</body>
 </html>

+ 76 - 94
examples/webgl_loader_obj2_ww_parallels.html → examples/webgl_loader_obj2_run_director.html

@@ -73,7 +73,7 @@
 
 		</div>
 		<div id="info">
-			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - WWOBJLoader2Director Parallels Demo
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Web Worker LoaderDirector Parallels Demo
 		</div>
 		<div id="feedback">
 		</div>
@@ -84,15 +84,15 @@
 		<script src="js/loaders/MTLLoader.js"></script>
 		<script src="js/libs/dat.gui.min.js"></script>
 
+		<script src="js/loaders/LoaderSupport.js"></script>
 		<script src="js/loaders/OBJLoader2.js"></script>
-		<script src="js/loaders/WWOBJLoader2.js"></script>
 		<script>
 
 			'use strict';
 
 			var WWParallels = (function () {
 
-				var Validator = THREE.OBJLoader2.prototype._getValidator();
+				var Validator = THREE.LoaderSupport.Validator;
 
 				function WWParallels( elementToBindTo ) {
 					this.renderer = null;
@@ -111,8 +111,8 @@
 					this.camera = null;
 					this.cameraTarget = this.cameraDefaults.posCameraTarget;
 
-					this.wwDirector = new THREE.OBJLoader2.WWOBJLoader2Director();
-					this.wwDirector.setCrossOrigin( 'anonymous' );
+					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2 );
+					this.workerDirector.setCrossOrigin( 'anonymous' );
 
 					this.controls = null;
 					this.cube = null;
@@ -126,11 +126,12 @@
 				WWParallels.prototype.initGL = function () {
 					this.renderer = new THREE.WebGLRenderer( {
 						canvas: this.canvas,
-						antialias: true
+						antialias: true,
+						autoClear: true
 					} );
+					this.renderer.setClearColor( 0x050505 );
 
 					this.scene = new THREE.Scene();
-					this.scene.background = new THREE.Color( 0x050505 );
 
 					this.camera = new THREE.PerspectiveCamera( this.cameraDefaults.fov, this.aspectRatio, this.cameraDefaults.near, this.cameraDefaults.far );
 					this.resetCamera();
@@ -204,8 +205,9 @@
 						this.running = true;
 
 					}
+
 					var scope = this;
-					scope.wwDirector.objectsCompleted = 0;
+					scope.workerDirector.objectsCompleted = 0;
 					scope.feedbackArray = [];
 					scope.reportDonwload = [];
 
@@ -218,18 +220,18 @@
 					}
 					scope.reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 
-					var callbackCompletedLoading = function ( modelName, instanceNo ) {
+					var callbackOnLoad = function ( loaderRootNode, modelName, instanceNo ) {
 						scope.reportDonwload[ instanceNo ] = false;
 
-						var msg = 'Worker #' + instanceNo + ': Completed loading: ' + modelName + ' (#' + scope.wwDirector.objectsCompleted + ')';
+						var msg = 'Worker #' + instanceNo + ': Completed loading: ' + modelName + ' (#' + scope.workerDirector.objectsCompleted + ')';
 						console.log( msg );
 						scope.feedbackArray[ instanceNo ] = msg;
 						scope.reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 
-						if ( scope.wwDirector.objectsCompleted + 1 === maxQueueSize ) scope.running = false;
+						if ( scope.workerDirector.objectsCompleted + 1 === maxQueueSize ) scope.running = false;
 					};
 
-					var callbackReportProgress = function ( content, instanceNo ) {
+					var callbackReportProgress = function ( content, modelName, instanceNo ) {
 						if ( scope.reportDonwload[ instanceNo ] ) {
 							var msg = 'Worker #' + instanceNo + ': ' + content;
 							console.log( msg );
@@ -239,83 +241,71 @@
 						}
 					};
 
-					var callbackMeshLoaded = function ( name, bufferGeometry, material ) {
-						var materialOverride;
+					var callbackMeshAlter = function ( name, bufferGeometry, material ) {
+						var override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, false );
 
-						if ( Validator.isValid( material ) && material.name === 'defaultMaterial' || name === 'Mesh_Mesh_head_geo.001' ) {
+						if ( Validator.isValid( material ) && material.name === 'defaultMaterial' || name === 'Mesh_Mesh_head_geo.001_lambert2SG.001' ) {
 
-							materialOverride = material;
+							var materialOverride = material;
 							materialOverride.color = new THREE.Color( Math.random(), Math.random(), Math.random() );
+							var mesh = new THREE.Mesh( bufferGeometry, material );
+							mesh.name = name;
 
-						}
+							override.addMesh( mesh );
+							override.alteredMesh = true;
 
-						return new THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride( false, undefined, materialOverride );
+						}
+						return override;
 					};
 
-					var globalCallbacks = new THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks();
-					globalCallbacks.registerCallbackProgress( callbackReportProgress );
-					globalCallbacks.registerCallbackCompletedLoading( callbackCompletedLoading );
-					globalCallbacks.registerCallbackMeshLoaded( callbackMeshLoaded );
-					this.wwDirector.prepareWorkers( globalCallbacks, maxQueueSize, maxWebWorkers );
-					console.log( 'Configuring WWManager with queue size ' + this.wwDirector.getMaxQueueSize() + ' and ' + this.wwDirector.getMaxWebWorkers() + ' workers.' );
+					var callbacks = new THREE.LoaderSupport.Callbacks();
+					callbacks.setCallbackOnProgress( callbackReportProgress );
+					callbacks.setCallbackOnLoad( callbackOnLoad );
+					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 
-					var callbackCompletedLoadingWalt = function () {
-						console.log( 'Callback check: WALT was loaded (#' + scope.wwDirector.objectsCompleted + ')' );
-					};
+					this.workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
+					console.log( 'Configuring WWManager with queue size ' + this.workerDirector.getMaxQueueSize() + ' and ' + this.workerDirector.getMaxWebWorkers() + ' workers.' );
 
-					var models = [];
-					models.push( {
-						modelName: 'male02',
-						dataAvailable: false,
-						pathObj: 'obj/male02/',
-						fileObj: 'male02.obj',
-						pathTexture: 'obj/male02/',
-						fileMtl: 'male02.mtl'
-					} );
+					var modelPrepDatas = [];
+					prepData = new THREE.LoaderSupport.PrepData( 'male02' );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/male02/male02.obj', 'OBJ ') );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/male02/male02.mtl', 'MTL' ) );
+					modelPrepDatas.push( prepData );
 
-					models.push( {
-						modelName: 'female02',
-						dataAvailable: false,
-						pathObj: 'obj/female02/',
-						fileObj: 'female02.obj',
-						pathTexture: 'obj/female02/',
-						fileMtl: 'female02.mtl'
-					} );
+					prepData = new THREE.LoaderSupport.PrepData( 'female02' );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/female02/female02.obj', 'OBJ' ) );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/female02/female02.mtl', 'MTL' ) );
+					modelPrepDatas.push( prepData );
 
-					models.push( {
-						modelName: 'viveController',
-						dataAvailable: false,
-						pathObj: 'models/obj/vive-controller/',
-						fileObj: 'vr_controller_vive_1_5.obj',
-						scale: 400.0
-					} );
+					prepData = new THREE.LoaderSupport.PrepData( 'viveController' );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/vive-controller/vr_controller_vive_1_5.obj', 'OBJ' ) );
+					prepData.scale = 400.0;
+					modelPrepDatas.push( prepData );
 
-					models.push( {
-						modelName: 'cerberus',
-						dataAvailable: false,
-						pathObj: 'models/obj/cerberus/',
-						fileObj: 'Cerberus.obj',
-						scale: 50.0
-					} );
-					models.push( {
-						modelName: 'WaltHead',
-						dataAvailable: false,
-						pathObj: 'obj/walt/',
-						fileObj: 'WaltHead.obj',
-						pathTexture: 'obj/walt/',
-						fileMtl: 'WaltHead.mtl'
-					} );
+					prepData = new THREE.LoaderSupport.PrepData( 'cerberus' );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'models/obj/cerberus/Cerberus.obj', 'OBJ' ) );
+					prepData.scale = 50.0;
+					modelPrepDatas.push( prepData );
+
+					prepData = new THREE.LoaderSupport.PrepData( 'WaltHead' );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/walt/WaltHead.obj', 'OBJ' ) );
+					prepData.addResource( new THREE.LoaderSupport.ResourceDescriptor( 'obj/walt/WaltHead.mtl', 'MTL' ) );
+					modelPrepDatas.push( prepData );
 
 					var pivot;
 					var distributionBase = -500;
 					var distributionMax = 1000;
-					var modelIndex = 0;
-					var model;
-					var runParams;
+					var modelPrepDataIndex = 0;
+					var modelPrepData;
+					var prepData;
+					var scale;
 					for ( i = 0; i < maxQueueSize; i++ ) {
 
-						modelIndex = Math.floor( Math.random() * models.length );
-						model = models[ modelIndex ];
+						modelPrepDataIndex = Math.floor( Math.random() * modelPrepDatas.length );
+
+						modelPrepData = modelPrepDatas[ modelPrepDataIndex ];
+						scale = Validator.verifyInput( modelPrepData.scale, 0 );
+						modelPrepData = modelPrepData.clone();
 
 						pivot = new THREE.Object3D();
 						pivot.position.set(
@@ -323,38 +313,28 @@
 							distributionBase + distributionMax * Math.random(),
 							distributionBase + distributionMax * Math.random()
 						);
-						if ( Validator.isValid( model.scale ) ) pivot.scale.set( model.scale, model.scale, model.scale );
-
+						if ( scale > 0 ) pivot.scale.set( scale, scale, scale );
 						this.scene.add( pivot );
+						modelPrepData.setStreamMeshesTo( pivot );
+						modelPrepData.setUseAsync( true );
 
-						model.sceneGraphBaseNode = pivot;
-
-						runParams = new THREE.OBJLoader2.WWOBJLoader2.PrepDataFile(
-							model.modelName, model.pathObj, model.fileObj, model.pathTexture, model.fileMtl
-						);
-						runParams.setSceneGraphBaseNode( model.sceneGraphBaseNode );
-						runParams.setStreamMeshes( streamMeshes );
-						if ( model.modelName === 'WaltHead' ) {
-							runParams.getCallbacks().registerCallbackCompletedLoading( callbackCompletedLoadingWalt );
-						}
-
-						this.wwDirector.enqueueForRun( runParams );
-						this.allAssets.push( runParams );
+						this.workerDirector.enqueueForRun( modelPrepData );
+						this.allAssets.push( modelPrepData );
 					}
 
-					this.wwDirector.processQueue();
+					this.workerDirector.processQueue();
 				};
 
 				WWParallels.prototype.clearAllAssests = function () {
-					var ref;
+					var prepData;
 					var scope = this;
 
 					for ( var asset in this.allAssets ) {
-						ref = this.allAssets[ asset ];
+						prepData = this.allAssets[ asset ];
 
 						var remover = function ( object3d ) {
 
-							if ( object3d === ref.sceneGraphBaseNode ) return;
+							if ( object3d === prepData.streamMeshesTo ) return;
 							console.log( 'Removing ' + object3d.name );
 							scope.scene.remove( object3d );
 
@@ -374,21 +354,23 @@
 							}
 							if ( object3d.hasOwnProperty( 'texture' ) ) object3d.texture.dispose();
 						};
-						scope.scene.remove( ref.sceneGraphBaseNode );
-						ref.sceneGraphBaseNode.traverse( remover );
-						ref.sceneGraphBaseNode = null;
+						scope.scene.remove( prepData.streamMeshesTo );
+						prepData.streamMeshesTo.traverse( remover );
+						prepData.streamMeshesTo = null;
 					}
 					this.allAssets = [];
 				};
 
 				WWParallels.prototype.terminateManager = function () {
-					this.wwDirector.deregister();
+					this.workerDirector.deregister();
+					this.running = false;
 				};
 
 				return WWParallels;
 
 			})();
 
+
 			var app = new WWParallels( document.getElementById( 'example' ) );
 
 			var WWParallelsControl = function() {

+ 472 - 0
examples/webgl_loader_ww_meshspray.html

@@ -0,0 +1,472 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>Mesh Spray - Worker based mesh spray</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000;
+				color: #fff;
+				margin: 0 0 0 0;
+				padding: 0 0 0 0;
+				border: none;
+				cursor: default;
+			}
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 100;
+				display:block;
+			}
+			#info a {
+				color: #f00;
+				font-weight: bold;
+				text-decoration: underline;
+				cursor: pointer
+			}
+			#glFullscreen {
+				width: 100%;
+				height: 100vh;
+				min-width: 640px;
+				min-height: 360px;
+				position: relative;
+				overflow: hidden;
+				z-index: 0;
+			}
+			#example {
+				width: 100%;
+				height: 100%;
+				top: 0;
+				left: 0;
+				background-color: #000000;
+			}
+			#feedback {
+				color: darkorange;
+			}
+			#dat {
+				user-select: none;
+				position: absolute;
+				left: 0;
+				top: 0;
+				z-Index: 200;
+			}
+		</style>
+
+	</head>
+
+	<body>
+	<div id="glFullscreen">
+		<canvas id="example"></canvas>
+	</div>
+	<div id="dat">
+
+	</div>
+	<div id="info">
+		<a href="http://threejs.org" target="_blank">three.js</a> - Mesh Spray
+		<div id="feedback"></div>
+	</div>
+
+		<script src="js/Detector.js"></script>
+		<script src="../build/three.js"></script>
+		<script src="js/controls/TrackballControls.js"></script>
+		<script src="js/loaders/MTLLoader.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/loaders/LoaderSupport.js"></script>
+		<script src="js/loaders/OBJLoader2.js"></script>
+
+		<script>
+			/**
+			 * @author Kai Salmen / www.kaisalmen.de
+			 */
+
+			'use strict';
+
+			var MeshSpray = (function () {
+
+				var Validator = THREE.LoaderSupport.Validator;
+
+				MeshSpray.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );
+				MeshSpray.prototype.constructor = MeshSpray;
+
+				function MeshSpray( manager ) {
+					THREE.LoaderSupport.Commons.call( this, manager );
+					this.workerSupport = null;
+				};
+
+				MeshSpray.prototype.run = function ( prepData, workerSupportExternal ) {
+					console.time( 'MeshSpray' );
+
+					this._applyPrepData( prepData );
+
+					var scope = this;
+					var scopeBuilderFunc = function ( payload ) {
+						var meshes = scope.builder.buildMeshes( payload );
+						var mesh;
+						for ( var i in meshes ) {
+							mesh = meshes[ i ];
+							scope.loaderRootNode.add( mesh );
+						}
+					};
+					var scopeFuncComplete = function ( message ) {
+						var callback = scope.callbacks.onLoad;
+						if ( Validator.isValid( callback ) ) callback( scope.loaderRootNode, scope.modelName, scope.instanceNo, message );
+						console.timeEnd( 'MeshSpray' );
+					};
+
+			        this.workerSupport = Validator.verifyInput( workerSupportExternal, this.workerSupport );
+					this.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport() );
+					var buildCode = function ( funcBuildObject, funcBuildSingelton ) {
+						var workerCode = '';
+						workerCode += '/**\n';
+						workerCode += '  * This code was constructed by MeshSpray buildWorkerCode.\n';
+						workerCode += '  */\n\n';
+						workerCode += funcBuildObject( 'Validator', Validator );
+						workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
+
+						return workerCode;
+					};
+					this.workerSupport.validate( buildCode, false );
+					this.workerSupport.setCallbacks( scopeBuilderFunc, scopeFuncComplete );
+					this.workerSupport.run(
+						{
+							cmd: 'run',
+							params: {
+								debug: this.debug,
+								dimension: prepData.dimension,
+								quantity: prepData.quantity,
+								globalObjectCount: prepData.globalObjectCount
+							},
+							materials: {
+								materialNames: this.builder.materialNames
+							},
+							buffers: {
+								input: null
+							}
+						}
+					);
+				};
+
+				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.callbackBuilder = null;
+					};
+
+					Parser.prototype.parse = function () {
+						var materialDescription;
+						var materialDescriptions = [];
+						var materialGroups = [];
+
+						materialDescription = {
+							name: 'Gen',
+							flat: false,
+							vertexColors: true,
+							default: true
+						};
+						materialDescriptions.push( materialDescription );
+
+						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 triagle
+						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 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 ) {
+
+							colorFA.set( colors, 0 );
+
+						}
+
+						if ( normalFA ) {
+
+							normalFA.set( normals, 0 );
+
+						}
+						if ( uvFA ) {
+
+							uvFA.set( uvs, 0 );
+
+						}
+
+						this.globalObjectCount++;
+						this.callbackBuilder(
+							{
+								cmd: 'meshData',
+								params: {
+									meshName: 'Gen' + this.globalObjectCount
+								},
+								materials: {
+									multiMaterial: false,
+									materialDescriptions: materialDescriptions,
+									materialGroups: materialGroups
+								},
+								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
+						);
+
+						console.log( 'Global output object count: ' + this.globalObjectCount );
+					};
+
+					return Parser;
+				})();
+
+				return MeshSpray;
+
+			})();
+
+			var MeshSprayApp = (function () {
+
+				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;
+				}
+
+				MeshSprayApp.prototype.initGL = function () {
+					this.renderer = new THREE.WebGLRenderer( {
+						canvas: this.canvas,
+						antialias: true,
+						autoClear: true
+					} );
+					this.renderer.setClearColor( 0x050505 );
+
+					this.scene = new THREE.Scene();
+
+					this.camera = new THREE.PerspectiveCamera( this.cameraDefaults.fov, this.aspectRatio, this.cameraDefaults.near, this.cameraDefaults.far );
+					this.resetCamera();
+					this.controls = new THREE.TrackballControls( this.camera, this.renderer.domElement );
+
+					var ambientLight = new THREE.AmbientLight( 0x404040 );
+					var directionalLight1 = new THREE.DirectionalLight( 0xC0C090 );
+					var directionalLight2 = new THREE.DirectionalLight( 0xC0C090 );
+
+					directionalLight1.position.set( -100, -50, 100 );
+					directionalLight2.position.set( 100, 50, -100 );
+
+					this.scene.add( directionalLight1 );
+					this.scene.add( directionalLight2 );
+					this.scene.add( ambientLight );
+
+					var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
+					this.scene.add( helper );
+
+					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 );
+					this.scene.add( this.cube );
+
+					this.pivot = new THREE.Object3D();
+					this.pivot.name = 'Pivot';
+					this.scene.add( this.pivot );
+				};
+
+				MeshSprayApp.prototype.initContent = function () {
+					var maxQueueSize = 1024;
+					var maxWebWorkers = 4;
+					var radius = 640;
+					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray );
+					this.workerDirector.setCrossOrigin( 'anonymous' );
+
+					var scope = this;
+					var callbackOnLoad = function ( sceneGraphBaseNode, modelName, instanceNo ) {
+						var msg = 'Worker #' + instanceNo + ': Completed loading. (#' + scope.workerDirector.objectsCompleted + ')';
+						console.log( msg );
+					};
+					var reportProgress = function( content, modelName, instanceNo ) {
+						if ( THREE.LoaderSupport.Validator.isValid( content ) && content.length > 0 ) {
+
+							document.getElementById( 'feedback' ).innerHTML = content;
+							console.log( content );
+
+						}
+					};
+					var callbackMeshAlter = function ( name, bufferGeometry, material ) {
+						var override = new THREE.LoaderSupport.LoadedMeshUserOverride( false, true );
+
+						var mesh = new THREE.Mesh( bufferGeometry, material );
+						material.side = THREE.DoubleSide;
+						mesh.name = name;
+						override.addMesh( mesh );
+
+						return override;
+					};
+
+
+					var callbacks = new THREE.LoaderSupport.Callbacks();
+					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
+					callbacks.setCallbackOnLoad( callbackOnLoad );
+					callbacks.setCallbackOnProgress( reportProgress );
+					this.workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
+
+					var prepData;
+					var pivot;
+					var s, t, r, x, y, z;
+					var globalObjectCount = 0;
+					for ( var i = 0; i < maxQueueSize; i++ ) {
+						prepData = new THREE.LoaderSupport.PrepData( 'Triangles_' + i );
+
+						pivot = new THREE.Object3D();
+						s = 2 * Math.PI * Math.random();
+						t = Math.PI * Math.random();
+						r = radius * Math.random();
+						x = r * Math.cos( s ) * Math.sin( t );
+						y = r * Math.sin( s ) * Math.sin( t );
+						z = r * Math.cos( t );
+						pivot.position.set( x, y, z );
+						this.scene.add( pivot );
+						prepData.setStreamMeshesTo( pivot );
+
+						prepData.quantity = 8192;
+						prepData.dimension = Math.max( Math.random() * 500, 100 );
+						prepData.globalObjectCount = globalObjectCount++;
+
+						this.workerDirector.enqueueForRun( prepData );
+					}
+					this.workerDirector.processQueue();
+				};
+
+				MeshSprayApp.prototype.resizeDisplayGL = function () {
+					this.controls.handleResize();
+
+					this.recalcAspectRatio();
+					this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
+
+					this.updateCamera();
+				};
+
+				MeshSprayApp.prototype.recalcAspectRatio = function () {
+					this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
+				};
+
+				MeshSprayApp.prototype.resetCamera = function () {
+					this.camera.position.copy( this.cameraDefaults.posCamera );
+					this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
+
+					this.updateCamera();
+				};
+
+				MeshSprayApp.prototype.updateCamera = function () {
+					this.camera.aspect = this.aspectRatio;
+					this.camera.lookAt( this.cameraTarget );
+					this.camera.updateProjectionMatrix();
+				};
+
+				MeshSprayApp.prototype.render = function () {
+					if ( ! this.renderer.autoClear ) this.renderer.clear();
+
+					this.controls.update();
+
+					this.cube.rotation.x += 0.05;
+					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 );
+
+			console.log( 'Starting initialisation phase...' );
+			app.initGL();
+			app.resizeDisplayGL();
+			app.initContent();
+
+			render();
+
+		</script>
+	</body>
+</html>