diff --git a/examples/js/loaders/webworker/WWCommons.js b/examples/js/loaders/webworker/WWCommons.js new file mode 100644 index 00000000000000..f5917067d0e816 --- /dev/null +++ b/examples/js/loaders/webworker/WWCommons.js @@ -0,0 +1,23 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +'use strict'; + +if ( THREE === undefined ) { + var THREE = {} +} + +if ( THREE.WebWorker === undefined ) { + + THREE.WebWorker = { + Commons: { + paths: { + threejsPath: '../../../../build/three.min.js', + objLoaderPath: '../OBJLoader.js', + mtlLoaderPath: '../MTLLoader.js' + } + } + } + +} diff --git a/examples/js/loaders/webworker/WWOBJLoader.js b/examples/js/loaders/webworker/WWOBJLoader.js new file mode 100644 index 00000000000000..1ee73d02a74289 --- /dev/null +++ b/examples/js/loaders/webworker/WWOBJLoader.js @@ -0,0 +1,220 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +'use strict'; + +importScripts( './WWCommons.js' ); +importScripts( THREE.WebWorker.Commons.paths.threejsPath ); +importScripts( THREE.WebWorker.Commons.paths.objLoaderPath ); +importScripts( THREE.WebWorker.Commons.paths.mtlLoaderPath ); + +THREE.WebWorker.WWOBJLoader = (function () { + + WWOBJLoader.prototype = Object.create( THREE.OBJLoader.prototype, { + constructor: { + configurable: true, + enumerable: true, + value: WWOBJLoader, + writable: true + } + } ); + + function WWOBJLoader() { + THREE.OBJLoader.call( this ); + this.cmdState = 'created'; + this.debug = false; + + this.basePath = ''; + this.objFile = ''; + this.dataAvailable = false; + this.objAsArrayBuffer = null; + + this.setLoadAsArrayBuffer( true ); + this.setWorkInline( true ); + + this.counter = 0; + } + + WWOBJLoader.prototype._buildSingleMesh = function ( object, material ) { + // Fast-Fail: Skip o/g line declarations that did not follow with any faces + if ( object.geometry.vertices.length === 0 ) return null; + + this.counter ++; + + var geometry = object.geometry; + var objectMaterials = object.materials; + + var verticesOut = new Float32Array( geometry.vertices ); + var normalsOut = null; + if ( geometry.normals.length > 0 ) { + + normalsOut = new Float32Array( geometry.normals ); + + } + var uvsOut = new Float32Array( geometry.uvs ); + + + var materialGroups = []; + var materialNames = []; + var multiMaterial = false; + if ( material instanceof THREE.MultiMaterial ) { + + for ( var objectMaterial, group, i = 0, length = objectMaterials.length; i < length; i ++ ) { + + objectMaterial = objectMaterials[ i ]; + group = { + start: objectMaterial.groupStart, + count: objectMaterial.groupCount, + index: i + }; + materialGroups.push( group ); + + } + + var mMaterial; + for ( var key in material.materials ) { + + mMaterial = material.materials[ key ]; + materialNames.push( mMaterial.name ); + + } + multiMaterial = true; + } + + + self.postMessage( { + cmd: 'objData', + meshName: object.name, + multiMaterial: multiMaterial, + materialName: multiMaterial ? JSON.stringify( materialNames ) : material.name, + materialGroups: multiMaterial ? JSON.stringify( materialGroups ) : null, + vertices: verticesOut, + normals: normalsOut, + uvs: uvsOut, + }, [ verticesOut.buffer ], [ normalsOut.buffer ], [ uvsOut.buffer ] ); + + return null; + }; + + + WWOBJLoader.prototype.init = function ( payload ) { + this.cmdState = 'init'; + + this.debug = payload.debug; + this.dataAvailable = payload.dataAvailable; + this.basePath = payload.basePath === null ? '' : payload.basePath; + this.objFile = payload.objFile === null ? '' : payload.objFile; + + // configure OBJLoader + if ( payload.loadAsArrayBuffer !== undefined ) { + + this.setLoadAsArrayBuffer( payload.loadAsArrayBuffer ); + + } + if ( payload.workInline !== undefined ) { + + this.setWorkInline( payload.workInline ); + + } + this.setPath( this.basePath ); + + if ( this.dataAvailable ) { + + // this must be the case, otherwise loading will fail + this.setLoadAsArrayBuffer( true ); + this.objAsArrayBuffer = payload.objAsArrayBuffer; + + } + }; + + WWOBJLoader.prototype.initMaterials = function ( payload ) { + this.cmdState = 'initMaterials'; + + var materialsJSON = JSON.parse( payload.materials ); + var materialCreator = new THREE.MTLLoader.MaterialCreator( payload.baseUrl, payload.options ); + materialCreator.setMaterials( materialsJSON ); + materialCreator.preload(); + + this.setMaterials( materialCreator ); + }; + + WWOBJLoader.prototype.run = function () { + this.cmdState = 'run'; + var scope = this; + + var complete = function () { + console.log( 'OBJ loading complete!' ); + + scope.cmdState = 'complete'; + self.postMessage( { + cmd: scope.cmdState + } ); + + scope.dispose(); + }; + + if ( scope.dataAvailable ) { + + scope.parseArrayBuffer( scope.objAsArrayBuffer ); + complete(); + + } else { + + var onLoad = function () { + complete(); + }; + + var onProgress = function ( xhr ) { + if ( xhr.lengthComputable ) { + var percentComplete = xhr.loaded / xhr.total * 100; + console.log( Math.round( percentComplete, 2 ) + '% downloaded' ); + } + }; + + var onError = function ( xhr ) { + console.error( xhr ); + }; + + scope.load( scope.objFile, onLoad, onProgress, onError ); + + } + }; + + return WWOBJLoader; +})(); + +var implRef = new THREE.WebWorker.WWOBJLoader( this ); + +var runner = function ( event ) { + var payload = event.data; + + console.log( 'Command state before: ' + implRef.cmdState ); + + switch ( payload.cmd ) { + case 'init': + + implRef.init( payload ); + break; + + case 'initMaterials': + + implRef.initMaterials( payload ); + break; + + case 'run': + + implRef.run(); + break; + + default: + + console.error( 'WWOBJLoader: Received unknown command: ' + payload.cmd ); + break; + + } + + console.log( 'Command state after: ' + implRef.cmdState ); +}; + +self.addEventListener( 'message', runner, false ); diff --git a/examples/js/loaders/webworker/WWOBJLoaderFrontEnd.js b/examples/js/loaders/webworker/WWOBJLoaderFrontEnd.js new file mode 100644 index 00000000000000..3a7ccb98531575 --- /dev/null +++ b/examples/js/loaders/webworker/WWOBJLoaderFrontEnd.js @@ -0,0 +1,383 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +'use strict'; + +THREE.WebWorker.WWOBJLoaderFrontEnd = (function () { + + function WWOBJLoaderFrontEnd( basedir ) { + // check worker support first + if ( window.Worker === undefined ) { + throw "This browser does not support web workers!" + } + + this.worker = new Worker( basedir + "/js/apps/tools/loaders/WWOBJLoader.js" ); + + var scope = this; + var scopeFunction = function ( e ) { + scope.processData( e ); + }; + this.worker.addEventListener( 'message', scopeFunction, false ); + + this.mtlLoader = new THREE.MTLLoader(); + this.mtlFile = null; + this.texturePath = null; + this.dataAvailable = false; + this.mtlAsString = null; + + this.materials = []; + this.defaultMaterial = new THREE.MeshPhongMaterial(); + this.defaultMaterial.name = "defaultMaterial"; + + this.counter = 0; + this.objGroup = null; + + this.debug = false; + + // callbacks + this.callbackMaterialsLoaded = null; + this.callbackProgress = null; + this.callbackMeshLoaded = null; + this.callbackCompletedLoading = null; + } + + WWOBJLoaderFrontEnd.prototype.setObjGroup = function ( objGroup ) { + this.objGroup = objGroup; + }; + + WWOBJLoaderFrontEnd.prototype.setDebug = function ( enabled ) { + this.debug = enabled; + }; + + WWOBJLoaderFrontEnd.prototype.registerHookMaterialsLoaded = function ( callback ) { + this.callbackMaterialsLoaded = callback; + }; + + WWOBJLoaderFrontEnd.prototype.registerProgressCallback = function ( callbackProgress ) { + this.callbackProgress = callbackProgress; + }; + + WWOBJLoaderFrontEnd.prototype.registerHookMeshLoaded = function ( callback ) { + this.callbackMeshLoaded = callback; + }; + + WWOBJLoaderFrontEnd.prototype.registerHookCompletedLoading = function ( callback ) { + this.callbackCompletedLoading = callback; + }; + + WWOBJLoaderFrontEnd.prototype.addMaterial = function ( name, material ) { + if ( material.name !== name ) { + + material.name = name; + + } + this.materials[ name ] = material; + }; + + WWOBJLoaderFrontEnd.prototype.getMaterial = function ( name ) { + var material = this.materials[ name ]; + if ( material === undefined ) { + + material = null; + + } + return material; + }; + + WWOBJLoaderFrontEnd.prototype.announceProgress = function ( baseText, text ) { + var output = ""; + if ( baseText !== null && baseText !== undefined ) { + + output = baseText; + + } + if ( text !== null && text !== undefined ) { + + output = output + " " + text; + + } + if ( this.callbackProgress !== null ) { + + this.callbackProgress( output ); + + } + if ( this.debug ) { + + console.log( output ); + + } + }; + + WWOBJLoaderFrontEnd.prototype.initWithFiles = function ( basePath, objFile, mtlFile, texturePath ) { + + console.time( 'WWOBJLoaderFrontEnd' ); + this.dataAvailable = false; + + this.worker.postMessage( { + cmd: 'init', + debug: this.debug, + dataAvailable: this.dataAvailable, + basePath: basePath, + objFile: objFile, + objAsArrayBuffer: null + } ); + + + // configure MTLLoader + this.mtlFile = mtlFile; + this.texturePath = texturePath; + this.mtlLoader.setPath( this.texturePath ); + }; + + WWOBJLoaderFrontEnd.prototype.initWithData = function ( objAsArrayBuffer, mtlAsString, texturePath ) { + + console.time( 'WWOBJLoaderFrontEnd' ); + this.dataAvailable = true; + + this.worker.postMessage( { + cmd: 'init', + debug: this.debug, + dataAvailable: this.dataAvailable, + basePath: null, + objFile: null, + objAsArrayBuffer: objAsArrayBuffer === undefined ? null : objAsArrayBuffer + }, [ objAsArrayBuffer.buffer ] ); + + this.mtlAsString = mtlAsString; + this.texturePath = texturePath; + this.mtlLoader.setPath( this.texturePath ); + }; + + WWOBJLoaderFrontEnd.prototype.run = function () { + var scope = this; + + var kickRun = function () { + scope.worker.postMessage({ + cmd: 'run', + }); + }; + + // fast-exec in case of no mtl file or data + if ( this.dataAvailable && ( scope.mtlAsString === undefined || scope.mtlAsString === null ) ) { + + kickRun(); + return; + + } + else if ( ! this.dataAvailable && ( scope.mtlFile === undefined || scope.mtlFile === null ) ) { + + kickRun(); + return; + + } + + var processMaterials = function ( materialsOrg ) { + + var matInfoOrg = materialsOrg.materialsInfo; + + // Clone mtl input material objects + var matInfoMod = {}; + var name, matOrg, matMod; + for ( name in matInfoOrg ) { + + if ( matInfoOrg.hasOwnProperty( name ) ) { + + matOrg = matInfoOrg[ name ]; + matMod = Object.assign( {}, matOrg ); + + if ( matMod.hasOwnProperty( 'map_kd' ) ) { + + delete matMod[ 'map_kd' ]; + + } + if ( matMod.hasOwnProperty( 'map_ks' ) ) { + + delete matMod[ 'map_ks' ]; + + } + if ( matMod.hasOwnProperty( 'map_bump' ) ) { + + delete matMod[ 'map_bump' ]; + + } + if ( matMod.hasOwnProperty( 'bump' ) ) { + + delete matMod[ 'bump' ]; + + } + matInfoMod[ name ] = matMod; + + } + } + var materialsMod = new THREE.MTLLoader.MaterialCreator( materialsOrg.baseUrl, materialsOrg.options ); + materialsMod.setMaterials( matInfoMod ); + materialsMod.preload(); + + // set 'castrated' materials in associated materials array + for ( name in materialsMod.materials ) { + + if ( materialsMod.materials.hasOwnProperty( name ) ) { + + scope.materials[ name ] = materialsMod.materials[ name ]; + + } + } + + // pass 'castrated' materials to web worker + scope.worker.postMessage( { + cmd: 'initMaterials', + materials: JSON.stringify( matInfoMod ), + baseUrl: materialsMod.baseUrl, + options: materialsMod.options + } ); + + // process obj immediately + kickRun(); + + + console.time( 'Loading MTL textures' ); + materialsOrg.preload(); + + // update stored materials with texture mapping information (= fully restoration) + var matWithTextures = materialsOrg.materials; + var intermediate; + var updated; + for ( name in scope.materials ) { + + if ( scope.materials.hasOwnProperty( name ) && matWithTextures.hasOwnProperty( name ) ) { + + intermediate = scope.materials[ name ]; + updated = matWithTextures[ name ]; + intermediate.setValues( updated ); + + } + } + + if ( scope.callbackMaterialsLoaded !== null ) { + + scope.materials = scope.callbackMaterialsLoaded( scope.materials ); + + } + console.timeEnd( 'Loading MTL textures' ); + }; + + if ( this.dataAvailable ) { + + processMaterials( scope.mtlLoader.parse( scope.mtlAsString ) ); + + } else { + + scope.mtlLoader.load( scope.mtlFile, processMaterials ); + + } + }; + + WWOBJLoaderFrontEnd.prototype.processData = function ( event ) { + var payload = event.data; + var material; + + switch ( payload.cmd ) { + case 'objData': + + this.counter ++; + var bufferGeometry = new THREE.BufferGeometry(); + + bufferGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( payload.vertices ), 3 ) ); + if ( payload.normals !== undefined ) { + + bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.normals ), 3 ) ); + + } else { + + bufferGeometry.computeVertexNormals(); + + } + if ( payload.uvs !== undefined ) { + + bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.uvs ), 2 ) ); + + } + if ( payload.multiMaterial ) { + + var materialNames = JSON.parse( payload.materialName ); + var multiMaterials = []; + var name; + for ( var key in materialNames ) { + + name = materialNames[ key ]; + multiMaterials.push( this.materials[ name ] ); + + } + + material = new THREE.MultiMaterial( multiMaterials ); + + } else { + + material = this.materials[ payload.materialName ]; + + } + + if ( material === null || material === undefined ) { + + material = this.defaultMaterial; + + } + + if ( payload.materialGroups !== null ) { + + var materialGroups = JSON.parse( payload.materialGroups ); + for ( var group, i = 0, length = materialGroups.length; i < length; i ++ ) { + + group = materialGroups[ i ]; + bufferGeometry.addGroup( group.start, group.count, group.index ); + + } + } + + if ( this.callbackMeshLoaded !== null ) { + + var materialOverride = this.callbackMeshLoaded( payload.meshName, material ); + if ( materialOverride !== null && materialOverride !== undefined ) { + + material = materialOverride; + + } + } + + var mesh = new THREE.Mesh( bufferGeometry, material ); + mesh.name = payload.meshName; + + this.objGroup.add( mesh ); + + var output = '(' + this.counter + '): ' + payload.meshName; + this.announceProgress( 'Adding mesh', output ); + + break; + + case 'complete': + + console.timeEnd( 'WWOBJLoaderFrontEnd' ); + this.announceProgress(); + + if ( this.callbackCompletedLoading !== null ) { + + this.callbackCompletedLoading(); + + } + + this.worker.terminate(); + + break; + + default: + + console.error( 'Received unknown command: ' + payload.cmd ); + break; + + } + }; + + return WWOBJLoaderFrontEnd; + +})();