Browse Source

Merge pull request #11200 from kaisalmen/wwobjloader2_docs

wwobjloader2 docs and code enhancements
Mr.doob 8 years ago
parent
commit
af446c3b01

+ 114 - 0
docs/examples/loaders/OBJLoader2.html

@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading an <em>.obj</em> resource.</div>
+
+		<h2>Example</h2>
+
+		<code>
+		// instantiate the loader
+		var loader = new THREE.OBJLoader2();
+
+		// function called on successful load
+		var intergrateIntoScene = function ( object ) {
+			scene.add( object );
+		};
+
+		// load a resource from provided URL
+		loader.load( 'obj/female02/female02.obj', intergrateIntoScene );
+		</code>
+
+		[example:webgl_loader_obj2]
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:LoadingManager manager] )</h3>
+		<div>
+		[page:LoadingManager manager] — The [page:LoadingManager loadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager].
+		</div>
+		<div>
+			Use [name] to load OBJ data from files or to parse OBJ data from arraybuffer or text.
+		</div>
+
+		<h2>Properties</h2>
+
+
+		<h2>Methods</h2>
+
+		<h3>[method:null load]( [page:String url], [page:Function onLoad], [page:Function onProgress], [page:Function onError], [page:Boolean useArrayBuffer] )</h3>
+		<div>
+			[page:String url] — URL of the file to load<br />
+			[page:Function onLoad] — Called after loading was successfully completed. The argument will be the loaded [page:Object3D].<br />
+			[page:Function onProgress] — Called to report progress of loading. The argument will be the XmlHttpRequest instance, that contain .[page:Integer total] and .[page:Integer loaded] bytes.<br />
+			[page:Function onError]  Called after an error occurred during loading.<br />
+			[page:boolean useArrayBuffer] — Set this to false to force string based parsing<br />
+		</div>
+		<div>
+			Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer
+		</div>
+
+		<h3>[method:Object3D parse]( [page:ArrayBuffer arrayBuffer] )</h3>
+		<div>
+			[page:ArrayBuffer arrayBuffer] — OBJ data as Uint8Array
+		</div>
+		<div>
+			Default parse function: Parses OBJ file content stored in arrayBuffer and returns the [page:Object3D sceneGraphBaseNode].
+		</div>
+
+		<h3>[method:Object3D parseText]( [page:String test] )</h3>
+		<div>
+			[page:String text] — OBJ data as string
+		</div>
+		<div>
+			Legacy parse function: Parses OBJ file content stored in string and returns the [page:Object3D sceneGraphBaseNode].
+		</div>
+
+		<h3>[method:null setMaterials] ( Array of [page:Material materials] )</h3>
+		<div>
+			Array of [page:Material materials] — Array of [page:Material Materials] from MTLLoader
+		</div>
+		<div>
+			Set materials loaded by MTLLoader or any other supplier of an Array of [page:Material Materials].
+		</div>
+
+		<h3>[method:null setPath] ( [page:String path] )</h3>
+		<div>
+			[page:String path] — The basePath
+		</div>
+		<div>
+			Base path to use.
+		</div>
+
+		<h3>[method:null setSceneGraphBaseNode] ( [page:Object3D sceneGraphBaseNode] )</h3>
+		<div>
+			[page:Object3D sceneGraphBaseNode] — Scenegraph object where meshes will be attached
+		</div>
+		<div>
+			Set the node where the loaded objects will be attached.
+		</div>
+
+		<h3>[method:null setDebug]( [page:Boolean parserDebug], [page:Boolean meshCreatorDebug] )</h3>
+		<div>
+			[page:Boolean parserDebug] — Internal Parser will produce debug output<br>
+			[page:Boolean meshCreatorDebug] — Internal MeshCreator will produce debug output
+		</div>
+		<div>
+			Allows to set debug mode for the parser and the meshCreator.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader2.js examples/js/loaders/OBJLoader2.js]
+	</body>
+</html>

+ 404 - 0
docs/examples/loaders/WWOBJLoader2.html

@@ -0,0 +1,404 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading an <em>.obj</em> resource within a web worker.</div>
+
+		<h2>Sub-Classes</h2>
+		[page:WWOBJLoader2.PrepDataArrayBuffer]<br>
+		[page:WWOBJLoader2.PrepDataFile]<br>
+		[page:WWOBJLoader2.PrepDataCallbacks]<br>
+		[page:WWOBJLoader2.LoadedMeshUserOverride]<br>
+		[page:WWOBJLoader2.WWOBJLoader2Director]
+
+		<h2>Example</h2>
+
+		<code>
+			// instantiate the loader
+			var loader = new THREE.OBJLoader2.WWOBJLoader2();
+
+			// load an OBJ file by providing a name, the path and the file name
+			var prepData = new THREE.OBJLoader2.WWOBJLoader2.PrepDataFile(
+				'female02',
+				'obj/female02/',
+				'female02.obj'
+			);
+
+			// set where to add the loaded data in the scene graph.
+			prepData.setSceneGraphBaseNode( scene );
+
+			// provide the preparation data to the loader and let it run.
+			loader.prepareRun( prepData );
+			loader.run();
+		</code>
+
+		[example:webgl_loader_obj2_ww] — Simple example that allows to load own models via file selection.<br>
+		[example:webgl_loader_obj2_ww_parallels] — Advanced example using [page:WWOBJLoader2.WWOBJLoader2Director] for orchestration of multiple workers.
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]()</h3>
+		<div>
+			OBJ data will be loaded by dynamically created web worker.<br>
+			First feed instructions with: [page:WWOBJLoader2.prepareRun prepareRun]<br>
+			Then execute with: [page:WWOBJLoader2.run run]
+		</div>
+
+		<h2>Properties</h2>
+
+
+		<h2>Methods</h2>
+
+		<h3>[method:null prepareRun]( [page:Object params] )</h3>
+		<div>
+			[page:Object params] — Either [page:WWOBJLoader2.PrepDataArrayBuffer] or [page:WWOBJLoader2.PrepDataFile]
+		</div>
+		<div>
+			Set all parameters for required for execution of [page:WWOBJLoader2.run run].
+		</div>
+
+
+		<h3>[method:null run]()</h3>
+		<div>
+			Run the loader according the preparation instruction provided in [page:WWOBJLoader2.prepareRun prepareRun].
+		</div>
+
+
+		<h3>[method:null setCrossOrigin]( [page:String crossOrigin] )</h3>
+		<div>
+			[page:String crossOrigin] — CORS value
+		</div>
+		<div>
+			Sets the CORS string to be used.
+		</div>
+
+
+		<h3>[method:null setDebug]( [page:Boolean enabled] )</h3>
+		<div>
+			[page:Boolean enabled] — True or false
+		</div>
+		<div>
+			Enable or disable debug logging.
+		</div>
+
+
+		<h3>[method:null setRequestTerminate]( [page:Boolean requestTerminate] )</h3>
+		<div>
+			[page:Boolean requestTerminate] — True or false
+		</div>
+		<div>
+			Call requestTerminate to terminate the web worker and free local resource after execution.
+		</div>
+
+
+		<h3>[method:null registerCallbackCompletedLoading]( [page:Function callbackCompletedLoading] )</h3>
+		<div>
+			[page:Function callbackCompletedLoading] — 	Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called once loading of the complete model is completed.
+		</div>
+
+
+		<h3>[method:null registerCallbackProgress]( [page:Function callbackProgress] )</h3>
+		<div>
+			[page:Function callbackProgress] — 	Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is invoked by internal function "_announceProgress" to print feedback.
+		</div>
+
+
+		<h3>[method:null registerCallbackMaterialsLoaded]( [page:Function callbackMaterialsLoaded] )</h3>
+		<div>
+			[page:Function callbackMaterialsLoaded] — 	Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called once materials have been loaded. It allows to alter and return materials.
+		</div>
+
+
+		<h3>[method:null registerCallbackMeshLoaded]( [page:Function callbackMeshLoaded] )</h3>
+		<div>
+			[page:Function callbackMeshLoaded] — 	Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called every time a mesh was loaded. Use [page:WWOBJLoader2.LoadedMeshUserOverride] for alteration instructions (geometry, material or disregard mesh).
+		</div>
+
+		<h3>[method:null registerCallbackErrorWhileLoading]( [page:Function callbackErrorWhileLoading] )</h3>
+		<div>
+			[page:Function callbackErrorWhileLoading] — 	Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called to report an error that prevented loading.
+		</div>
+
+
+		<h3>[method:null clearAllCallbacks]()</h3>
+		<div>
+			Clears all registered callbacks.
+		</div>
+
+
+		<h2>Sub-Classes</h2>
+		<br>
+		<a name="PrepDataArrayBuffer"></a><h1>PrepDataArrayBuffer</h1>
+		<h2>Constructor</h2>
+
+		<h3>PrepDataArrayBuffer( [page:String modelName], [page:Uint8Array objAsArrayBuffer], [page:String pathTexture], [page:String mtlAsString] )</h3>
+		<div>
+			[page:String modelName] — Overall name of the model<br>
+			[page:Uint8Array objAsArrayBuffer] — OBJ file content as ArrayBuffer<br>
+			[page:String pathTexture] — Path to texture files<br>
+			[page:String mtlAsString] — MTL file content as string
+		</div>
+		<div>
+			Instruction to configure [page:WWOBJLoader2.prepareRun] to load OBJ from given ArrayBuffer and MTL from given String.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:PrepDataCallbacks getCallbacks]()</h3>
+		<div>
+			Returns all callbacks as [page:WWOBJLoader2.PrepDataCallbacks].
+		</div>
+
+
+		<h3>[method:null setRequestTerminate]( [page:Boolean requestTerminate] )</h3>
+		<div>
+			[page:Boolean requestTerminate] — Default is false
+		</div>
+		<div>
+			Request termination of web worker and free local resources after execution.
+		</div>
+
+
+		<h3>[method:null setSceneGraphBaseNode]( [page:THREE.Object3D sceneGraphBaseNode] )</h3>
+		<div>
+			[page:Object3D sceneGraphBaseNode] — Scene graph object
+		</div>
+		<div>
+			[page:Object3D] where meshes will be attached.
+		</div>
+
+
+		<h3>[method:null setStreamMeshes]( [page:Boolean streamMeshes] )</h3>
+		<div>
+			[page:Boolean streamMeshes] — Default is true
+		</div>
+		<div>
+			Singles meshes are directly integrated into scene when loaded or later.
+		</div>
+		<br>
+		<br>
+
+
+		<a name="PrepDataFile"></a><h1>PrepDataFile</h1>
+		<h2>Constructor</h2>
+
+		<h3>PrepDataFile( [page:String modelName], [page:String pathObj], [page:String fileObj], [page:String pathTexture], [page:String fileMtl] )</h3>
+		<div>
+			[page:String modelName] — Overall name of the model<br>
+			[page:String pathObj] — Path to OBJ file<br>
+			[page:String fileObj] — OBJ file name<br>
+			[page:String pathTexture] — Path to texture files<br>
+			[page:String fileMtl] — MTL file name
+		</div>
+		<div>
+			Instruction to configure [page:WWOBJLoader2.prepareRun] to load OBJ and MTL from files.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:PrepDataCallbacks getCallbacks]()</h3>
+		<div>
+			Returns all callbacks as [page:WWOBJLoader2.PrepDataCallbacks].
+		</div>
+
+
+		<h3>[method:null setRequestTerminate]( [page:Boolean requestTerminate] )</h3>
+		<div>
+			[page:Boolean requestTerminate] — Default is false
+		</div>
+		<div>
+			Request termination of web worker and free local resources after execution.
+		</div>
+
+
+		<h3>[method:null setSceneGraphBaseNode]( [page:THREE.Object3D sceneGraphBaseNode] )</h3>
+		<div>
+			[page:Object3D sceneGraphBaseNode] — Scene graph object
+		</div>
+		<div>
+			[page:Object3D] where meshes will be attached.
+		</div>
+
+
+		<h3>[method:null setStreamMeshes]( [page:Boolean streamMeshes] )</h3>
+		<div>
+			[page:Boolean streamMeshes] — Default is true
+		</div>
+		<div>
+			Singles meshes are directly integrated into scene when loaded or later.
+		</div>
+		<br>
+		<br>
+
+
+		<a name="PrepDataCallbacks"></a><h1>PrepDataCallbacks</h1>
+		<h2>Constructor</h2>
+
+		<h3>PrepDataCallbacks()</h3>
+		<div>
+			Callbacks utilized by functions working with [page:WWOBJLoader2.PrepDataArrayBuffer] or [page:WWOBJLoader2.PrepDataFile].
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:null registerCallbackCompletedLoading]( [page:Function callbackCompletedLoading] )</h3>
+		<div>
+			[page:Function callbackCompletedLoading] — Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called once loading of the complete model is completed.
+		</div>
+
+
+		<h3>[method:null registerCallbackProgress]( [page:Function callbackProgress] )</h3>
+		<div>
+			[page:Function callbackProgress] — Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is invoked by internal function "_announceProgress" to print feedback.
+		</div>
+
+
+		<h3>[method:null registerCallbackErrorWhileLoading]( [page:Function callbackErrorWhileLoading] )</h3>
+		<div>
+			[page:Function callbackErrorWhileLoading] — Callback function for described functionality
+		</div>
+		<div>
+			Report if an error prevented loading.
+		</div>
+
+
+		<h3>[method:null registerCallbackMaterialsLoaded]( [page:Function callbackMaterialsLoaded] )</h3>
+		<div>
+			[page:Function callbackMaterialsLoaded] — Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called once materials have been loaded. It allows to alter and return materials.
+		</div>
+
+
+		<h3>[method:null registerCallbackMeshLoaded]( [page:Function callbackMeshLoaded] )</h3>
+		<div>
+			[page:Function callbackMeshLoaded] — Callback function for described functionality
+		</div>
+		<div>
+			Register callback function that is called every time a mesh was loaded. Use [page:WWOBJLoader2.LoadedMeshUserOverride] for alteration instructions (geometry, material or disregard mesh).
+		</div>
+		<br>
+		<br>
+
+
+		<a name="LoadedMeshUserOverride"></a><h1>LoadedMeshUserOverride</h1>
+		<h2>Constructor</h2>
+
+		<h3>LoadedMeshUserOverride( [page:Boolean disregardMesh], [page:THREE.BufferGeometry bufferGeometry], [page:THREE.Material material] )</h3>
+		<div>
+			[page:Boolean disregardMesh] — Tell [page:WWOBJLoader2] to completely disregard this mesh<br>
+			[page:BufferGeometry bufferGeometry] — The [page:BufferGeometry] to be used<br>
+			[page:Material material] — The [page:Material] to be used
+		</div>
+		<div>
+			Object to return by THREE.OBJLoader2.WWOBJLoader2.callbacks.meshLoaded. Used to adjust bufferGeometry or material or prevent complete loading of mesh.
+		</div>
+		<br>
+		<br>
+
+
+		<a name="WWOBJLoader2Director"></a><h1>WWOBJLoader2Director</h1>
+		<h2>Constructor</h2>
+
+		<h3>WWOBJLoader2Director()</h3>
+		<div>
+			Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).<br>
+			Workflow:<br>
+				prepareWorkers<br>
+				enqueueForRun<br>
+				processQueue<br>
+				deregister
+		</div>
+
+		<h3>[method:null prepareWorkers]( [page:WWOBJLoader2.PrepDataCallbacks globalCallbacks], [page:Number maxQueueSize], [page:Number maxWebWorkers] )</h3>
+		<div>
+			[page:WWOBJLoader2.PrepDataCallbacks globalCallbacks] — Register global callbacks used by all web workers<br>
+			[page:Number maxQueueSize] — Set the maximum size of the instruction queue (1-1024)<br>
+			[page:Number maxWebWorkers] — Set the maximum amount of workers (1-16)
+		</div>
+		<div>
+			Create or destroy workers according limits. Set the name and register callbacks for dynamically created web workers.
+		</div>
+
+
+		<h3>[method:null enqueueForRun]( [page:Object runParams] )</h3>
+		<div>
+			[page:Object runParams] — Either [page:WWOBJLoader2.PrepDataArrayBuffer] or [page:WWOBJLoader2.PrepDataFile]
+		</div>
+		<div>
+			Store run instructions in internal instructionQueue.
+		</div>
+
+
+		<h3>[method:null processQueue]()</h3>
+		<div>
+			Process the instructionQueue until it is depleted.
+		</div>
+
+
+		<h3>[method:null deregister]()</h3>
+		<div>
+			Terminate all workers
+		</div>
+
+
+		<h3>[method:null getMaxQueueSize]()</h3>
+		<div>
+			Returns the maximum length of the instruction queue.
+		</div>
+
+
+		<h3>[method:null getMaxWebWorkers]()</h3>
+		<div>
+			Returns the maximum number of workers.
+		</div>
+
+
+		<h3>[method:null setCrossOrigin]( [page:String crossOrigin] )</h3>
+		<div>
+			[page:String crossOrigin] — CORS value
+		</div>
+		<div>
+			Sets the CORS string to be used.
+		</div>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader2.js examples/js/loaders/OBJLoader2.js]
+
+	</body>
+</html>

+ 2 - 0
docs/list.js

@@ -349,6 +349,8 @@ var list = {
 			"GLTF2Loader": "examples/loaders/GLTF2Loader",
 			"MTLLoader": "examples/loaders/MTLLoader",
 			"OBJLoader": "examples/loaders/OBJLoader",
+			"OBJLoader2": "examples/loaders/OBJLoader2",
+			"WWOBJLoader2": "examples/loaders/WWOBJLoader2",
 			"PCDLoader": "examples/loaders/PCDLoader",
 			"PDBLoader": "examples/loaders/PDBLoader",
 			"SVGLoader": "examples/loaders/SVGLoader",

+ 58 - 33
examples/js/loaders/OBJLoader2.js

@@ -6,18 +6,20 @@
 'use strict';
 
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
-THREE.OBJLoader2.version = '1.1.0';
 
 /**
  * Use this class to load OBJ data from files or to parse OBJ data from arraybuffer or text
  * @class
  *
- * @param {THREE.DefaultLoadingManager} [manager] Extension of {@link THREE.DefaultLoadingManager}
+ * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
  */
 THREE.OBJLoader2 = (function () {
 
+	var OBJLOADER2_VERSION = '1.2.1';
+
 	function OBJLoader2( manager ) {
-		this.manager = ( manager == null ) ? THREE.DefaultLoadingManager : 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 );
@@ -29,17 +31,17 @@ THREE.OBJLoader2 = (function () {
 	}
 
 	/**
-	 * Base path to use
+	 * Base path to use.
 	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {string} path The basepath
 	 */
 	OBJLoader2.prototype.setPath = function ( path ) {
-		this.path = ( path == null ) ? this.path : path;
+		this.path = Validator.verifyInput( path, this.path );
 	};
 
 	/**
-	 * Set the node where the loaded objects will be attached
+	 * Set the node where the loaded objects will be attached.
 	 * @memberOf THREE.OBJLoader2
 	 *
 	 * @param {THREE.Object3D} sceneGraphBaseNode Scenegraph object where meshes will be attached
@@ -49,21 +51,21 @@ THREE.OBJLoader2 = (function () {
 	};
 
 	/**
-	 * Set materials loaded by MTLLoader
+	 * Set materials loaded by MTLLoader or any other supplier of an Array of {@link THREE.Material}.
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {THREE.MTLLoader.MaterialCreator.materials[]} materials {@link THREE.MTLLoader.MaterialCreator.materials}
+	 * @param {THREE.Material[]} materials  Array of {@link THREE.Material} from MTLLoader
 	 */
 	OBJLoader2.prototype.setMaterials = function ( materials ) {
 		this.meshCreator.setMaterials( materials );
 	};
 
 	/**
-	 * Allows to set debug mode for the parser and the meshCreator
+	 * Allows to set debug mode for the parser and the meshCreator.
 	 * @memberOf THREE.OBJLoader2
 	 *
-	 * @param {boolean} parserDebug {@link Parser} will produce debug output
-	 * @param {boolean} meshCreatorDebug {@link THREE.OBJLoader2.MeshCreator} will produce debug output
+	 * @param {boolean} parserDebug Internal Parser will produce debug output
+	 * @param {boolean} meshCreatorDebug Internal MeshCreator will produce debug output
 	 */
 	OBJLoader2.prototype.setDebug = function ( parserDebug, meshCreatorDebug ) {
 		this.parser.setDebug( parserDebug );
@@ -76,20 +78,20 @@ THREE.OBJLoader2 = (function () {
 	 *
 	 * @param {string} url URL of the file to load
 	 * @param {callback} onLoad Called after loading was successfully completed
-	 * @param {callback} onProgress Called to report progress of loading
+	 * @param {callback} onProgress Called to report progress of loading. The argument will be the XmlHttpRequest instance, that contain {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
 	 */
 	OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, useArrayBuffer ) {
 		this._validate();
 		this.fileLoader.setPath( this.path );
-		this.fileLoader.setResponseType( ( useArrayBuffer || useArrayBuffer == null ) ? 'arraybuffer' : 'text' );
+		this.fileLoader.setResponseType( useArrayBuffer !== false ? 'arraybuffer' : 'text' );
 
 		var scope = this;
 		scope.fileLoader.load( url, function ( content ) {
 
 			// only use parseText if useArrayBuffer is explicitly set to false
-			onLoad( ( useArrayBuffer || useArrayBuffer == null ) ? scope.parse( content ) : scope.parseText( content ) );
+			onLoad( useArrayBuffer !== false ? scope.parse( content ) : scope.parseText( content ) );
 
 		}, onProgress, onError );
 	};
@@ -147,8 +149,7 @@ THREE.OBJLoader2 = (function () {
 	OBJLoader2.prototype._validate = function () {
 		if ( this.validated ) return;
 
-		this.fileLoader = ( this.fileLoader == null ) ? new THREE.FileLoader( this.manager ) : this.fileLoader;
-		this.setPath();
+		this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );
 		this.parser.validate();
 		this.meshCreator.validate();
 
@@ -207,6 +208,32 @@ THREE.OBJLoader2 = (function () {
 		QUAD_INDICES_3: [ 1, 4, 7, 7, 10, 1 ]
 	};
 
+	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
@@ -221,7 +248,7 @@ THREE.OBJLoader2 = (function () {
 		}
 
 		Parser.prototype.setDebug = function ( debug ) {
-			this.debug = ( debug == null ) ? this.debug : debug;
+			if ( debug === true || debug === false ) this.debug = debug;
 		};
 
 		Parser.prototype.validate = function () {
@@ -487,9 +514,9 @@ THREE.OBJLoader2 = (function () {
 			this.uvs = [];
 
 			// faces are stored according combined index of group, material and smoothingGroup (0 or not)
-			this.mtllibName = ( mtllibName != null ) ? mtllibName : 'none';
-			this.objectName = ( objectName != null ) ? objectName : 'none';
-			this.groupName = ( groupName != null ) ? groupName : 'none';
+			this.mtllibName = Validator.verifyInput( mtllibName, 'none' );
+			this.objectName = Validator.verifyInput( objectName, 'none' );
+			this.groupName = Validator.verifyInput( groupName, 'none' );
 			this.activeMtlName = 'none';
 			this.activeSmoothingGroup = 1;
 
@@ -563,7 +590,7 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		RawObject.prototype.pushUsemtl = function ( mtlName ) {
-			if ( this.activeMtlName === mtlName || mtlName == null ) return;
+			if ( this.activeMtlName === mtlName || ! Validator.isValid( mtlName ) ) return;
 			this.activeMtlName = mtlName;
 			this.mtlCount++;
 
@@ -581,16 +608,11 @@ THREE.OBJLoader2 = (function () {
 
 		RawObject.prototype.verifyIndex = function () {
 			var index = this.buildIndex( this.activeMtlName, ( this.activeSmoothingGroup === 0 ) ? 0 : 1 );
-			if ( this.rawObjectDescriptions[ index ] == null ) {
-
-				this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ] =
-					new RawObjectDescription(
-						this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup
-					);
-
-			} else {
+			this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ];
+			if ( ! Validator.isValid( this.rawObjectDescriptionInUse ) ) {
 
-				this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ];
+				this.rawObjectDescriptionInUse = new RawObjectDescription( this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup );
+				this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse;
 
 			}
 		};
@@ -815,15 +837,17 @@ THREE.OBJLoader2 = (function () {
 		}
 
 		MeshCreator.prototype.setSceneGraphBaseNode = function ( sceneGraphBaseNode ) {
-			this.sceneGraphBaseNode = ( sceneGraphBaseNode == null ) ? ( this.sceneGraphBaseNode == null ? new THREE.Group() : this.sceneGraphBaseNode ) : sceneGraphBaseNode;
+			this.sceneGraphBaseNode = Validator.verifyInput( sceneGraphBaseNode, this.sceneGraphBaseNode );
+			this.sceneGraphBaseNode = Validator.verifyInput( this.sceneGraphBaseNode, new THREE.Group() );
 		};
 
 		MeshCreator.prototype.setMaterials = function ( materials ) {
-			this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials;
+			this.materials = Validator.verifyInput( materials, this.materials );
+			this.materials = Validator.verifyInput( this.materials, { materials: [] } );
 		};
 
 		MeshCreator.prototype.setDebug = function ( debug ) {
-			this.debug = ( debug == null ) ? this.debug : debug;
+			if ( debug === true || debug === false ) this.debug = debug;
 		};
 
 		MeshCreator.prototype.validate = function () {
@@ -992,6 +1016,7 @@ THREE.OBJLoader2 = (function () {
 	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 );

+ 408 - 171
examples/js/loaders/WWOBJLoader2.js

@@ -1,12 +1,11 @@
-/**
-  * @author Kai Salmen / https://kaisalmen.de
-  * Development repository: https://github.com/kaisalmen/WWOBJLoader
-  */
-
-'use strict';
-
+/**
+  * @author Kai Salmen / https://kaisalmen.de
+  * Development repository: https://github.com/kaisalmen/WWOBJLoader
+  */
+
+'use strict';
+
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
-THREE.OBJLoader2.version = '1.1.0';
 
 /**
  * OBJ data will be loaded by dynamically created web worker.
@@ -16,11 +15,17 @@ THREE.OBJLoader2.version = '1.1.0';
  */
 THREE.OBJLoader2.WWOBJLoader2 = (function () {
 
+	var WWOBJLOADER2_VERSION = '1.2.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!";
@@ -39,17 +44,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		this.running = false;
 		this.requestTerminate = false;
 
-		this.callbacks = {
-			progress: null,
-			completedLoading: null,
-			errorWhileLoading: null,
-			materialsLoaded: null,
-			meshLoaded: null,
-			director: {
-				completedLoading: null,
-				errorWhileLoading: null
-			}
-		};
+		this.clearAllCallbacks();
 
 		this.manager = THREE.DefaultLoadingManager;
 		this.fileLoader = new THREE.FileLoader( this.manager );
@@ -70,10 +65,10 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	/**
-	 * Set enable or disable debug logging
+	 * Enable or disable debug logging.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
 	 *
-	 * @param {boolean} enabled
+	 * @param {boolean} enabled True or false
 	 */
 	WWOBJLoader2.prototype.setDebug = function ( enabled ) {
 		this.debug = enabled;
@@ -90,68 +85,83 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	/**
-	 * Register callback function that is invoked by internal function "_announceProgress" to print feedback
+	 * 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 ( callbackProgress != null ) this.callbacks.progress = callbackProgress;
+		if ( Validator.isValid( callbackProgress ) ) this.callbacks.progress.push( callbackProgress );
 	};
 
 	/**
-	 * Register callback function that is called once loading of the complete model is completed
+	 * 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 ( callbackCompletedLoading != null ) this.callbacks.completedLoading = 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
+	 * 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 ( callbackMaterialsLoaded != null ) this.callbacks.materialsLoaded = callbackMaterialsLoaded;
+		if ( Validator.isValid( callbackMaterialsLoaded ) ) this.callbacks.materialsLoaded.push( callbackMaterialsLoaded );
 	};
 
 	/**
-	 * Register callback function that is called every time a mesh was loaded
+	 * 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 ( callbackMeshLoaded != null ) this.callbacks.meshLoaded = callbackMeshLoaded;
+		if ( Validator.isValid( callbackMeshLoaded ) ) this.callbacks.meshLoaded.push( callbackMeshLoaded );
 	};
 
 	/**
-	 * Report if an error prevented loading
+	 * 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 ( callbackErrorWhileLoading != null ) this.callbacks.errorWhileLoading = 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
+	 * Call requestTerminate to terminate the web worker and free local resource after execution.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2
 	 *
-	 * @param {boolean} requestTerminate
+	 * @param {boolean} requestTerminate True or false
 	 */
 	WWOBJLoader2.prototype.setRequestTerminate = function ( requestTerminate ) {
-		this.requestTerminate = ( requestTerminate != null && requestTerminate ) ? true : false;
+		this.requestTerminate = requestTerminate === true;
 	};
 
 	WWOBJLoader2.prototype._validate = function () {
 		if ( this.validated ) return;
-		if ( this.worker == null ) {
+		if ( ! Validator.isValid( this.worker ) ) {
 
 			this._buildWebWorkerCode();
 			var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } );
@@ -173,9 +183,9 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		this.running = true;
 		this.requestTerminate = false;
 
-		this.fileLoader = ( this.fileLoader == null ) ? new THREE.FileLoader( this.manager ) : this.fileLoader;
-		this.mtlLoader = ( this.mtlLoader == null ) ?  new THREE.MTLLoader() : this.mtlLoader;
-		if ( this.crossOrigin != null ) this.mtlLoader.setCrossOrigin( this.crossOrigin );
+		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;
@@ -253,7 +263,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		var processLoadedMaterials = function ( materialCreator ) {
 			var materialCreatorMaterials = [];
 			var materialNames = [];
-			if ( materialCreator != null ) {
+			if ( Validator.isValid( materialCreator ) ) {
 
 				materialCreator.preload();
 				materialCreatorMaterials = materialCreator.materials;
@@ -274,13 +284,15 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 				materialNames: materialNames
 			} );
 
-			if ( scope.callbacks.materialsLoaded != null ) {
+			var materialsFromCallback;
+			var callbackMaterialsLoaded;
+			for ( var index in scope.callbacks.materialsLoaded ) {
 
-				var materialsCallback = scope.callbacks.materialsLoaded( scope.materials );
-				if ( materialsCallback != null ) scope.materials = materialsCallback;
+				callbackMaterialsLoaded = scope.callbacks.materialsLoaded[ index ];
+				materialsFromCallback = callbackMaterialsLoaded( scope.materials );
+				if ( Validator.isValid( materialsFromCallback ) ) scope.materials = materialsFromCallback;
 
 			}
-
 			if ( scope.dataAvailable && scope.objAsArrayBuffer ) {
 
 				scope.worker.postMessage({
@@ -337,17 +349,24 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		this.mtlLoader.setPath( this.pathTexture );
 		if ( this.dataAvailable ) {
 
-			processLoadedMaterials( ( this.mtlAsString != null ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
+			processLoadedMaterials( Validator.isValid( this.mtlAsString ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
 
 		} else {
 
-			if ( this.fileMtl == null ) {
+			if ( Validator.isValid( this.fileMtl ) ) {
 
-				processLoadedMaterials();
+				var onError = function ( event ) {
+					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 {
 
-				this.mtlLoader.load( this.fileMtl, processLoadedMaterials );
+				processLoadedMaterials();
 
 			}
 
@@ -360,11 +379,12 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		switch ( payload.cmd ) {
 			case 'objData':
 
-				this.counter ++;
-				var bufferGeometry = new THREE.BufferGeometry();
+				this.counter++;
+				var meshName = payload.meshName;
 
+				var bufferGeometry = new THREE.BufferGeometry();
 				bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( payload.vertices ), 3 ) );
-				if ( payload.normals !== null ) {
+				if ( Validator.isValid( payload.normals ) ) {
 
 					bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.normals ), 3 ) );
 
@@ -373,7 +393,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 					bufferGeometry.computeVertexNormals();
 
 				}
-				if ( payload.uvs !== null ) {
+				if ( Validator.isValid( payload.uvs ) ) {
 
 					bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.uvs ), 2 ) );
 
@@ -386,7 +406,8 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 				var createMultiMaterial = payload.multiMaterial;
 				var multiMaterials = [];
 
-				for ( var key in materialDescriptions ) {
+				var key;
+				for ( key in materialDescriptions ) {
 
 					materialDescription = materialDescriptions[ key ];
 					material = this.materials[ materialDescription.name ];
@@ -421,7 +442,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 					material = multiMaterials;
 					var materialGroups = payload.materialGroups;
 					var materialGroup;
-					for ( var key in materialGroups ) {
+					for ( key in materialGroups ) {
 
 						materialGroup = materialGroups[ key ];
 						bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index );
@@ -429,41 +450,68 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 					}
 
 				}
-				if ( this.callbacks.meshLoaded !== null ) {
 
-					var materialOverride = this.callbacks.meshLoaded( payload.meshName, material );
-					if ( materialOverride != null ) material = materialOverride;
+				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;
+
+					}
 
 				}
-				var mesh = new THREE.Mesh( bufferGeometry, material );
-				mesh.name = payload.meshName;
-				if ( this.streamMeshes ) {
 
-					this.sceneGraphBaseNode.add( mesh );
+				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.meshStore.push( mesh );
+					this._announceProgress( 'Removing mesh:', meshName );
 
 				}
-				var output = '(' + this.counter + '): ' + payload.meshName;
-				this._announceProgress( 'Adding mesh', output );
 				break;
 
 			case 'complete':
 
 				if ( ! this.streamMeshes ) {
 
-					for ( var key in this.meshStore ) {
+					for ( var meshStoreKey in this.meshStore ) {
 
-						this.sceneGraphBaseNode.add( this.meshStore[ key ] );
+						if ( this.meshStore.hasOwnProperty( meshStoreKey ) ) this.sceneGraphBaseNode.add( this.meshStore[ meshStoreKey ] );
 
 					}
 
 				}
 
 				console.timeEnd( 'WWOBJLoader2' );
-				if ( payload.msg != null ) {
+				if ( Validator.isValid( payload.msg ) ) {
 
 					this._announceProgress( payload.msg );
 
@@ -488,7 +536,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	WWOBJLoader2.prototype._terminate = function () {
-		if ( this.worker != null ) {
+		if ( Validator.isValid( this.worker ) ) {
 
 			if ( this.running ) throw 'Unable to gracefully terminate worker as it is currently running!';
 
@@ -504,15 +552,26 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 
 	WWOBJLoader2.prototype._finalize = function ( reason, requestTerminate ) {
 		this.running = false;
+		var index;
+		var callback;
+
 		if ( reason === 'complete' ) {
 
-			if ( this.callbacks.completedLoading != null ) this.callbacks.completedLoading( this.modelName, this.instanceNo, this.requestTerminate );
-			if ( this.callbacks.director.completedLoading != null ) this.callbacks.director.completedLoading( this.modelName, this.instanceNo, this.requestTerminate );
+			for ( index in this.callbacks.completedLoading ) {
+
+				callback = this.callbacks.completedLoading[ index ];
+				callback( this.modelName, this.instanceNo, this.requestTerminate );
+
+			}
 
 		} else if ( reason === 'error' ) {
 
-			if ( this.callbacks.errorWhileLoading != null ) this.callbacks.errorWhileLoading( this.modelName, this.instanceNo, this.requestTerminate );
-			if ( this.callbacks.director.errorWhileLoading != null ) this.callbacks.director.errorWhileLoading( this.modelName, this.instanceNo, this.requestTerminate );
+			for ( index in this.callbacks.errorWhileLoading ) {
+
+				callback = this.callbacks.errorWhileLoading[ index ];
+				callback( this.modelName, this.instanceNo, this.requestTerminate );
+
+			}
 
 		}
 		this.validated = false;
@@ -525,32 +584,23 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	WWOBJLoader2.prototype._announceProgress = function ( baseText, text ) {
-		var output = "";
-		if ( baseText !== null && baseText !== undefined ) {
-
-			output = baseText;
-
-		}
-		if ( text !== null && text !== undefined ) {
+		var output = Validator.isValid( baseText ) ? baseText: "";
+		output = Validator.isValid( text ) ? output + " " + text : output;
 
-			output = output + " " + text;
+		var callbackProgress;
+		for ( var index in this.callbacks.progress ) {
 
-		}
-		if ( this.callbacks.progress !== null ) {
-
-			this.callbacks.progress( output );
+			callbackProgress = this.callbacks.progress[ index ];
+			callbackProgress( output );
 
 		}
-		if ( this.debug ) {
 
-			console.log( output );
-
-		}
+		if ( this.debug ) console.log( output );
 	};
 
 	WWOBJLoader2.prototype._buildWebWorkerCode = function ( existingWorkerCode ) {
-		if ( existingWorkerCode != null ) this.workerCode = existingWorkerCode;
-		if ( this.workerCode == null ) {
+		if ( Validator.isValid( existingWorkerCode ) ) this.workerCode = existingWorkerCode;
+		if ( ! Validator.isValid( this.workerCode ) ) {
 
 			console.time( 'buildWebWorkerCode' );
 			var wwDef = (function () {
@@ -645,11 +695,12 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 				}
 
 				WWMeshCreator.prototype.setMaterials = function ( materials ) {
-					this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials;
+					this.materials = Validator.verifyInput( materials, this.materials );
+					this.materials = Validator.verifyInput( this.materials, { materials: [] } );
 				};
 
 				WWMeshCreator.prototype.setDebug = function ( debug ) {
-					this.debug = ( debug == null ) ? this.debug : debug;
+					if ( debug === true || debug === false ) this.debug = debug;
 				};
 
 				WWMeshCreator.prototype.validate = function () {
@@ -700,6 +751,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 					var uvOffset = 0;
 
 					for ( var oodIndex in rawObjectDescriptions ) {
+						if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
 						rawObjectDescription = rawObjectDescriptions[ oodIndex ];
 
 						materialDescription = { name: rawObjectDescription.materialName, flat: false, default: false };
@@ -877,8 +929,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 			this.workerCode += '  */\n\n';
 
 			// parser re-construction
-			var objLoaderHelper = new THREE.OBJLoader2();
-			this.workerCode += objLoaderHelper._buildWebWorkerCode( buildObject, buildSingelton );
+			this.workerCode += THREE.OBJLoader2.prototype._buildWebWorkerCode( buildObject, buildSingelton );
 
 			// web worker construction
 			this.workerCode += buildSingelton( 'WWOBJLoader', 'WWOBJLoader', wwDef );
@@ -898,69 +949,240 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 })();
 
 /**
- * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ from given ArrayBuffer and MTL from given String
+ * 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
- * @param {THREE.Object3D} sceneGraphBaseNode {@link THREE.Object3D} where meshes will be attached
- * @param {boolean} streamMeshes=true Singles meshes are directly integrated into scene when loaded or later
- * @param {boolean} [requestTerminate=false] Request termination of web worker and free local resources after execution
  *
  * @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, sceneGraphBaseNode, streamMeshes, requestTerminate ) {
-
-	var data = {
-		modelName: ( modelName == null ) ? 'none' : modelName,
+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, 'none' ),
 		dataAvailable: true,
-		objAsArrayBuffer: ( objAsArrayBuffer == null ) ? null : objAsArrayBuffer,
-		pathTexture: ( pathTexture == null ) ? null : pathTexture,
-		mtlAsString: ( mtlAsString == null ) ? null : mtlAsString,
-		sceneGraphBaseNode: ( sceneGraphBaseNode == null ) ? null : sceneGraphBaseNode,
-		streamMeshes: ( streamMeshes == null ) ? true : streamMeshes,
-		requestTerminate: ( requestTerminate == null ) ? false : requestTerminate
+		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()
 	};
-
-	return data;
 };
 
 /**
- * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ and MTL from files
+ * 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
- * @param {THREE.Object3D} sceneGraphBaseNode {@link THREE.Object3D} where meshes will be attached
- * @param {boolean} streamMeshes=true Singles meshes are directly integrated into scene when loaded or later
- * @param {boolean} [requestTerminate=false] Request termination of web worker and free local resources after execution
  *
  * @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, sceneGraphBaseNode, streamMeshes, requestTerminate ) {
-
-	var data = {
-		modelName: ( modelName == null ) ? 'none' : modelName,
+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, 'none' ),
 		dataAvailable: false,
-		pathObj: ( pathObj == null ) ? null : pathObj,
-		fileObj: ( fileObj == null ) ? null : fileObj,
-		pathTexture: ( pathTexture == null ) ? null : pathTexture,
-		fileMtl: ( fileMtl == null ) ? null : fileMtl,
-		sceneGraphBaseNode: ( sceneGraphBaseNode == null ) ? null : sceneGraphBaseNode,
-		streamMeshes: ( streamMeshes == null ) ? true : streamMeshes,
-		requestTerminate: ( requestTerminate == null ) ? false : requestTerminate
+		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 )
+	};
+};
 
-	return data;
-};
 /**
  * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).
- * Use:
+ * Workflow:
  *   prepareWorkers
  *   enqueueForRun
  *   processQueue
@@ -970,6 +1192,8 @@ THREE.OBJLoader2.WWOBJLoader2.PrepDataFile = function ( modelName, pathObj, file
  */
 THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 
+	var Validator = THREE.OBJLoader2.prototype._getValidator();
+
 	var MAX_WEB_WORKER = 16;
 	var MAX_QUEUE_SIZE = 1024;
 
@@ -980,7 +1204,7 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 
 		this.workerDescription = {
 			prototypeDef: THREE.OBJLoader2.WWOBJLoader2.prototype,
-			callbacks: {},
+			globalCallbacks: {},
 			webWorkers: [],
 			codeBuffer: null
 		};
@@ -1022,22 +1246,12 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 	 * Create or destroy workers according limits. Set the name and register callbacks for dynamically created web workers.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
 	 *
-	 * @param {callback[]} callbacks Register callbacks for all web workers:
-	 * 		{ progress: null, completedLoading: null, errorWhileLoading: null, materialsLoaded: null, meshLoaded: null }
+	 * @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 ( callbacks, maxQueueSize, maxWebWorkers ) {
-		if ( callbacks != null ) {
-
-			for ( var key in callbacks ) {
-
-				if ( callbacks.hasOwnProperty( key ) ) this.workerDescription.callbacks[ key ] = callbacks[ key ];
-
-			}
-
-		}
-
+	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;
@@ -1068,10 +1282,10 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 	};
 
 	/**
-	 * Store run instructions in internal instructionQueue
+	 * Store run instructions in internal instructionQueue.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
 	 *
-	 * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile}
+	 * @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 ) {
@@ -1080,76 +1294,99 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 	};
 
 	/**
-	 * Process the instructionQueue until it is depleted
+	 * Process the instructionQueue until it is depleted.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
 	 */
 	WWOBJLoader2Director.prototype.processQueue = function () {
 		if ( this.instructionQueue.length === 0 ) return;
 
-		var webWorker;
-		var runParams;
 		var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
 		for ( var i = 0; i < length; i++ ) {
 
-			webWorker = this.workerDescription.webWorkers[ i ];
-			runParams = this.instructionQueue[ 0 ];
-			webWorker.prepareRun( runParams );
-			webWorker.run();
+			this._kickWebWorkerRun( this.workerDescription.webWorkers[ i ], this.instructionQueue[ 0 ] );
 			this.instructionQueue.shift();
 
 		}
 	};
 
-	WWOBJLoader2Director.prototype._buildWebWorker = function () {
-		var webWorker = Object.create( this.workerDescription.prototypeDef );
-		webWorker._init();
-		if ( this.crossOrigin != null )	webWorker.setCrossOrigin( this.crossOrigin );
-
-		// Ensure code string is built once and then it is just passed on to every new instance
-		if ( this.workerDescription.codeBuffer == null ) {
+	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 ) {
 
-			this.workerDescription.codeBuffer = webWorker._buildWebWorkerCode();
+			if ( workerCallbacks.hasOwnProperty( key ) && globalCallbacks.hasOwnProperty( key ) ) {
 
-		} else {
+				selectedGlobalCallback = globalCallbacks[ key ];
+				if ( Validator.isValid( selectedGlobalCallback ) ) workerCallbacks[ key ].push( selectedGlobalCallback );
 
-			webWorker._buildWebWorkerCode( this.workerDescription.codeBuffer );
+			}
 
 		}
-		for ( var key in this.workerDescription.callbacks ) {
+		// register per object callbacks
+		var runCallbacks = runParams.callbacks;
+		if ( Validator.isValid( runCallbacks ) ) {
+
+			for ( key in runCallbacks ) {
 
-			if ( webWorker.callbacks.hasOwnProperty( key ) && this.workerDescription.callbacks.hasOwnProperty( key ) ) {
+				if ( workerCallbacks.hasOwnProperty( key ) && runCallbacks.hasOwnProperty( key ) && Validator.isValid( runCallbacks[ key ] ) ) {
 
-				webWorker.callbacks[ key ] = this.workerDescription.callbacks[ key ];
+					workerCallbacks[ key ].push( runCallbacks[ key ] );
+
+				}
 
 			}
 
 		}
+
 		var scope = this;
-		var managerCompletedLoading = function ( modelName, instanceNo, requestTerminate ) {
+		var directorCompletedLoading = function ( modelName, instanceNo, requestTerminate ) {
 			scope.objectsCompleted++;
 			if ( ! requestTerminate ) {
 
-				var rekick = scope.workerDescription.webWorkers[ instanceNo ];
+				var worker = scope.workerDescription.webWorkers[ instanceNo ];
 				var runParams = scope.instructionQueue[ 0 ];
-				if ( runParams != null ) {
+				if ( Validator.isValid( runParams ) ) {
 
-					rekick.prepareRun( runParams );
-					rekick.run();
+					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.callbacks.director[ 'completedLoading' ] = managerCompletedLoading;
 		webWorker.instanceNo = this.workerDescription.webWorkers.length;
 		this.workerDescription.webWorkers.push( webWorker );
 		return webWorker;
 	};
 
 	/**
-	 * Terminate all workers
+	 * Terminate all workers.
 	 * @memberOf THREE.OBJLoader2.WWOBJLoader2Director
 	 */
 	WWOBJLoader2Director.prototype.deregister = function () {
@@ -1160,7 +1397,7 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 			webWorker.setRequestTerminate( true );
 
 		}
-		this.workerDescription.callbacks = {};
+		this.workerDescription.globalCallbacks = {};
 		this.workerDescription.webWorkers = [];
 		this.workerDescription.codeBuffer = null;
 	};

+ 3 - 2
examples/webgl_loader_obj2.html

@@ -264,9 +264,10 @@
 
 					if ( object3d.material instanceof THREE.MultiMaterial ) {
 
-						for ( var matName in object3d.material.materials ) {
+						var materials = object3d.material.materials;
+						for ( var name in materials ) {
 
-							this.traversalFunction( object3d.material.materials[ matName ] );
+							if ( materials.hasOwnProperty( name ) )	this.traversalFunction( materials[ name ] );
 
 						}
 

+ 21 - 13
examples/webgl_loader_obj2_ww.html

@@ -90,6 +90,8 @@
 
 			var WWOBJLoader2Example = (function () {
 
+				var Validator = THREE.OBJLoader2.prototype._getValidator();
+
 				function WWOBJLoader2Example( elementToBindTo ) {
 					this.renderer = null;
 					this.canvas = elementToBindTo;
@@ -181,26 +183,26 @@
 						console.log( 'Progress: ' + content );
 					};
 					var materialsLoaded = function ( materials ) {
-						var count = 0;
-						console.log( 'The following materials have been loaded:' );
-						for ( var mat in materials ) {
-							count++;
-						}
+						var count = Validator.isValid( materials ) ? materials.length : 0;
 						console.log( 'Loaded #' + count + ' materials.' );
 					};
+					var meshLoaded = function ( name, bufferGeometry, material ) {
+						console.log( 'Loaded mesh: ' + name + ' Material name: ' + material.name );
+					};
 					var completedLoading = function () {
 						console.log( 'Loading complete!' );
 					};
 					this.wwObjLoader2.registerCallbackProgress( reportProgress );
 					this.wwObjLoader2.registerCallbackCompletedLoading( completedLoading );
 					this.wwObjLoader2.registerCallbackMaterialsLoaded( materialsLoaded );
+					this.wwObjLoader2.registerCallbackMeshLoaded( meshLoaded );
 
 					return true;
 				};
 
 				WWOBJLoader2Example.prototype.loadFiles = function ( prepData ) {
-					prepData.sceneGraphBaseNode = this.pivot;
-					prepData.streamMeshes = this.streamMeshes;
+					prepData.setSceneGraphBaseNode( this.pivot );
+					prepData.setStreamMeshes( this.streamMeshes );
 					this.wwObjLoader2.prepareRun( prepData );
 					this.wwObjLoader2.run();
 				};
@@ -222,7 +224,7 @@
 
 					}
 
-					if ( fileObj == null ) {
+					if ( ! Validator.isValid( fileObj ) ) {
 						alert( 'Unable to load OBJ file from given files.' );
 					}
 
@@ -261,8 +263,10 @@
 
 				WWOBJLoader2Example.prototype.loadFilesUser = function ( objDef ) {
 					var prepData = new THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer(
-						objDef.name, objDef.objAsArrayBuffer, objDef.pathTexture, objDef.mtlAsString, this.pivot, this.streamMeshes
+						objDef.name, objDef.objAsArrayBuffer, objDef.pathTexture, objDef.mtlAsString
 					);
+					prepData.setSceneGraphBaseNode( this.pivot );
+					prepData.setStreamMeshes( this.streamMeshes );
 					this.wwObjLoader2.prepareRun( prepData );
 					this.wwObjLoader2.run();
 				};
@@ -340,9 +344,10 @@
 
 					if ( object3d.material instanceof THREE.MultiMaterial ) {
 
-						for ( var matName in object3d.material.materials ) {
+						var materials = object3d.material.materials;
+						for ( var name in materials ) {
 
-							this.traversalFunction( object3d.material.materials[ matName ] );
+							if ( materials.hasOwnProperty( name ) )	this.traversalFunction( materials[ name ] );
 
 						}
 
@@ -372,8 +377,11 @@
 							var mat = object3d.material;
 							if ( mat.hasOwnProperty( 'materials' ) ) {
 
-								for ( var mmat in mat.materials ) {
-									mat.materials[ mmat ].dispose();
+								var materials = mat.materials;
+								for ( var name in materials ) {
+
+									if ( materials.hasOwnProperty( name ) ) materials[ name ].dispose();
+
 								}
 							}
 						}

+ 60 - 35
examples/webgl_loader_obj2_ww_parallels.html

@@ -58,9 +58,9 @@
 			#dat {
 				user-select: none;
 				position: absolute;
-				left: 0px;
-				top: 0px;
-				z-Index: 2;
+				left: 0;
+				top: 0;
+				z-Index: 200;
 			}
 		</style>
 	</head>
@@ -92,6 +92,8 @@
 
 			var WWParallels = (function () {
 
+				var Validator = THREE.OBJLoader2.prototype._getValidator();
+
 				function WWParallels( elementToBindTo ) {
 					this.renderer = null;
 					this.canvas = elementToBindTo;
@@ -117,6 +119,8 @@
 
 					this.allAssets = [];
 					this.feedbackArray = null;
+
+					this.running = false;
 				}
 
 				WWParallels.prototype.initGL = function () {
@@ -192,11 +196,24 @@
 				};
 
 				WWParallels.prototype.enqueueAllAssests = function ( maxQueueSize, maxWebWorkers, streamMeshes ) {
+					if ( this.running ) {
+
+						return;
+
+					} else {
+
+						this.running = true;
+
+					}
 					var scope = this;
 					scope.wwDirector.objectsCompleted = 0;
 					scope.feedbackArray = new Array( maxWebWorkers );
-					for ( var i = 0; i < maxWebWorkers; i++ ) {
+
+					var i;
+					for ( i = 0; i < maxWebWorkers; i++ ) {
+
 						scope.feedbackArray[ i ] = 'Worker #' + i + ': Awaiting feedback';
+
 					}
 					scope.reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 
@@ -205,28 +222,33 @@
 						console.log( msg );
 						scope.feedbackArray[ instanceNo ] = msg;
 						scope.reportProgress( scope.feedbackArray.join( '\<br\>' ) );
+
+						if ( scope.wwDirector.objectsCompleted + 1 === maxQueueSize ) scope.running = false;
 					};
-					var callbackMeshLoaded = function ( meshName, material ) {
-						var replacedMaterial = null;
 
-						if ( material != null && material.name === 'defaultMaterial' || meshName === 'Mesh_Mesh_head_geo.001' ) {
-							replacedMaterial = material;
-							replacedMaterial.color = new THREE.Color( Math.random(), Math.random(), Math.random() );
+					var callbackMeshLoaded = function ( name, bufferGeometry, material ) {
+						var materialOverride;
+
+						if ( Validator.isValid( material ) && material.name === 'defaultMaterial' || name === 'Mesh_Mesh_head_geo.001' ) {
+
+							materialOverride = material;
+							materialOverride.color = new THREE.Color( Math.random(), Math.random(), Math.random() );
+
 						}
 
-						return replacedMaterial;
+						return new THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride( false, undefined, materialOverride );
 					};
 
-					this.wwDirector.prepareWorkers(
-						{
-							completedLoading: callbackCompletedLoading,
-							meshLoaded: callbackMeshLoaded
-						},
-						maxQueueSize,
-						maxWebWorkers
-					);
+					var globalCallbacks = new THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks();
+					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 callbackCompletedLoadingWalt = function () {
+						console.log( 'Callback check: WALT was loaded (#' + scope.wwDirector.objectsCompleted + ')' );
+					};
+
 					var models = [];
 					models.push( {
 						modelName: 'male02',
@@ -262,7 +284,7 @@
 						scale: 50.0
 					} );
 					models.push( {
-						modelName:'WaltHead',
+						modelName: 'WaltHead',
 						dataAvailable: false,
 						pathObj: 'obj/walt/',
 						fileObj: 'WaltHead.obj',
@@ -276,9 +298,9 @@
 					var modelIndex = 0;
 					var model;
 					var runParams;
-					for ( var i = 0; i < maxQueueSize; i++ ) {
+					for ( i = 0; i < maxQueueSize; i++ ) {
 
-						modelIndex = Math.floor( Math.random() * 5 );
+						modelIndex = Math.floor( Math.random() * models.length );
 						model = models[ modelIndex ];
 
 						pivot = new THREE.Object3D();
@@ -287,15 +309,21 @@
 							distributionBase + distributionMax * Math.random(),
 							distributionBase + distributionMax * Math.random()
 						);
-						if ( model.scale != null ) pivot.scale.set( model.scale, model.scale, model.scale );
+						if ( Validator.isValid( model.scale ) ) pivot.scale.set( model.scale, model.scale, model.scale );
 
 						this.scene.add( pivot );
 
 						model.sceneGraphBaseNode = pivot;
 
 						runParams = new THREE.OBJLoader2.WWOBJLoader2.PrepDataFile(
-							model.modelName, model.pathObj, model.fileObj, model.pathTexture, model.fileMtl, model.sceneGraphBaseNode, streamMeshes
+							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 );
 					}
@@ -308,32 +336,29 @@
 					var scope = this;
 
 					for ( var asset in this.allAssets ) {
-						ref = this.allAssets[asset];
+						ref = this.allAssets[ asset ];
 
 						var remover = function ( object3d ) {
 
-							if ( object3d === ref.sceneGraphBaseNode ) {
-								return;
-							}
+							if ( object3d === ref.sceneGraphBaseNode ) return;
 							console.log( 'Removing ' + object3d.name );
 							scope.scene.remove( object3d );
 
-							if ( object3d.hasOwnProperty( 'geometry' ) ) {
-								object3d.geometry.dispose();
-							}
+							if ( object3d.hasOwnProperty( 'geometry' ) ) object3d.geometry.dispose();
 							if ( object3d.hasOwnProperty( 'material' ) ) {
 
 								var mat = object3d.material;
 								if ( mat.hasOwnProperty( 'materials' ) ) {
 
-									for ( var mmat in mat.materials ) {
-										mat.materials[mmat].dispose();
+									var materials = mat.materials;
+									for ( var name in materials ) {
+
+										if ( materials.hasOwnProperty( name ) ) materials[ name ].dispose();
+
 									}
 								}
 							}
-							if ( object3d.hasOwnProperty( 'texture' ) ) {
-								object3d.texture.dispose();
-							}
+							if ( object3d.hasOwnProperty( 'texture' ) ) object3d.texture.dispose();
 						};
 						scope.scene.remove( ref.sceneGraphBaseNode );
 						ref.sceneGraphBaseNode.traverse( remover );