Browse Source

Updated code to version V1.2.1 and updated docs accordingly.

Code related changes

THREE.OBJLoader2.WWOBJLoader2:
- Function _receiveWorkerMessage now uses a meshDescription that allows to override material or bufferGeometry or to completely disregard the mesh. THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride was introduced for this.
- Allow usage of multiple callbacks per callback type
- THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer and THREE.OBJLoader2.WWOBJLoader2.PrepDataFile require less mandatory parameters. Setters are introduced to handle optional things

THREE.OBJLoader2.WWOBJLoader2Director:
- Added per queue object callbacks
- Global callbacks in prepareWorkers will be specified with new object OBJLoader2.WWOBJLoader2.PrepDataCallbacks. This object is also used in both PrepData objects for defining extra per model callbacks in addition to the global ones
- Callbacks will be reset and reassigned for every run

All:
- Improve code quality, especially, replaced != or == with Boolean() or ! Boolean() where applicable
- Improve logging and comments
Kai Salmen 8 years ago
parent
commit
561febce05

+ 152 - 9
docs/examples/loaders/WWOBJLoader2.html

@@ -16,6 +16,8 @@
 		<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>
@@ -131,7 +133,7 @@
 			[page:Function callbackMeshLoaded] — 	Callback function for described functionality
 		</div>
 		<div>
-			Register callback function that is called every time a mesh was loaded. Return altered [page:Material] or null from callback.
+			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>
@@ -143,6 +145,12 @@
 		</div>
 
 
+		<h3>[method:null clearAllCallbacks]()</h3>
+		<div>
+			Clears all registered callbacks.
+		</div>
+
+
 		<h2>Sub-Classes</h2>
 		<br>
 		<a name="PrepDataArrayBuffer"></a><h1>PrepDataArrayBuffer</h1>
@@ -154,13 +162,44 @@
 			[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
-			[page:Object3D sceneGraphBaseNode] [page:Object3D] where meshes will be attached
-			[page:Boolean streamMeshes] Singles meshes are directly integrated into scene when loaded or later
-			[page:Boolean requestTerminate] Request termination of web worker and free local resources after execution
 		</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>
 
@@ -175,13 +214,117 @@
 			[page:String fileObj] — OBJ file name<br>
 			[page:String pathTexture] — Path to texture files<br>
 			[page:String fileMtl] — MTL file name
-			[page:Object3D sceneGraphBaseNode] [page:Object3D] where meshes will be attached
-			[page:Boolean streamMeshes] Singles meshes are directly integrated into scene when loaded or later
-			[page:Boolean requestTerminate] Request termination of web worker and free local resources after execution
 		</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>
 
@@ -199,9 +342,9 @@
 				deregister
 		</div>
 
-		<h3>[method:null prepareWorkers]( Array of [page:Function callbacks], [page:Number maxQueueSize], [page:Number maxWebWorkers] )</h3>
+		<h3>[method:null prepareWorkers]( [page:WWOBJLoader2.PrepDataCallbacks globalCallbacks], [page:Number maxQueueSize], [page:Number maxWebWorkers] )</h3>
 		<div>
-			Array of [page:Function callbacks] — Register callbacks for all web workers: progress, completedLoading, errorWhileLoading, materialsLoaded, meshLoaded<br>
+			[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>

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

@@ -6,18 +6,18 @@
 'use strict';
 
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
-THREE.OBJLoader2.version = '1.1.0';
+THREE.OBJLoader2.version = '1.2.1';
 
 /**
  * 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 () {
 
 	function OBJLoader2( manager ) {
-		this.manager = ( manager == null ) ? THREE.DefaultLoadingManager : manager;
+		this.manager = ! Boolean( manager ) ? THREE.DefaultLoadingManager : manager;
 
 		this.path = '';
 		this.fileLoader = new THREE.FileLoader( this.manager );
@@ -29,17 +29,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 = Boolean( path ) ? 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 +49,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 +76,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 || useArrayBuffer === null || useArrayBuffer === undefined ) ? '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 || useArrayBuffer === null || useArrayBuffer === undefined ) ? scope.parse( content ) : scope.parseText( content ) );
 
 		}, onProgress, onError );
 	};
@@ -147,8 +147,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 = Boolean( this.fileLoader ) ? this.fileLoader : new THREE.FileLoader( this.manager );
 		this.parser.validate();
 		this.meshCreator.validate();
 
@@ -221,7 +220,7 @@ THREE.OBJLoader2 = (function () {
 		}
 
 		Parser.prototype.setDebug = function ( debug ) {
-			this.debug = ( debug == null ) ? this.debug : debug;
+			this.debug = Boolean( debug ) ? debug : this.debug;
 		};
 
 		Parser.prototype.validate = function () {
@@ -487,9 +486,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 = Boolean( mtllibName ) ? mtllibName : 'none';
+			this.objectName = Boolean( objectName ) ? objectName : 'none';
+			this.groupName = Boolean( groupName ) ? groupName : 'none';
 			this.activeMtlName = 'none';
 			this.activeSmoothingGroup = 1;
 
@@ -563,7 +562,7 @@ THREE.OBJLoader2 = (function () {
 		};
 
 		RawObject.prototype.pushUsemtl = function ( mtlName ) {
-			if ( this.activeMtlName === mtlName || mtlName == null ) return;
+			if ( this.activeMtlName === mtlName || mtlName === null || mtlName === undefined ) return;
 			this.activeMtlName = mtlName;
 			this.mtlCount++;
 
@@ -581,16 +580,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 ];
+			if ( ! Boolean( this.rawObjectDescriptionInUse ) ) {
 
-				this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ] =
-					new RawObjectDescription(
-						this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup
-					);
-
-			} else {
-
-				this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ];
+				this.rawObjectDescriptionInUse = new RawObjectDescription( this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup );
+				this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse;
 
 			}
 		};
@@ -815,15 +809,15 @@ THREE.OBJLoader2 = (function () {
 		}
 
 		MeshCreator.prototype.setSceneGraphBaseNode = function ( sceneGraphBaseNode ) {
-			this.sceneGraphBaseNode = ( sceneGraphBaseNode == null ) ? ( this.sceneGraphBaseNode == null ? new THREE.Group() : this.sceneGraphBaseNode ) : sceneGraphBaseNode;
+			this.sceneGraphBaseNode = Boolean( sceneGraphBaseNode ) ? sceneGraphBaseNode : ( Boolean( this.sceneGraphBaseNode ) ? this.sceneGraphBaseNode : new THREE.Group() );
 		};
 
 		MeshCreator.prototype.setMaterials = function ( materials ) {
-			this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials;
+			this.materials = Boolean( materials ) ? materials : ( Boolean( this.materials ) ? this.materials : { materials: [] } );
 		};
 
 		MeshCreator.prototype.setDebug = function ( debug ) {
-			this.debug = ( debug == null ) ? this.debug : debug;
+			this.debug = Boolean( debug ) ? debug : this.debug;
 		};
 
 		MeshCreator.prototype.validate = function () {

+ 378 - 160
examples/js/loaders/WWOBJLoader2.js

@@ -6,7 +6,7 @@
 'use strict';
 
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
-THREE.OBJLoader2.version = '1.1.0';
+THREE.OBJLoader2.version = '1.2.1';
 
 /**
  * OBJ data will be loaded by dynamically created web worker.
@@ -39,17 +39,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 +60,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 +80,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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 = Boolean( requestTerminate );
 	};
 
 	WWOBJLoader2.prototype._validate = function () {
 		if ( this.validated ) return;
-		if ( this.worker == null ) {
+		if ( ! Boolean( this.worker ) ) {
 
 			this._buildWebWorkerCode();
 			var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } );
@@ -173,9 +178,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 = Boolean( this.fileLoader ) ? this.fileLoader : new THREE.FileLoader( this.manager );
+		this.mtlLoader = Boolean( this.mtlLoader ) ? this.mtlLoader : new THREE.MTLLoader();
+		if ( Boolean( this.crossOrigin ) ) this.mtlLoader.setCrossOrigin( this.crossOrigin );
 
 		this.dataAvailable = false;
 		this.fileObj = null;
@@ -253,7 +258,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		var processLoadedMaterials = function ( materialCreator ) {
 			var materialCreatorMaterials = [];
 			var materialNames = [];
-			if ( materialCreator != null ) {
+			if ( Boolean( materialCreator ) ) {
 
 				materialCreator.preload();
 				materialCreatorMaterials = materialCreator.materials;
@@ -274,13 +279,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 ( Boolean( materialsFromCallback ) ) scope.materials = materialsFromCallback;
 
 			}
-
 			if ( scope.dataAvailable && scope.objAsArrayBuffer ) {
 
 				scope.worker.postMessage({
@@ -337,17 +344,24 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 		this.mtlLoader.setPath( this.pathTexture );
 		if ( this.dataAvailable ) {
 
-			processLoadedMaterials( ( this.mtlAsString != null ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
+			processLoadedMaterials( Boolean( this.mtlAsString ) ? this.mtlLoader.parse( this.mtlAsString ) : null );
 
 		} else {
 
-			if ( this.fileMtl == null ) {
+			if ( Boolean( 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,9 +374,10 @@ 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 ) {
 
@@ -386,7 +401,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 +437,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 +445,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 ( Boolean( 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 ( Boolean( payload.msg ) ) {
 
 					this._announceProgress( payload.msg );
 
@@ -488,7 +531,7 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	WWOBJLoader2.prototype._terminate = function () {
-		if ( this.worker != null ) {
+		if ( Boolean( this.worker ) ) {
 
 			if ( this.running ) throw 'Unable to gracefully terminate worker as it is currently running!';
 
@@ -504,15 +547,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 +579,23 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 	};
 
 	WWOBJLoader2.prototype._announceProgress = function ( baseText, text ) {
-		var output = "";
-		if ( baseText !== null && baseText !== undefined ) {
+		var output = ( Boolean( baseText ) ) ? baseText: "";
+		output = ( Boolean( text ) ) ? output + " " + text : output;
 
-			output = baseText;
-
-		}
-		if ( text !== null && text !== undefined ) {
-
-			output = output + " " + text;
-
-		}
-		if ( this.callbacks.progress !== null ) {
+		var callbackProgress;
+		for ( var index in this.callbacks.progress ) {
 
-			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 ( Boolean( existingWorkerCode ) ) this.workerCode = existingWorkerCode;
+		if ( ! Boolean( this.workerCode ) ) {
 
 			console.time( 'buildWebWorkerCode' );
 			var wwDef = (function () {
@@ -645,11 +690,11 @@ THREE.OBJLoader2.WWOBJLoader2 = (function () {
 				}
 
 				WWMeshCreator.prototype.setMaterials = function ( materials ) {
-					this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials;
+					this.materials = Boolean( materials ) ? materials : ( Boolean( this.materials ) ? this.materials : { materials: [] } );
 				};
 
 				WWMeshCreator.prototype.setDebug = function ( debug ) {
-					this.debug = ( debug == null ) ? this.debug : debug;
+					this.debug = Boolean( debug ) ? debug : this.debug;
 				};
 
 				WWMeshCreator.prototype.validate = function () {
@@ -700,6 +745,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 };
@@ -898,69 +944,228 @@ 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 ) {
+	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 = Boolean( sceneGraphBaseNode ) ? 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 === null || streamMeshes === undefined ) ? true : streamMeshes;
+		},
+
+		/**
+		 * 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 = Boolean( requestTerminate );
+		},
+
+		/**
+		 * 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: Boolean( modelName ) ? 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: Boolean( objAsArrayBuffer ) ? objAsArrayBuffer : null,
+		pathTexture: Boolean( pathTexture ) ? pathTexture : null,
+		mtlAsString: Boolean( mtlAsString ) ? 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 ) {
+	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 = Boolean( sceneGraphBaseNode ) ? 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 === null || streamMeshes === undefined ) ? true : streamMeshes;
+		},
+
+		/**
+		 * 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 = Boolean( requestTerminate );
+		},
+
+		/**
+		 * 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: Boolean( modelName ) ? 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: Boolean( pathObj ) ? pathObj : null,
+		fileObj: Boolean( fileObj ) ? fileObj : null,
+		pathTexture: Boolean( pathTexture ) ? pathTexture : null,
+		fileMtl: Boolean( fileMtl ) ? fileMtl : null,
+		sceneGraphBaseNode: null,
+		streamMeshes: true,
+		requestTerminate: false,
+		callbacks: new THREE.OBJLoader2.WWOBJLoader2.PrepDataCallbacks()
 	};
+};
 
-	return data;
-};
+/**
+ * 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 () {
+	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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 ( Boolean( 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 ) {
+	return {
+		disregardMesh: Boolean( disregardMesh ),
+		replaceBufferGeometry: Boolean( bufferGeometry ),
+		bufferGeometry: Boolean( bufferGeometry ) ? bufferGeometry : null,
+		replaceMaterial: Boolean( material ),
+		material: Boolean( material ) ? material : null
+	};
+};
+
 /**
  * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16).
- * Use:
+ * Workflow:
  *   prepareWorkers
  *   enqueueForRun
  *   processQueue
@@ -980,7 +1185,7 @@ THREE.OBJLoader2.WWOBJLoader2Director = (function () {
 
 		this.workerDescription = {
 			prototypeDef: THREE.OBJLoader2.WWOBJLoader2.prototype,
-			callbacks: {},
+			globalCallbacks: {},
 			webWorkers: [],
 			codeBuffer: null
 		};
@@ -1022,22 +1227,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 ( Boolean( 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 +1263,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 +1275,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 ( Boolean( 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 ( Boolean( runCallbacks ) ) {
 
-			if ( webWorker.callbacks.hasOwnProperty( key ) && this.workerDescription.callbacks.hasOwnProperty( key ) ) {
+			for ( key in runCallbacks ) {
 
-				webWorker.callbacks[ key ] = this.workerDescription.callbacks[ key ];
+				if ( workerCallbacks.hasOwnProperty( key ) && runCallbacks.hasOwnProperty( key ) && Boolean( runCallbacks[ 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 ( Boolean( 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 ( Boolean( this.crossOrigin ) ) webWorker.setCrossOrigin( this.crossOrigin );
+
+		// Ensure code string is built once and then it is just passed on to every new instance
+		if ( Boolean( 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 +1378,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 ] );
 
 						}
 

+ 19 - 13
examples/webgl_loader_obj2_ww.html

@@ -181,26 +181,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 = Boolean( 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 +222,7 @@
 
 					}
 
-					if ( fileObj == null ) {
+					if ( ! Boolean( fileObj ) ) {
 						alert( 'Unable to load OBJ file from given files.' );
 					}
 
@@ -261,8 +261,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 +342,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 +375,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();
+
 								}
 							}
 						}

+ 45 - 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>
@@ -195,8 +195,12 @@
 					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\>' ) );
 
@@ -206,27 +210,30 @@
 						scope.feedbackArray[ instanceNo ] = msg;
 						scope.reportProgress( scope.feedbackArray.join( '\<br\>' ) );
 					};
-					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 ( Boolean( 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 +269,7 @@
 						scale: 50.0
 					} );
 					models.push( {
-						modelName:'WaltHead',
+						modelName: 'WaltHead',
 						dataAvailable: false,
 						pathObj: 'obj/walt/',
 						fileObj: 'WaltHead.obj',
@@ -276,9 +283,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 +294,21 @@
 							distributionBase + distributionMax * Math.random(),
 							distributionBase + distributionMax * Math.random()
 						);
-						if ( model.scale != null ) pivot.scale.set( model.scale, model.scale, model.scale );
+						if ( Boolean( 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 +321,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 );