diff --git a/examples/files.js b/examples/files.js index 5749e7fc84d0d6..04df412e842c76 100644 --- a/examples/files.js +++ b/examples/files.js @@ -100,6 +100,7 @@ var files = { "webgl_loader_msgpack", "webgl_loader_obj", "webgl_loader_obj_mtl", + "webgl_loader_obj2", "webgl_loader_nrrd", "webgl_loader_pcd", "webgl_loader_pdb", @@ -117,6 +118,8 @@ var files = { "webgl_loader_utf8", "webgl_loader_vrml", "webgl_loader_vtk", + "webgl_loader_wwobj2", + "webgl_loader_wwobj2_parallels", "webgl_lod", "webgl_marchingcubes", "webgl_materials", diff --git a/examples/js/loaders/wwobjloader2/OBJLoader2.js b/examples/js/loaders/wwobjloader2/OBJLoader2.js new file mode 100644 index 00000000000000..c419499c47620a --- /dev/null +++ b/examples/js/loaders/wwobjloader2/OBJLoader2.js @@ -0,0 +1,994 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +'use strict'; + +if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} } + +/** + * 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} + */ +THREE.OBJLoader2 = (function () { + + function OBJLoader2( manager ) { + this.manager = ( manager == null ) ? THREE.DefaultLoadingManager : manager; + + this.path = ''; + this.fileLoader = new THREE.FileLoader( this.manager ); + + this.meshCreator = new THREE.OBJLoader2.MeshCreator(); + this.parser = new THREE.OBJLoader2.Parser( this.meshCreator ); + + this.validated = false; + } + + /** + * Base path to use + * @memberOf THREE.OBJLoader2 + * + * @param {string} path The basepath + */ + OBJLoader2.prototype.setPath = function ( path ) { + this.path = ( path == null ) ? this.path : path; + }; + + /** + * Set the node where the loaded objects will be attached + * @memberOf THREE.OBJLoader2 + * + * @param {THREE.Object3D} sceneGraphBaseNode Scenegraph object where meshes will be attached + */ + OBJLoader2.prototype.setSceneGraphBaseNode = function ( sceneGraphBaseNode ) { + this.meshCreator._setSceneGraphBaseNode( sceneGraphBaseNode ); + }; + + /** + * Set materials loaded by MTLLoader + * @memberOf THREE.OBJLoader2 + * + * @param {THREE.MTLLoader.MaterialCreator.materials[]} materials {@link THREE.MTLLoader.MaterialCreator.materials} + */ + OBJLoader2.prototype.setMaterials = function ( materials ) { + this.meshCreator._setMaterials( materials ); + }; + + /** + * Allows to set debug mode for the parser and the meshCreator + * @memberOf THREE.OBJLoader2 + * + * @param {boolean} parserDebug {@link THREE.OBJLoader2.Parser} will produce debug output + * @param {boolean} meshCreatorDebug {@link THREE.OBJLoader2.MeshCreator} will produce debug output + */ + OBJLoader2.prototype.setDebug = function ( parserDebug, meshCreatorDebug ) { + this.parser._setDebug( parserDebug ); + this.meshCreator._setDebug( meshCreatorDebug ); + }; + + /** + * Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer + * @memberOf THREE.OBJLoader2 + * + * @param {string} 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} 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' ); + + 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 ) ); + + }, onProgress, onError ); + }; + + /** + * Default parse function: Parses OBJ file content stored in arrayBuffer and returns the sceneGraphBaseNode + * @memberOf THREE.OBJLoader2 + * + * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array + */ + OBJLoader2.prototype.parse = function ( arrayBuffer ) { + // fast-fail on bad type + if ( ! ( arrayBuffer instanceof ArrayBuffer || arrayBuffer instanceof Uint8Array ) ) { + + throw 'Provided input is not of type arraybuffer! Aborting...'; + + } + console.log( 'Parsing arrayBuffer...' ); + console.time( 'parseArrayBuffer' ); + + this._validate(); + this.parser.parseArrayBuffer( arrayBuffer ); + var sceneGraphAttach = this._finalize(); + + console.timeEnd( 'parseArrayBuffer' ); + + return sceneGraphAttach; + }; + + /** + * Legacy parse function: Parses OBJ file content stored in string and returns the sceneGraphBaseNode + * @memberOf THREE.OBJLoader2 + * + * @param {string} text OBJ data as string + */ + OBJLoader2.prototype.parseText = function ( text ) { + // fast-fail on bad type + if ( ! ( typeof( text ) === 'string' || text instanceof String ) ) { + + throw 'Provided input is not of type String! Aborting...'; + + } + console.log( 'Parsing text...' ); + console.time( 'parseText' ); + + this._validate(); + this.parser.parseText( text ); + var sceneGraphBaseNode = this._finalize(); + + console.timeEnd( 'parseText' ); + + return sceneGraphBaseNode; + }; + + OBJLoader2.prototype._validate = function () { + if ( this.validated ) return; + + this.fileLoader = ( this.fileLoader == null ) ? new THREE.FileLoader( this.manager ) : this.fileLoader; + this.setPath( null ); + this.parser._validate(); + this.meshCreator._validate(); + + this.validated = true; + }; + + OBJLoader2.prototype._finalize = function () { + console.log( 'Global output object count: ' + this.meshCreator.globalObjectCount ); + + this.parser._finalize(); + this.fileLoader = null; + var sceneGraphBaseNode = this.meshCreator.sceneGraphBaseNode; + this.meshCreator._finalize(); + this.validated = false; + + return sceneGraphBaseNode; + }; + + return OBJLoader2; +})(); + + + +if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} } + +/** + * Constants used by THREE.OBJLoader2.Parser + */ +THREE.OBJLoader2.consts = { + CODE_LF: 10, + CODE_CR: 13, + CODE_SPACE: 32, + CODE_SLASH: 47, + STRING_LF: '\n', + STRING_CR: '\r', + STRING_SPACE: ' ', + STRING_SLASH: '/', + LINE_F: 'f', + LINE_G: 'g', + LINE_L: 'l', + LINE_O: 'o', + LINE_S: 's', + LINE_V: 'v', + LINE_VT: 'vt', + LINE_VN: 'vn', + LINE_MTLLIB: 'mtllib', + LINE_USEMTL: 'usemtl', + /* + * Build Face/Quad: first element in indexArray is the line identification, therefore offset of one needs to be taken into account + * N-Gons are not supported + * Quad Faces: FaceA: 0, 1, 2 FaceB: 2, 3, 0 + * + * 0: "f vertex/uv/normal vertex/uv/normal vertex/uv/normal (vertex/uv/normal)" + * 1: "f vertex/uv vertex/uv vertex/uv (vertex/uv )" + * 2: "f vertex//normal vertex//normal vertex//normal (vertex//normal )" + * 3: "f vertex vertex vertex (vertex )" + * + * @param indexArray + * @param faceType + */ + QUAD_INDICES_1: [ 1, 2, 3, 3, 4, 1 ], + QUAD_INDICES_2: [ 1, 3, 5, 5, 7, 1 ], + QUAD_INDICES_3: [ 1, 4, 7, 7, 10, 1 ], + _buildIndex: function ( materialName, smoothingGroup) { + return materialName + '|' + smoothingGroup; + } +}; + +/** + * Parse OBJ data either from ArrayBuffer or string + * @class + */ +THREE.OBJLoader2.Parser = (function () { + + function Parser( meshCreator ) { + this.meshCreator = meshCreator; + this.rawObject = null; + this.inputObjectCount = 1; + this.debug = false; + } + + Parser.prototype._setDebug = function ( debug ) { + this.debug = ( debug == null ) ? this.debug : debug; + }; + + Parser.prototype._validate = function () { + this.rawObject = new THREE.OBJLoader2._RawObject(); + this.inputObjectCount = 1; + }; + + /** + * Parse the provided arraybuffer + * @memberOf THREE.OBJLoader2.Parser + * + * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array + */ + Parser.prototype.parseArrayBuffer = function ( arrayBuffer ) { + var arrayBufferView = new Uint8Array( arrayBuffer ); + var length = arrayBufferView.byteLength; + var buffer = new Array( 32 ); + var bufferPointer = 0; + var slashes = new Array( 32 ); + var slashesPointer = 0; + var reachedFaces = false; + var code; + var word = ''; + for ( var i = 0; i < length; i++ ) { + + code = arrayBufferView[ i ]; + switch ( code ) { + case THREE.OBJLoader2.consts.CODE_SPACE: + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + break; + + case THREE.OBJLoader2.consts.CODE_SLASH: + slashes[ slashesPointer++ ] = i; + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + break; + + case THREE.OBJLoader2.consts.CODE_LF: + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + reachedFaces = this._processLine( buffer, bufferPointer, slashes, slashesPointer, reachedFaces ); + slashesPointer = 0; + bufferPointer = 0; + break; + + case THREE.OBJLoader2.consts.CODE_CR: + break; + + default: + word += String.fromCharCode( code ); + break; + } + } + }; + + /** + * Parse the provided text + * @memberOf THREE.OBJLoader2.Parser + * + * @param {string} text OBJ data as string + */ + Parser.prototype.parseText = function ( text ) { + var length = text.length; + var buffer = new Array( 32 ); + var bufferPointer = 0; + var slashes = new Array( 32 ); + var slashesPointer = 0; + var reachedFaces = false; + var char; + var word = ''; + for ( var i = 0; i < length; i++ ) { + + char = text[ i ]; + switch ( char ) { + case THREE.OBJLoader2.consts.STRING_SPACE: + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + break; + + case THREE.OBJLoader2.consts.STRING_SLASH: + slashes[ slashesPointer++ ] = i; + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + break; + + case THREE.OBJLoader2.consts.STRING_LF: + if ( word.length > 0 ) buffer[ bufferPointer++ ] = word; + word = ''; + reachedFaces = this._processLine( buffer, bufferPointer, slashes, slashesPointer, reachedFaces ); + slashesPointer = 0; + bufferPointer = 0; + break; + + case THREE.OBJLoader2.consts.STRING_CR: + break; + + default: + word += char; + } + } + }; + + Parser.prototype._processLine = function ( buffer, bufferPointer, slashes, slashesPointer, reachedFaces ) { + if ( bufferPointer < 1 ) return reachedFaces; + + var bufferLength = bufferPointer - 1; + switch ( buffer[ 0 ] ) { + case THREE.OBJLoader2.consts.LINE_V: + + // object complete instance required if reached faces already (= reached next block of v) + if ( reachedFaces ) { + + this._processCompletedObject( null, this.rawObject.groupName ); + reachedFaces = false; + + } + this.rawObject._pushVertex( buffer ); + break; + + case THREE.OBJLoader2.consts.LINE_VT: + this.rawObject._pushUv( buffer ); + break; + + case THREE.OBJLoader2.consts.LINE_VN: + this.rawObject._pushNormal( buffer ); + break; + + case THREE.OBJLoader2.consts.LINE_F: + reachedFaces = true; + /* + * 0: "f vertex/uv/normal ..." + * 1: "f vertex/uv ..." + * 2: "f vertex//normal ..." + * 3: "f vertex ..." + */ + var haveQuad = bufferLength % 4 === 0; + if ( slashesPointer > 1 && ( slashes[ 1 ] - slashes[ 0 ] ) === 1 ) { + + if ( haveQuad ) { + this.rawObject._buildQuadVVn( buffer ); + } else { + this.rawObject._buildFaceVVn( buffer ); + } + + } else if ( bufferLength === slashesPointer * 2 ) { + + if ( haveQuad ) { + this.rawObject._buildQuadVVt( buffer ); + } else { + this.rawObject._buildFaceVVt( buffer ); + } + + } else if ( bufferLength * 2 === slashesPointer * 3 ) { + + if ( haveQuad ) { + this.rawObject._buildQuadVVtVn( buffer ); + } else { + this.rawObject._buildFaceVVtVn( buffer ); + } + + } else { + + if ( haveQuad ) { + this.rawObject._buildQuadV( buffer ); + } else { + this.rawObject._buildFaceV( buffer ); + } + + } + break; + + case THREE.OBJLoader2.consts.LINE_L: + if ( bufferLength === slashesPointer * 2 ) { + + this.rawObject._buildLineVvt( buffer ); + + } else { + + this.rawObject._buildLineV( buffer ); + + } + break; + + case THREE.OBJLoader2.consts.LINE_S: + this.rawObject._pushSmoothingGroup( buffer[ 1 ] ); + break; + + case THREE.OBJLoader2.consts.LINE_G: + this._processCompletedGroup( buffer[ 1 ] ); + break; + + case THREE.OBJLoader2.consts.LINE_O: + if ( this.rawObject.vertices.length > 0 ) { + + this._processCompletedObject( buffer[ 1 ], null ); + reachedFaces = false; + + } else { + + this.rawObject._pushObject( buffer[ 1 ] ); + + } + break; + + case THREE.OBJLoader2.consts.LINE_MTLLIB: + this.rawObject._pushMtllib( buffer[ 1 ] ); + break; + + case THREE.OBJLoader2.consts.LINE_USEMTL: + this.rawObject._pushUsemtl( buffer[ 1 ] ); + break; + + default: + break; + } + return reachedFaces; + }; + + Parser.prototype._processCompletedObject = function ( objectName, groupName ) { + this.rawObject._finalize( this.meshCreator, this.inputObjectCount, this.debug ); + this.inputObjectCount++; + this.rawObject = this.rawObject._newInstanceFromObject( objectName, groupName ); + }; + + Parser.prototype._processCompletedGroup = function ( groupName ) { + var notEmpty = this.rawObject._finalize( this.meshCreator, this.inputObjectCount, this.debug ); + if ( notEmpty ) { + + this.inputObjectCount ++; + this.rawObject = this.rawObject._newInstanceFromGroup( groupName ); + + } else { + + // if a group was set that did not lead to object creation in finalize, then the group name has to be updated + this.rawObject._pushGroup( groupName ); + + } + }; + + Parser.prototype._finalize = function () { + this.rawObject._finalize( this.meshCreator, this.inputObjectCount, this.debug ); + this.inputObjectCount++; + }; + + return Parser; +})(); + +/** + * {@link THREE.OBJLoader2._RawObject} is only used by {@link THREE.OBJLoader2.Parser}. + * The user of OBJLoader2 does not need to care about this class. + * It is defined publicly for inclusion in web worker based OBJ loader ({@link THREE.OBJLoader2.WWOBJLoader2}) + */ +THREE.OBJLoader2._RawObject = (function () { + + function _RawObject( objectName, groupName, mtllibName ) { + this.globalVertexOffset = 1; + this.globalUvOffset = 1; + this.globalNormalOffset = 1; + + this.vertices = []; + this.normals = []; + 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.activeMtlName = 'none'; + this.activeSmoothingGroup = 1; + + this.mtlCount = 0; + this.smoothingGroupCount = 0; + + this.rawObjectDescriptions = []; + // this default index is required as it is possible to define faces without 'g' or 'usemtl' + var index = THREE.OBJLoader2.consts._buildIndex( this.activeMtlName, this.activeSmoothingGroup ); + this.rawObjectDescriptionInUse = new THREE.OBJLoader2.RawObjectDescription( this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup ); + this.rawObjectDescriptions[ index ] = this.rawObjectDescriptionInUse; + } + + _RawObject.prototype._newInstanceFromObject = function ( objectName, groupName ) { + var newRawObject = new _RawObject( objectName, groupName, this.mtllibName ); + + // move indices forward + newRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3; + newRawObject.globalUvOffset = this.globalUvOffset + this.uvs.length / 2; + newRawObject.globalNormalOffset = this.globalNormalOffset + this.normals.length / 3; + + return newRawObject; + }; + + _RawObject.prototype._newInstanceFromGroup = function ( groupName ) { + var newRawObject = new _RawObject( this.objectName, groupName, this.mtllibName ); + + // keep current buffers and indices forward + newRawObject.vertices = this.vertices; + newRawObject.uvs = this.uvs; + newRawObject.normals = this.normals; + newRawObject.globalVertexOffset = this.globalVertexOffset; + newRawObject.globalUvOffset = this.globalUvOffset; + newRawObject.globalNormalOffset = this.globalNormalOffset; + + return newRawObject; + }; + + _RawObject.prototype._pushVertex = function ( buffer ) { + this.vertices.push( parseFloat( buffer[ 1 ] ) ); + this.vertices.push( parseFloat( buffer[ 2 ] ) ); + this.vertices.push( parseFloat( buffer[ 3 ] ) ); + }; + + _RawObject.prototype._pushUv = function ( buffer ) { + this.uvs.push( parseFloat( buffer[ 1 ] ) ); + this.uvs.push( parseFloat( buffer[ 2 ] ) ); + }; + + _RawObject.prototype._pushNormal = function ( buffer ) { + this.normals.push( parseFloat( buffer[ 1 ] ) ); + this.normals.push( parseFloat( buffer[ 2 ] ) ); + this.normals.push( parseFloat( buffer[ 3 ] ) ); + }; + + _RawObject.prototype._pushObject = function ( objectName ) { + this.objectName = objectName; + }; + + _RawObject.prototype._pushMtllib = function ( mtllibName ) { + this.mtllibName = mtllibName; + }; + + _RawObject.prototype._pushGroup = function ( groupName ) { + this.groupName = groupName; + this._verifyIndex(); + }; + + _RawObject.prototype._pushUsemtl = function ( mtlName ) { + if ( this.activeMtlName === mtlName || mtlName == null ) return; + this.activeMtlName = mtlName; + this.mtlCount++; + + this._verifyIndex(); + }; + + _RawObject.prototype._pushSmoothingGroup = function ( activeSmoothingGroup ) { + var normalized = activeSmoothingGroup === 'off' ? 0 : activeSmoothingGroup; + if ( this.activeSmoothingGroup === normalized ) return; + this.activeSmoothingGroup = normalized; + this.smoothingGroupCount++; + + this._verifyIndex(); + }; + + _RawObject.prototype._verifyIndex = function () { + var index = THREE.OBJLoader2.consts._buildIndex( this.activeMtlName, ( this.activeSmoothingGroup === 0 ) ? 0 : 1 ); + if ( this.rawObjectDescriptions[ index ] == null ) { + + this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ] = + new THREE.OBJLoader2.RawObjectDescription( + this.objectName, this.groupName, this.activeMtlName, this.activeSmoothingGroup + ); + + } else { + + this.rawObjectDescriptionInUse = this.rawObjectDescriptions[ index ]; + + } + }; + + _RawObject.prototype._buildQuadVVtVn = function ( indexArray ) { + for ( var i = 0; i < 6; i ++ ) { + this._attachFaceV_( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_3[ i ] ] ); + this._attachFaceVt( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_3[ i ] + 1 ] ); + this._attachFaceVn( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_3[ i ] + 2 ] ); + } + }; + + _RawObject.prototype._buildQuadVVt = function ( indexArray ) { + for ( var i = 0; i < 6; i ++ ) { + this._attachFaceV_( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_2[ i ] ] ); + this._attachFaceVt( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_2[ i ] + 1 ] ); + } + }; + + _RawObject.prototype._buildQuadVVn = function ( indexArray ) { + for ( var i = 0; i < 6; i ++ ) { + this._attachFaceV_( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_2[ i ] ] ); + this._attachFaceVn( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_2[ i ] + 1 ] ); + } + }; + + _RawObject.prototype._buildQuadV = function ( indexArray ) { + for ( var i = 0; i < 6; i ++ ) { + this._attachFaceV_( indexArray[ THREE.OBJLoader2.consts.QUAD_INDICES_1[ i ] ] ); + } + }; + + _RawObject.prototype._buildFaceVVtVn = function ( indexArray ) { + for ( var i = 1; i < 10; i += 3 ) { + this._attachFaceV_( indexArray[ i ] ); + this._attachFaceVt( indexArray[ i + 1 ] ); + this._attachFaceVn( indexArray[ i + 2 ] ); + } + }; + + _RawObject.prototype._buildFaceVVt = function ( indexArray ) { + for ( var i = 1; i < 7; i += 2 ) { + this._attachFaceV_( indexArray[ i ] ); + this._attachFaceVt( indexArray[ i + 1 ] ); + } + }; + + _RawObject.prototype._buildFaceVVn = function ( indexArray ) { + for ( var i = 1; i < 7; i += 2 ) { + this._attachFaceV_( indexArray[ i ] ); + this._attachFaceVn( indexArray[ i + 1 ] ); + } + }; + + _RawObject.prototype._buildFaceV = function ( indexArray ) { + for ( var i = 1; i < 4; i ++ ) { + this._attachFaceV_( indexArray[ i ] ); + } + }; + + _RawObject.prototype._attachFaceV_ = function ( faceIndex ) { + var faceIndexInt = parseInt( faceIndex ); + var index = ( faceIndexInt - this.globalVertexOffset ) * 3; + + var rodiu = this.rawObjectDescriptionInUse; + rodiu.vertices.push( this.vertices[ index++ ] ); + rodiu.vertices.push( this.vertices[ index++ ] ); + rodiu.vertices.push( this.vertices[ index ] ); + }; + + _RawObject.prototype._attachFaceVt = function ( faceIndex ) { + var faceIndexInt = parseInt( faceIndex ); + var index = ( faceIndexInt - this.globalUvOffset ) * 2; + + var rodiu = this.rawObjectDescriptionInUse; + rodiu.uvs.push( this.uvs[ index++ ] ); + rodiu.uvs.push( this.uvs[ index ] ); + }; + + _RawObject.prototype._attachFaceVn = function ( faceIndex ) { + var faceIndexInt = parseInt( faceIndex ); + var index = ( faceIndexInt - this.globalNormalOffset ) * 3; + + var rodiu = this.rawObjectDescriptionInUse; + rodiu.normals.push( this.normals[ index++ ] ); + rodiu.normals.push( this.normals[ index++ ] ); + rodiu.normals.push( this.normals[ index ] ); + }; + + /* + * Support for lines with or without texture. irst element in indexArray is the line identification + * 0: "f vertex/uv vertex/uv ..." + * 1: "f vertex vertex ..." + */ + _RawObject.prototype._buildLineVvt = function ( lineArray ) { + var length = lineArray.length; + for ( var i = 1; i < length; i ++ ) { + this.vertices.push( parseInt( lineArray[ i ] ) ); + this.uvs.push( parseInt( lineArray[ i ] ) ); + } + }; + + _RawObject.prototype._buildLineV = function ( lineArray ) { + var length = lineArray.length; + for ( var i = 1; i < length; i++ ) { + this.vertices.push( parseInt( lineArray[ i ] ) ); + } + }; + + /** + * Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts + */ + _RawObject.prototype._finalize = function ( meshCreator, inputObjectCount, debug ) { + var temp = this.rawObjectDescriptions; + this.rawObjectDescriptions = []; + var rawObjectDescription; + var index = 0; + var absoluteVertexCount = 0; + var absoluteNormalCount = 0; + var absoluteUvCount = 0; + + for ( var name in temp ) { + + rawObjectDescription = temp[ name ]; + if ( rawObjectDescription.vertices.length > 0 ) { + + if ( rawObjectDescription.objectName === 'none' ) rawObjectDescription.objectName = rawObjectDescription.groupName; + this.rawObjectDescriptions[ index++ ] = rawObjectDescription; + absoluteVertexCount += rawObjectDescription.vertices.length; + absoluteUvCount += rawObjectDescription.uvs.length; + absoluteNormalCount += rawObjectDescription.normals.length; + + } + } + + // don not continue if no result + var notEmpty = false; + if ( index > 0 ) { + + if ( debug ) this._createReport( inputObjectCount, true ); + meshCreator.buildMesh( + this.rawObjectDescriptions, + inputObjectCount, + absoluteVertexCount, + absoluteNormalCount, + absoluteUvCount + ); + notEmpty = true; + + } + return notEmpty; + }; + + _RawObject.prototype._createReport = function ( inputObjectCount, printDirectly ) { + var report = { + name: this.objectName ? this.objectName : 'groups', + mtllibName: this.mtllibName, + vertexCount: this.vertices.length / 3, + normalCount: this.normals.length / 3, + uvCount: this.uvs.length / 2, + smoothingGroupCount: this.smoothingGroupCount, + mtlCount: this.mtlCount, + rawObjectDescriptions: this.rawObjectDescriptions.length + }; + + if ( printDirectly ) { + console.log( 'Input Object number: ' + inputObjectCount + ' Object name: ' + report.name ); + console.log( 'Mtllib name: ' + report.mtllibName ); + console.log( 'Vertex count: ' + report.vertexCount ); + console.log( 'Normal count: ' + report.normalCount ); + console.log( 'UV count: ' + report.uvCount ); + console.log( 'SmoothingGroup count: ' + report.smoothingGroupCount ); + console.log( 'Material count: ' + report.mtlCount ); + console.log( 'Real RawObjectDescription count: ' + report.rawObjectDescriptions ); + console.log( '' ); + } + + return report; + }; + + return _RawObject; +})(); + +/** + * Descriptive information and data (vertices, normals, uvs) to passed on to mesh building function. + * @class + * + * @param {string} objectName Name of the mesh + * @param {string} groupName Name of the group + * @param {string} materialName Name of the material + * @param {number} smoothingGroup Normalized smoothingGroup (0: THREE.FlatShading, 1: THREE.SmoothShading) + */ +THREE.OBJLoader2.RawObjectDescription = (function () { + + function RawObjectDescription( objectName, groupName, materialName, smoothingGroup ) { + this.objectName = objectName; + this.groupName = groupName; + this.materialName = materialName; + this.smoothingGroup = smoothingGroup; + this.vertices = []; + this.uvs = []; + this.normals = []; + } + + return RawObjectDescription; +})(); + +/** + * MeshCreator is used to transform THREE.OBJLoader2.RawObjectDescriptions to THREE.Mesh + * + * @class + */ +THREE.OBJLoader2.MeshCreator = (function () { + + function MeshCreator() { + this.sceneGraphBaseNode = null; + this.materials = null; + this.debug = false; + this.globalObjectCount = 1; + + this.validated = false; + } + + MeshCreator.prototype._setSceneGraphBaseNode = function ( sceneGraphBaseNode ) { + this.sceneGraphBaseNode = ( sceneGraphBaseNode == null ) ? ( this.sceneGraphBaseNode == null ? new THREE.Group() : this.sceneGraphBaseNode ) : sceneGraphBaseNode; + }; + + MeshCreator.prototype._setMaterials = function ( materials ) { + this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials; + }; + + MeshCreator.prototype._setDebug = function ( debug ) { + this.debug = ( debug == null ) ? this.debug : debug; + }; + + MeshCreator.prototype._validate = function () { + if ( this.validated ) return; + + this._setSceneGraphBaseNode( null ); + this._setMaterials( null ); + this._setDebug( null ); + this.globalObjectCount = 1; + }; + + MeshCreator.prototype._finalize = function () { + this.sceneGraphBaseNode = null; + this.materials = null; + this.validated = false; + }; + + /** + * RawObjectDescriptions are transformed to THREE.Mesh. + * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check). + * This method shall be overridden by the web worker implementation + * @memberOf THREE.OBJLoader2.MeshCreator + * + * @param {THREE.OBJLoader2.RawObjectDescription[]} rawObjectDescriptions Array of descriptive information and data (vertices, normals, uvs) about the parsed object(s) + * @param {number} inputObjectCount Number of objects already retrieved from OBJ + * @param {number} absoluteVertexCount Sum of all vertices of all rawObjectDescriptions + * @param {number} absoluteNormalCount Sum of all normals of all rawObjectDescriptions + * @param {number} absoluteUvCount Sum of all uvs of all rawObjectDescriptions + */ + MeshCreator.prototype.buildMesh = function ( rawObjectDescriptions, inputObjectCount, absoluteVertexCount, absoluteNormalCount, absoluteUvCount ) { + + if ( this.debug ) console.log( 'MeshCreator.buildRawMeshData:\nInput object no.: ' + inputObjectCount ); + + var bufferGeometry = new THREE.BufferGeometry(); + var vertexBA = new THREE.BufferAttribute( new Float32Array( absoluteVertexCount ), 3 ); + bufferGeometry.addAttribute( 'position', vertexBA ); + + var normalBA; + if ( absoluteNormalCount > 0 ) { + + normalBA = new THREE.BufferAttribute( new Float32Array( absoluteNormalCount ), 3 ); + bufferGeometry.addAttribute( 'normal', normalBA ); + + } + var uvBA; + if ( absoluteUvCount > 0 ) { + + uvBA = new THREE.BufferAttribute( new Float32Array( absoluteUvCount ), 2 ); + bufferGeometry.addAttribute( 'uv', uvBA ); + + } + + if ( this.debug ) console.log( 'Creating Multi-Material for object no.: ' + this.globalObjectCount ); + + var rawObjectDescription; + var material; + var materialName; + var createMultiMaterial = ( rawObjectDescriptions.length > 1 ) ? true : false; + var materials = []; + var materialIndex = 0; + var materialIndexMapping = []; + var selectedMaterialIndex; + + var vertexBAOffset = 0; + var vertexGroupOffset = 0; + var vertexLength; + var normalOffset = 0; + var uvOffset = 0; + + for ( var oodIndex in rawObjectDescriptions ) { + rawObjectDescription = rawObjectDescriptions[ oodIndex ]; + + materialName = rawObjectDescription.materialName; + material = this.materials[ materialName ]; + if ( ! material ) { + + material = this.materials[ 'defaultMaterial' ]; + if ( ! material ) { + + material = new THREE.MeshStandardMaterial( { color: 0xDCF1FF} ); + material.name = 'defaultMaterial'; + this.materials[ 'defaultMaterial' ] = material; + + } + console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' ); + + } + // clone material in case flat shading is needed due to smoothingGroup 0 + if ( rawObjectDescription.smoothingGroup === 0 ) { + + materialName = material.name + '_flat'; + var materialClone = this.materials[ materialName ]; + if ( ! materialClone ) { + + materialClone = material.clone(); + materialClone.name = materialName; + materialClone.shading = THREE.FlatShading; + this.materials[ materialName ] = name; + + } + + } + + vertexLength = rawObjectDescription.vertices.length; + if ( createMultiMaterial ) { + + // re-use material if already used before. Reduces materials array size and eliminates duplicates + selectedMaterialIndex = materialIndexMapping[ materialName ]; + if ( ! selectedMaterialIndex ) { + + selectedMaterialIndex = materialIndex; + materialIndexMapping[ materialName ] = materialIndex; + materials.push( material ); + materialIndex++; + + } + + bufferGeometry.addGroup( vertexGroupOffset, vertexLength / 3, selectedMaterialIndex ); + vertexGroupOffset += vertexLength / 3; + } + + vertexBA.set( rawObjectDescription.vertices, vertexBAOffset ); + vertexBAOffset += vertexLength; + + if ( normalBA ) { + + normalBA.set( rawObjectDescription.normals, normalOffset ); + normalOffset += rawObjectDescription.normals.length; + + } + if ( uvBA ) { + + uvBA.set( rawObjectDescription.uvs, uvOffset ); + uvOffset += rawObjectDescription.uvs.length; + + } + if ( this.debug ) this._printReport( rawObjectDescription, selectedMaterialIndex ); + + } + if ( ! normalBA ) bufferGeometry.computeVertexNormals(); + + if ( createMultiMaterial ) material = new THREE.MultiMaterial( materials ); + var mesh = new THREE.Mesh( bufferGeometry, material ); + this.sceneGraphBaseNode.add( mesh ); + + this.globalObjectCount++; + }; + + MeshCreator.prototype._printReport = function ( rawObjectDescription, selectedMaterialIndex ) { + console.log( + ' Output Object no.: ' + this.globalObjectCount + + '\n objectName: ' + rawObjectDescription.objectName + + '\n groupName: ' + rawObjectDescription.groupName + + '\n materialName: ' + rawObjectDescription.materialName + + '\n materialIndex: ' + selectedMaterialIndex + + '\n smoothingGroup: ' + rawObjectDescription.smoothingGroup + + '\n #vertices: ' + rawObjectDescription.vertices.length / 3 + + '\n #uvs: ' + rawObjectDescription.uvs.length / 2 + + '\n #normals: ' + rawObjectDescription.normals.length / 3 + ); + }; + + return MeshCreator; +})(); \ No newline at end of file diff --git a/examples/js/loaders/wwobjloader2/OBJLoader2.min.js b/examples/js/loaders/wwobjloader2/OBJLoader2.min.js new file mode 100644 index 00000000000000..e28123730f57c8 --- /dev/null +++ b/examples/js/loaders/wwobjloader2/OBJLoader2.min.js @@ -0,0 +1 @@ +"use strict";void 0===THREE.OBJLoader2&&(THREE.OBJLoader2={}),THREE.OBJLoader2=function(){function t(t){this.manager=null==t?THREE.DefaultLoadingManager:t,this.path="",this.fileLoader=new THREE.FileLoader(this.manager),this.meshCreator=new THREE.OBJLoader2.MeshCreator,this.parser=new THREE.OBJLoader2.Parser(this.meshCreator),this.validated=!1}return t.prototype.setPath=function(t){this.path=null==t?this.path:t},t.prototype.setSceneGraphBaseNode=function(t){this.meshCreator._setSceneGraphBaseNode(t)},t.prototype.setMaterials=function(t){this.meshCreator._setMaterials(t)},t.prototype.setDebug=function(t,e){this.parser._setDebug(t),this.meshCreator._setDebug(e)},t.prototype.load=function(t,e,s,a,o){this._validate(),this.fileLoader.setPath(this.path),this.fileLoader.setResponseType(o||null==o?"arraybuffer":"text");var i=this;i.fileLoader.load(t,function(t){e(o||null==o?i.parse(t):i.parseText(t))},s,a)},t.prototype.parse=function(t){if(!(t instanceof ArrayBuffer||t instanceof Uint8Array))throw"Provided input is not of type arraybuffer! Aborting...";console.log("Parsing arrayBuffer..."),console.time("parseArrayBuffer"),this._validate(),this.parser.parseArrayBuffer(t);var e=this._finalize();return console.timeEnd("parseArrayBuffer"),e},t.prototype.parseText=function(t){if(!("string"==typeof t||t instanceof String))throw"Provided input is not of type String! Aborting...";console.log("Parsing text..."),console.time("parseText"),this._validate(),this.parser.parseText(t);var e=this._finalize();return console.timeEnd("parseText"),e},t.prototype._validate=function(){this.validated||(this.fileLoader=null==this.fileLoader?new THREE.FileLoader(this.manager):this.fileLoader,this.setPath(null),this.parser._validate(),this.meshCreator._validate(),this.validated=!0)},t.prototype._finalize=function(){console.log("Global output object count: "+this.meshCreator.globalObjectCount),this.parser._finalize(),this.fileLoader=null;var t=this.meshCreator.sceneGraphBaseNode;return this.meshCreator._finalize(),this.validated=!1,t},t}(),void 0===THREE.OBJLoader2&&(THREE.OBJLoader2={}),THREE.OBJLoader2.consts={CODE_LF:10,CODE_CR:13,CODE_SPACE:32,CODE_SLASH:47,STRING_LF:"\n",STRING_CR:"\r",STRING_SPACE:" ",STRING_SLASH:"/",LINE_F:"f",LINE_G:"g",LINE_L:"l",LINE_O:"o",LINE_S:"s",LINE_V:"v",LINE_VT:"vt",LINE_VN:"vn",LINE_MTLLIB:"mtllib",LINE_USEMTL:"usemtl",QUAD_INDICES_1:[1,2,3,3,4,1],QUAD_INDICES_2:[1,3,5,5,7,1],QUAD_INDICES_3:[1,4,7,7,10,1],_buildIndex:function(t,e){return t+"|"+e}},THREE.OBJLoader2.Parser=function(){function t(t){this.meshCreator=t,this.rawObject=null,this.inputObjectCount=1,this.debug=!1}return t.prototype._setDebug=function(t){this.debug=null==t?this.debug:t},t.prototype._validate=function(){this.rawObject=new THREE.OBJLoader2._RawObject,this.inputObjectCount=1},t.prototype.parseArrayBuffer=function(t){for(var e,s=new Uint8Array(t),a=s.byteLength,o=new Array(32),i=0,r=new Array(32),n=0,h=!1,c="",l=0;l0&&(o[i++]=c),c="";break;case THREE.OBJLoader2.consts.CODE_SLASH:r[n++]=l,c.length>0&&(o[i++]=c),c="";break;case THREE.OBJLoader2.consts.CODE_LF:c.length>0&&(o[i++]=c),c="",h=this._processLine(o,i,r,n,h),n=0,i=0;break;case THREE.OBJLoader2.consts.CODE_CR:break;default:c+=String.fromCharCode(e)}},t.prototype.parseText=function(t){for(var e,s=t.length,a=new Array(32),o=0,i=new Array(32),r=0,n=!1,h="",c=0;c0&&(a[o++]=h),h="";break;case THREE.OBJLoader2.consts.STRING_SLASH:i[r++]=c,h.length>0&&(a[o++]=h),h="";break;case THREE.OBJLoader2.consts.STRING_LF:h.length>0&&(a[o++]=h),h="",n=this._processLine(a,o,i,r,n),r=0,o=0;break;case THREE.OBJLoader2.consts.STRING_CR:break;default:h+=e}},t.prototype._processLine=function(t,e,s,a,o){if(e<1)return o;var i=e-1;switch(t[0]){case THREE.OBJLoader2.consts.LINE_V:o&&(this._processCompletedObject(null,this.rawObject.groupName),o=!1),this.rawObject._pushVertex(t);break;case THREE.OBJLoader2.consts.LINE_VT:this.rawObject._pushUv(t);break;case THREE.OBJLoader2.consts.LINE_VN:this.rawObject._pushNormal(t);break;case THREE.OBJLoader2.consts.LINE_F:o=!0;var r=i%4===0;a>1&&s[1]-s[0]===1?r?this.rawObject._buildQuadVVn(t):this.rawObject._buildFaceVVn(t):i===2*a?r?this.rawObject._buildQuadVVt(t):this.rawObject._buildFaceVVt(t):2*i===3*a?r?this.rawObject._buildQuadVVtVn(t):this.rawObject._buildFaceVVtVn(t):r?this.rawObject._buildQuadV(t):this.rawObject._buildFaceV(t);break;case THREE.OBJLoader2.consts.LINE_L:i===2*a?this.rawObject._buildLineVvt(t):this.rawObject._buildLineV(t);break;case THREE.OBJLoader2.consts.LINE_S:this.rawObject._pushSmoothingGroup(t[1]);break;case THREE.OBJLoader2.consts.LINE_G:this._processCompletedGroup(t[1]);break;case THREE.OBJLoader2.consts.LINE_O:this.rawObject.vertices.length>0?(this._processCompletedObject(t[1],null),o=!1):this.rawObject._pushObject(t[1]);break;case THREE.OBJLoader2.consts.LINE_MTLLIB:this.rawObject._pushMtllib(t[1]);break;case THREE.OBJLoader2.consts.LINE_USEMTL:this.rawObject._pushUsemtl(t[1])}return o},t.prototype._processCompletedObject=function(t,e){this.rawObject._finalize(this.meshCreator,this.inputObjectCount,this.debug),this.inputObjectCount++,this.rawObject=this.rawObject._newInstanceFromObject(t,e)},t.prototype._processCompletedGroup=function(t){var e=this.rawObject._finalize(this.meshCreator,this.inputObjectCount,this.debug);e?(this.inputObjectCount++,this.rawObject=this.rawObject._newInstanceFromGroup(t)):this.rawObject._pushGroup(t)},t.prototype._finalize=function(){this.rawObject._finalize(this.meshCreator,this.inputObjectCount,this.debug),this.inputObjectCount++},t}(),THREE.OBJLoader2._RawObject=function(){function t(t,e,s){this.globalVertexOffset=1,this.globalUvOffset=1,this.globalNormalOffset=1,this.vertices=[],this.normals=[],this.uvs=[],this.mtllibName=null!=s?s:"none",this.objectName=null!=t?t:"none",this.groupName=null!=e?e:"none",this.activeMtlName="none",this.activeSmoothingGroup=1,this.mtlCount=0,this.smoothingGroupCount=0,this.rawObjectDescriptions=[];var a=THREE.OBJLoader2.consts._buildIndex(this.activeMtlName,this.activeSmoothingGroup);this.rawObjectDescriptionInUse=new THREE.OBJLoader2.RawObjectDescription(this.objectName,this.groupName,this.activeMtlName,this.activeSmoothingGroup),this.rawObjectDescriptions[a]=this.rawObjectDescriptionInUse}return t.prototype._newInstanceFromObject=function(e,s){var a=new t(e,s,this.mtllibName);return a.globalVertexOffset=this.globalVertexOffset+this.vertices.length/3,a.globalUvOffset=this.globalUvOffset+this.uvs.length/2,a.globalNormalOffset=this.globalNormalOffset+this.normals.length/3,a},t.prototype._newInstanceFromGroup=function(e){var s=new t(this.objectName,e,this.mtllibName);return s.vertices=this.vertices,s.uvs=this.uvs,s.normals=this.normals,s.globalVertexOffset=this.globalVertexOffset,s.globalUvOffset=this.globalUvOffset,s.globalNormalOffset=this.globalNormalOffset,s},t.prototype._pushVertex=function(t){this.vertices.push(parseFloat(t[1])),this.vertices.push(parseFloat(t[2])),this.vertices.push(parseFloat(t[3]))},t.prototype._pushUv=function(t){this.uvs.push(parseFloat(t[1])),this.uvs.push(parseFloat(t[2]))},t.prototype._pushNormal=function(t){this.normals.push(parseFloat(t[1])),this.normals.push(parseFloat(t[2])),this.normals.push(parseFloat(t[3]))},t.prototype._pushObject=function(t){this.objectName=t},t.prototype._pushMtllib=function(t){this.mtllibName=t},t.prototype._pushGroup=function(t){this.groupName=t,this._verifyIndex()},t.prototype._pushUsemtl=function(t){this.activeMtlName!==t&&null!=t&&(this.activeMtlName=t,this.mtlCount++,this._verifyIndex())},t.prototype._pushSmoothingGroup=function(t){var e="off"===t?0:t;this.activeSmoothingGroup!==e&&(this.activeSmoothingGroup=e,this.smoothingGroupCount++,this._verifyIndex())},t.prototype._verifyIndex=function(){var t=THREE.OBJLoader2.consts._buildIndex(this.activeMtlName,0===this.activeSmoothingGroup?0:1);null==this.rawObjectDescriptions[t]?this.rawObjectDescriptionInUse=this.rawObjectDescriptions[t]=new THREE.OBJLoader2.RawObjectDescription(this.objectName,this.groupName,this.activeMtlName,this.activeSmoothingGroup):this.rawObjectDescriptionInUse=this.rawObjectDescriptions[t]},t.prototype._buildQuadVVtVn=function(t){for(var e=0;e<6;e++)this._attachFaceV_(t[THREE.OBJLoader2.consts.QUAD_INDICES_3[e]]),this._attachFaceVt(t[THREE.OBJLoader2.consts.QUAD_INDICES_3[e]+1]),this._attachFaceVn(t[THREE.OBJLoader2.consts.QUAD_INDICES_3[e]+2])},t.prototype._buildQuadVVt=function(t){for(var e=0;e<6;e++)this._attachFaceV_(t[THREE.OBJLoader2.consts.QUAD_INDICES_2[e]]),this._attachFaceVt(t[THREE.OBJLoader2.consts.QUAD_INDICES_2[e]+1])},t.prototype._buildQuadVVn=function(t){for(var e=0;e<6;e++)this._attachFaceV_(t[THREE.OBJLoader2.consts.QUAD_INDICES_2[e]]),this._attachFaceVn(t[THREE.OBJLoader2.consts.QUAD_INDICES_2[e]+1])},t.prototype._buildQuadV=function(t){for(var e=0;e<6;e++)this._attachFaceV_(t[THREE.OBJLoader2.consts.QUAD_INDICES_1[e]])},t.prototype._buildFaceVVtVn=function(t){for(var e=1;e<10;e+=3)this._attachFaceV_(t[e]),this._attachFaceVt(t[e+1]),this._attachFaceVn(t[e+2])},t.prototype._buildFaceVVt=function(t){for(var e=1;e<7;e+=2)this._attachFaceV_(t[e]),this._attachFaceVt(t[e+1])},t.prototype._buildFaceVVn=function(t){for(var e=1;e<7;e+=2)this._attachFaceV_(t[e]),this._attachFaceVn(t[e+1])},t.prototype._buildFaceV=function(t){for(var e=1;e<4;e++)this._attachFaceV_(t[e])},t.prototype._attachFaceV_=function(t){var e=parseInt(t),s=3*(e-this.globalVertexOffset),a=this.rawObjectDescriptionInUse;a.vertices.push(this.vertices[s++]),a.vertices.push(this.vertices[s++]),a.vertices.push(this.vertices[s])},t.prototype._attachFaceVt=function(t){var e=parseInt(t),s=2*(e-this.globalUvOffset),a=this.rawObjectDescriptionInUse;a.uvs.push(this.uvs[s++]),a.uvs.push(this.uvs[s])},t.prototype._attachFaceVn=function(t){var e=parseInt(t),s=3*(e-this.globalNormalOffset),a=this.rawObjectDescriptionInUse;a.normals.push(this.normals[s++]),a.normals.push(this.normals[s++]),a.normals.push(this.normals[s])},t.prototype._buildLineVvt=function(t){for(var e=t.length,s=1;s0&&("none"===o.objectName&&(o.objectName=o.groupName),this.rawObjectDescriptions[i++]=o,r+=o.vertices.length,h+=o.uvs.length,n+=o.normals.length);var l=!1;return i>0&&(s&&this._createReport(e,!0),t.buildMesh(this.rawObjectDescriptions,e,r,n,h),l=!0),l},t.prototype._createReport=function(t,e){var s={name:this.objectName?this.objectName:"groups",mtllibName:this.mtllibName,vertexCount:this.vertices.length/3,normalCount:this.normals.length/3,uvCount:this.uvs.length/2,smoothingGroupCount:this.smoothingGroupCount,mtlCount:this.mtlCount,rawObjectDescriptions:this.rawObjectDescriptions.length};return e&&(console.log("Input Object number: "+t+" Object name: "+s.name),console.log("Mtllib name: "+s.mtllibName),console.log("Vertex count: "+s.vertexCount),console.log("Normal count: "+s.normalCount),console.log("UV count: "+s.uvCount),console.log("SmoothingGroup count: "+s.smoothingGroupCount),console.log("Material count: "+s.mtlCount),console.log("Real RawObjectDescription count: "+s.rawObjectDescriptions),console.log("")),s},t}(),THREE.OBJLoader2.RawObjectDescription=function(){function t(t,e,s,a){this.objectName=t,this.groupName=e,this.materialName=s,this.smoothingGroup=a,this.vertices=[],this.uvs=[],this.normals=[]}return t}(),THREE.OBJLoader2.MeshCreator=function(){function t(){this.sceneGraphBaseNode=null,this.materials=null,this.debug=!1,this.globalObjectCount=1,this.validated=!1}return t.prototype._setSceneGraphBaseNode=function(t){this.sceneGraphBaseNode=null==t?null==this.sceneGraphBaseNode?new THREE.Group:this.sceneGraphBaseNode:t},t.prototype._setMaterials=function(t){this.materials=null==t?null==this.materials?{materials:[]}:this.materials:t},t.prototype._setDebug=function(t){this.debug=null==t?this.debug:t},t.prototype._validate=function(){this.validated||(this._setSceneGraphBaseNode(null),this._setMaterials(null),this._setDebug(null),this.globalObjectCount=1)},t.prototype._finalize=function(){this.sceneGraphBaseNode=null,this.materials=null,this.validated=!1},t.prototype.buildMesh=function(t,e,s,a,o){this.debug&&console.log("MeshCreator.buildRawMeshData:\nInput object no.: "+e);var i=new THREE.BufferGeometry,r=new THREE.BufferAttribute(new Float32Array(s),3);i.addAttribute("position",r);var n;a>0&&(n=new THREE.BufferAttribute(new Float32Array(a),3),i.addAttribute("normal",n));var h;o>0&&(h=new THREE.BufferAttribute(new Float32Array(o),2),i.addAttribute("uv",h)),this.debug&&console.log("Creating Multi-Material for object no.: "+this.globalObjectCount);var c,l,u,p,b,f=t.length>1,_=[],d=0,m=[],E=0,O=0,g=0,v=0;for(var N in t){if(c=t[N],u=c.materialName,l=this.materials[u],l||(l=this.materials.defaultMaterial,l||(l=new THREE.MeshStandardMaterial({color:14479871}),l.name="defaultMaterial",this.materials.defaultMaterial=l),console.warn('object_group "'+c.objectName+"_"+c.groupName+'" was defined without material! Assigning "defaultMaterial".')),0===c.smoothingGroup){u=l.name+"_flat";var L=this.materials[u];L||(L=l.clone(),L.name=u,L.shading=THREE.FlatShading,this.materials[u]=name)}b=c.vertices.length,f&&(p=m[u],p||(p=d,m[u]=d,_.push(l),d++),i.addGroup(O,b/3,p),O+=b/3),r.set(c.vertices,E),E+=b,n&&(n.set(c.normals,g),g+=c.normals.length),h&&(h.set(c.uvs,v),v+=c.uvs.length),this.debug&&this._printReport(c,p)}n||i.computeVertexNormals(),f&&(l=new THREE.MultiMaterial(_));var j=new THREE.Mesh(i,l);this.sceneGraphBaseNode.add(j),this.globalObjectCount++},t.prototype._printReport=function(t,e){console.log(" Output Object no.: "+this.globalObjectCount+"\n objectName: "+t.objectName+"\n groupName: "+t.groupName+"\n materialName: "+t.materialName+"\n materialIndex: "+e+"\n smoothingGroup: "+t.smoothingGroup+"\n #vertices: "+t.vertices.length/3+"\n #uvs: "+t.uvs.length/2+"\n #normals: "+t.normals.length/3)},t}(); \ No newline at end of file diff --git a/examples/js/loaders/wwobjloader2/WWOBJLoader2.js b/examples/js/loaders/wwobjloader2/WWOBJLoader2.js new file mode 100644 index 00000000000000..e1fa429ebd3bf6 --- /dev/null +++ b/examples/js/loaders/wwobjloader2/WWOBJLoader2.js @@ -0,0 +1,1126 @@ +/** + * @author Kai Salmen / www.kaisalmen.de + */ + +'use strict'; + +/** + * OBJ data will be loaded by dynamically created web worker. + * First feed instructions with: prepareRun + * Then: Execute with: run + * @class + */ +THREE.OBJLoader2.WWOBJLoader2 = (function () { + + function WWOBJLoader2() { + this._init(); + } + + WWOBJLoader2.prototype._init = function () { + // check worker support first + if ( window.Worker === undefined ) throw "This browser does not support web workers!"; + + this.instanceNo = 0; + this.worker = null; + this.workerCode = null; + this.debug = false; + + this.sceneGraphBaseNode = null; + this.modelName = 'none'; + this.validated = false; + this.running = false; + this.requestTerminate = false; + + this.callbacks = { + progress: null, + completedLoading: null, + errorWhileLoading: null, + materialsLoaded: null, + meshLoaded: null, + director: { + completedLoading: null, + errorWhileLoading: null + } + }; + + this.manager = THREE.DefaultLoadingManager; + this.fileLoader = new THREE.FileLoader( this.manager ); + this.mtlLoader = null; + + this.dataAvailable = false; + this.objAsArrayBuffer = null; + this.fileObj = null; + this.pathObj = null; + + this.fileMtl = null; + this.mtlAsString = null; + this.texturePath = null; + + this.materials = []; + this.counter = 0; + }; + + /** + * Set enable or disable debug logging + * @memberOf THREE.OBJLoader2.WWOBJLoader2 + * + * @param {boolean} enabled + */ + WWOBJLoader2.prototype.setDebug = function ( enabled ) { + this.debug = enabled; + }; + + /** + * 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; + }; + + /** + * 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; + }; + + /** + * 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; + }; + + /** + * Register callback function that is called every time a mesh was loaded + * @memberOf THREE.OBJLoader2.WWOBJLoader2 + * + * @param {callback} callbackMeshLoaded Callback function for described functionality + */ + WWOBJLoader2.prototype.registerCallbackMeshLoaded = function ( callbackMeshLoaded ) { + if ( callbackMeshLoaded != null ) this.callbacks.meshLoaded = callbackMeshLoaded; + }; + + /** + * Report if an error 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; + }; + + /** + * Call requestTerminate to terminate the web worker and free local resource after execution + * @memberOf THREE.OBJLoader2.WWOBJLoader2 + * + * @param {boolean} requestTerminate + */ + WWOBJLoader2.prototype.setRequestTerminate = function ( requestTerminate ) { + this.requestTerminate = ( requestTerminate != null && requestTerminate ) ? true : false; + }; + + WWOBJLoader2.prototype._validate = function () { + if ( this.validated ) return; + if ( this.worker == null ) { + + this._buildWebWorkerCode(); + var blob = new Blob( [ this.workerCode ], { type: 'text/plain' } ); + this.worker = new Worker( window.URL.createObjectURL( blob ) ); + + var scope = this; + var scopeFunction = function ( e ) { + scope._receiveWorkerMessage( e ); + }; + this.worker.addEventListener( 'message', scopeFunction, false ); + + } + + this.sceneGraphBaseNode = null; + this.modelName = 'none'; + this.validated = true; + 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; + + this.dataAvailable = false; + this.fileObj = null; + this.pathObj = null; + this.fileMtl = null; + this.texturePath = null; + + this.objAsArrayBuffer = null; + this.mtlAsString = null; + + this.materials = []; + var defaultMaterial = new THREE.MeshStandardMaterial( { color: 0xDCF1FF } ); + defaultMaterial.name = 'defaultMaterial'; + this.materials[ defaultMaterial.name ] = defaultMaterial; + + this.counter = 0; + }; + + /** + * Set all parameters for required for execution of "run". + * @memberOf THREE.OBJLoader2.WWOBJLoader2 + * + * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile} + */ + WWOBJLoader2.prototype.prepareRun = function ( params ) { + this._validate(); + this.dataAvailable = params.dataAvailable; + this.modelName = params.modelName; + console.time( 'WWOBJLoader2' ); + if ( this.dataAvailable ) { + + // fast-fail on bad type + if ( ! ( params.objAsArrayBuffer instanceof ArrayBuffer || params.objAsArrayBuffer instanceof Uint8Array ) ) { + throw 'Provided input is not of type arraybuffer! Aborting...'; + } + + this.worker.postMessage( { + cmd: 'init', + debug: this.debug + } ); + + this.objAsArrayBuffer = params.objAsArrayBuffer; + this.mtlAsString = params.mtlAsString; + + } else { + + // fast-fail on bad type + if ( ! ( typeof( params.fileObj ) === 'string' || params.fileObj instanceof String ) ) { + throw 'Provided file is not properly defined! Aborting...'; + } + + this.worker.postMessage( { + cmd: 'init', + debug: this.debug + } ); + + this.fileObj = params.fileObj; + this.pathObj = params.pathObj; + this.fileMtl = params.fileMtl; + + } + this.setRequestTerminate( params.requestTerminate ); + this.pathTexture = params.pathTexture; + this.sceneGraphBaseNode = params.sceneGraphBaseNode; + }; + + /** + * Run the loader according the preparation instruction provided in "prepareRun". + * @memberOf THREE.OBJLoader2.WWOBJLoader2 + */ + WWOBJLoader2.prototype.run = function () { + var scope = this; + var processLoadedMaterials = function ( materialCreator ) { + var materialCreatorMaterials = []; + var materialNames = []; + if ( materialCreator != null ) { + + materialCreator.preload(); + materialCreatorMaterials = materialCreator.materials; + for ( var materialName in materialCreatorMaterials ) { + + if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) { + + materialNames.push( materialName ); + scope.materials[ materialName ] = materialCreatorMaterials[ materialName ]; + + } + + } + + } + scope.worker.postMessage( { + cmd: 'setMaterials', + materialNames: materialNames + } ); + + if ( scope.callbacks.materialsLoaded != null ) { + + var materialsCallback = scope.callbacks.materialsLoaded( scope.materials ); + if ( materialsCallback != null ) scope.materials = materialsCallback; + + } + + if ( scope.dataAvailable && scope.objAsArrayBuffer ) { + + scope.worker.postMessage({ + cmd: 'run', + objAsArrayBuffer: scope.objAsArrayBuffer + }, [ scope.objAsArrayBuffer.buffer ] ); + + } else { + + var refPercentComplete = 0; + var percentComplete = 0; + var output; + var onLoad = function ( objAsArrayBuffer ) { + + scope._announceProgress( 'Running web worker!' ); + scope.objAsArrayBuffer = new Uint8Array( objAsArrayBuffer ); + scope.worker.postMessage( { + cmd: 'run', + objAsArrayBuffer: scope.objAsArrayBuffer + }, [ scope.objAsArrayBuffer.buffer ] ); + + }; + + var onProgress = function ( event ) { + if ( ! event.lengthComputable ) return; + + percentComplete = Math.round( event.loaded / event.total * 100 ); + if ( percentComplete > refPercentComplete ) { + + refPercentComplete = percentComplete; + output = 'Download of "' + scope.fileObj + '": ' + percentComplete + '%'; + console.log( output ); + scope._announceProgress( output ); + + } + }; + + var onError = function ( event ) { + output = 'Error occurred while downloading "' + scope.fileObj + '"'; + console.error( output + ': ' + event ); + scope._announceProgress( output ); + scope._finalize( 'error' ); + + }; + + scope.fileLoader.setPath( scope.pathObj ); + scope.fileLoader.setResponseType( 'arraybuffer' ); + scope.fileLoader.load( scope.fileObj, onLoad, onProgress, onError ); + } + console.timeEnd( 'Loading MTL textures' ); + }; + + if ( this.dataAvailable ) { + + processLoadedMaterials( ( this.mtlAsString != null ) ? this.mtlLoader.parse( this.mtlAsString ) : null ); + + } else { + + if ( this.fileMtl == null ) { + + processLoadedMaterials(); + + } else { + + this.mtlLoader.setPath( this.pathTexture ); + this.mtlLoader.load( this.fileMtl, processLoadedMaterials ); + + } + + } + }; + + WWOBJLoader2.prototype._receiveWorkerMessage = function ( event ) { + var payload = event.data; + + 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 !== null ) { + + bufferGeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( payload.normals ), 3 ) ); + + } else { + + bufferGeometry.computeVertexNormals(); + + } + if ( payload.uvs !== null ) { + + bufferGeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( payload.uvs ), 2 ) ); + + } + + var materialDescriptions = payload.materialDescriptions; + var materialDescription; + var material; + var materialName; + var createMultiMaterial = payload.multiMaterial; + var multiMaterials = []; + + for ( var key in materialDescriptions ) { + + materialDescription = materialDescriptions[ key ]; + material = this.materials[ materialDescription.name ]; + + if ( materialDescription.default ) { + + material = this.materials[ 'defaultMaterial' ]; + + } else if ( materialDescription.clone ) { + + materialName = material.name + '_flat'; + var materialClone = this.materials[ materialName ]; + if ( ! materialClone ) { + + materialClone = material.clone(); + materialClone.name = materialName; + materialClone.shading = THREE.FlatShading; + this.materials[ materialName ] = name; + + } + + } else if ( ! material ) { + + material = this.materials[ 'defaultMaterial' ]; + + } + if ( createMultiMaterial ) multiMaterials.push( material ); + + } + + if ( createMultiMaterial ) { + + material = new THREE.MultiMaterial( multiMaterials ); + var materialGroups = payload.materialGroups; + var materialGroup; + for ( var key in materialGroups ) { + + materialGroup = materialGroups[ key ]; + bufferGeometry.addGroup( materialGroup.start, materialGroup.count, materialGroup.index ); + + } + + } + if ( this.callbacks.meshLoaded !== null ) { + + var materialOverride = this.callbacks.meshLoaded( payload.meshName, material ); + if ( materialOverride != null ) material = materialOverride; + + } + var mesh = new THREE.Mesh( bufferGeometry, material ); + mesh.name = payload.meshName; + this.sceneGraphBaseNode.add( mesh ); + + var output = '(' + this.counter + '): ' + payload.meshName; + this._announceProgress( 'Adding mesh', output ); + break; + + case 'complete': + + console.timeEnd( 'WWOBJLoader2' ); + if ( payload.msg != null ) { + + this._announceProgress( payload.msg ); + + } else { + + this._announceProgress( '' ); + + } + + this._finalize( 'complete' ); + break; + + case 'report_progress': + this._announceProgress( '', payload.output ); + break; + + default: + console.error( 'Received unknown command: ' + payload.cmd ); + break; + + } + }; + + WWOBJLoader2.prototype._terminate = function () { + if ( this.worker != null ) { + + if ( this.running ) throw 'Unable to gracefully terminate worker as it is currently running!'; + + this.worker.terminate(); + this.worker = null; + this.workerCode = null; + this._finalize( 'terminate' ); + + } + this.fileLoader = null; + this.mtlLoader = null; + }; + + WWOBJLoader2.prototype._finalize = function ( reason, requestTerminate ) { + this.running = false; + 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 ); + + } 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 ); + + } + this.validated = false; + + this.setRequestTerminate( requestTerminate ); + + if ( this.requestTerminate ) { + this._terminate(); + } + }; + + WWOBJLoader2.prototype._announceProgress = function ( baseText, text ) { + var output = ""; + if ( baseText !== null && baseText !== undefined ) { + + output = baseText; + + } + if ( text !== null && text !== undefined ) { + + output = output + " " + text; + + } + if ( this.callbacks.progress !== null ) { + + this.callbacks.progress( output ); + + } + if ( this.debug ) { + + console.log( output ); + + } + }; + + WWOBJLoader2.prototype._buildWebWorkerCode = function ( existingWorkerCode ) { + if ( existingWorkerCode != null ) this.workerCode = existingWorkerCode; + if ( this.workerCode == null ) { + + console.time( 'buildWebWorkerCode' ); + var wwDef = (function () { + + function OBJLoader() { + this.meshCreator = new THREE.OBJLoader2.WW.MeshCreator(); + this.parser = new THREE.OBJLoader2.Parser( this.meshCreator ); + this.validated = false; + this.cmdState = 'created'; + + this.debug = false; + } + + /** + * Allows to set debug mode for the parser and the meshCreatorDebug + * + * @param parserDebug + * @param meshCreatorDebug + */ + OBJLoader.prototype._setDebug = function ( parserDebug, meshCreatorDebug ) { + this.parser._setDebug( parserDebug ); + this.meshCreator._setDebug( meshCreatorDebug ); + }; + + /** + * Validate status, then parse arrayBuffer, finalize and return objGroup + * + * @param arrayBuffer + */ + OBJLoader.prototype.parse = function ( arrayBuffer ) { + console.log( 'Parsing arrayBuffer...' ); + console.time( 'parseArrayBuffer' ); + + this._validate(); + this.parser.parseArrayBuffer( arrayBuffer ); + var objGroup = this._finalize(); + + console.timeEnd( 'parseArrayBuffer' ); + + return objGroup; + }; + + OBJLoader.prototype._validate = function () { + if ( this.validated ) return; + + this.parser._validate(); + this.meshCreator._validate(); + + this.validated = true; + }; + + OBJLoader.prototype._finalize = function () { + console.log( 'Global output object count: ' + this.meshCreator.globalObjectCount ); + this.parser._finalize(); + this.meshCreator._finalize(); + this.validated = false; + }; + + OBJLoader.prototype.init = function ( payload ) { + this.cmdState = 'init'; + this._setDebug( payload.debug, payload.debug ); + }; + + OBJLoader.prototype.setMaterials = function ( payload ) { + this.cmdState = 'setMaterials'; + this.meshCreator._setMaterials( payload.materialNames ); + }; + + OBJLoader.prototype.run = function ( payload ) { + this.cmdState = 'run'; + + this.parse( payload.objAsArrayBuffer ); + console.log( 'OBJ loading complete!' ); + + this.cmdState = 'complete'; + self.postMessage( { + cmd: this.cmdState, + msg: null + } ); + }; + + return OBJLoader; + })(); + + var wwMeshCreatorDef = (function () { + + function MeshCreator() { + this.materials = null; + this.debug = false; + this.globalObjectCount = 1; + this.validated = false; + } + + MeshCreator.prototype._setMaterials = function ( materials ) { + this.materials = ( materials == null ) ? ( this.materials == null ? { materials: [] } : this.materials ) : materials; + }; + + MeshCreator.prototype._setDebug = function ( debug ) { + this.debug = ( debug == null ) ? this.debug : debug; + }; + + MeshCreator.prototype._validate = function () { + if ( this.validated ) return; + + this._setMaterials( null ); + this._setDebug( null ); + this.globalObjectCount = 1; + }; + + MeshCreator.prototype._finalize = function () { + this.materials = null; + this.validated = false; + }; + + /** + * RawObjectDescriptions are transformed to THREE.Mesh. + * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check). + * + * @param rawObjectDescriptions + * @param inputObjectCount + * @param absoluteVertexCount + * @param absoluteNormalCount + * @param absoluteUvCount + */ + MeshCreator.prototype.buildMesh = function ( rawObjectDescriptions, inputObjectCount, absoluteVertexCount, absoluteNormalCount, absoluteUvCount ) { + if ( this.debug ) console.log( 'OBJLoader.buildMesh:\nInput object no.: ' + inputObjectCount ); + + var vertexFa = new Float32Array( absoluteVertexCount ); + var normalFA = ( absoluteNormalCount > 0 ) ? new Float32Array( absoluteNormalCount ) : null; + var uvFA = ( absoluteUvCount > 0 ) ? new Float32Array( absoluteUvCount ) : null; + + var rawObjectDescription; + var materialDescription; + var materialDescriptions = []; + + var createMultiMaterial = ( rawObjectDescriptions.length > 1 ) ? true : false; + var materialIndex = 0; + var materialIndexMapping = []; + var selectedMaterialIndex; + var materialGroup; + var materialGroups = []; + + var vertexBAOffset = 0; + var vertexGroupOffset = 0; + var vertexLength; + var normalOffset = 0; + var uvOffset = 0; + + for ( var oodIndex in rawObjectDescriptions ) { + rawObjectDescription = rawObjectDescriptions[ oodIndex ]; + + materialDescription = { name: rawObjectDescription.materialName, flat: false, default: false }; + if ( this.materials[ materialDescription.name ] === null ) { + + materialDescription.default = true; + console.warn( 'object_group "' + rawObjectDescription.objectName + '_' + rawObjectDescription.groupName + '" was defined without material! Assigning "defaultMaterial".' ); + + } + // Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0 + if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true; + + vertexLength = rawObjectDescription.vertices.length; + if ( createMultiMaterial ) { + + // re-use material if already used before. Reduces materials array size and eliminates duplicates + + selectedMaterialIndex = materialIndexMapping[ materialDescription.name ]; + if ( ! selectedMaterialIndex ) { + + selectedMaterialIndex = materialIndex; + materialIndexMapping[ materialDescription.name ] = materialIndex; + materialDescriptions.push( materialDescription ); + materialIndex++; + + } + materialGroup = { + start: vertexGroupOffset, + count: vertexLength / 3, + index: selectedMaterialIndex + }; + materialGroups.push( materialGroup ); + vertexGroupOffset += vertexLength / 3; + + } else { + + materialDescriptions.push( materialDescription ); + + } + + vertexFa.set( rawObjectDescription.vertices, vertexBAOffset ); + vertexBAOffset += vertexLength; + + if ( normalFA ) { + + normalFA.set( rawObjectDescription.normals, normalOffset ); + normalOffset += rawObjectDescription.normals.length; + + } + if ( uvFA ) { + + uvFA.set( rawObjectDescription.uvs, uvOffset ); + uvOffset += rawObjectDescription.uvs.length; + + } + if ( this.debug ) this.printReport( rawObjectDescription, selectedMaterialIndex ); + + } + + self.postMessage( { + cmd: 'objData', + meshName: rawObjectDescription.objectName, + multiMaterial: createMultiMaterial, + materialDescriptions: materialDescriptions, + materialGroups: materialGroups, + vertices: vertexFa, + normals: normalFA, + uvs: uvFA + }, [ vertexFa.buffer ], normalFA !== null ? [ normalFA.buffer ] : null, uvFA !== null ? [ uvFA.buffer ] : null ); + + this.globalObjectCount++; + }; + + return MeshCreator; + })(); + + var wwLoaderRunnerDef = (function () { + + function OBJLoaderRunner() { + self.addEventListener( 'message', this.runner, false ); + } + + OBJLoaderRunner.prototype.runner = function ( event ) { + var payload = event.data; + + console.log( 'Command state before: ' + THREE.OBJLoader2.WW.OBJLoaderRef.cmdState ); + + switch ( payload.cmd ) { + case 'init': + + THREE.OBJLoader2.WW.OBJLoaderRef.init( payload ); + break; + + case 'setMaterials': + + THREE.OBJLoader2.WW.OBJLoaderRef.setMaterials( payload ); + break; + + case 'run': + + THREE.OBJLoader2.WW.OBJLoaderRef.run( payload ); + break; + + default: + + console.error( 'OBJLoader: Received unknown command: ' + payload.cmd ); + break; + + } + + console.log( 'Command state after: ' + THREE.OBJLoader2.WW.OBJLoaderRef.cmdState ); + }; + + return OBJLoaderRunner; + })(); + + var buildObject = function ( fullName, object ) { + var objectString = fullName + ' = {\n'; + var part; + for ( var name in object ) { + + part = object[ name ]; + if ( typeof( part ) === 'string' || part instanceof String ) { + + part = part.replace( '\n', '\\n' ); + part = part.replace( '\r', '\\r' ); + objectString += '\t' + name + ': "' + part + '",\n'; + + } else if ( part instanceof Array ) { + + objectString += '\t' + name + ': [' + part + '],\n'; + + } else if ( Number.isInteger( part ) ) { + + objectString += '\t' + name + ': ' + part + ',\n'; + + } else if ( typeof part === 'function' ) { + + objectString += '\t' + name + ': ' + part + ',\n'; + + } + + } + objectString += '}\n\n'; + + return objectString; + }; + + var buildSingelton = function ( fullName, internalName, object ) { + var objectString = fullName + ' = (function () {\n\n'; + + var constructorString = object.prototype.constructor.toString(); + constructorString = constructorString.replace( /function\s[a-z]/g, 'function ' + internalName ); + objectString += '\t' + constructorString + '\n\n'; + + var funcString; + var objectPart; + for ( var name in object.prototype ) { + + objectPart = object.prototype[ name ]; + if ( typeof objectPart === 'function' ) { + + funcString = objectPart.toString(); + funcString = funcString.replace( /new\s[a-z]/g, 'new ' + internalName ); + objectString += '\t' + internalName + '.prototype.' + name + ' = ' + funcString + ';\n\n'; + + } + + } + objectString += '\treturn ' + internalName + ';\n'; + objectString += '})();\n\n'; + + return objectString; + }; + + this.workerCode = ''; + this.workerCode += '/**\n'; + this.workerCode += ' * This code was constructed by \n'; + this.workerCode += ' */\n\n'; + this.workerCode += 'var THREE = {\n'; + this.workerCode += '\tOBJLoader2: {\n'; + this.workerCode += '\t\tWW: {\n'; + this.workerCode += '\t\t}\n'; + this.workerCode += '\t}\n'; + this.workerCode += '};\n\n'; + + // parser re-construction + this.workerCode += 'if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }\n\n'; + this.workerCode += buildObject( 'THREE.OBJLoader2.consts', THREE.OBJLoader2.consts ); + this.workerCode += buildSingelton( 'THREE.OBJLoader2.Parser', 'Parser', THREE.OBJLoader2.Parser ); + this.workerCode += buildSingelton( 'THREE.OBJLoader2._RawObject', '_RawObject', THREE.OBJLoader2._RawObject ); + this.workerCode += buildSingelton( 'THREE.OBJLoader2.RawObjectDescription', 'RawObjectDescription', THREE.OBJLoader2.RawObjectDescription ); + + // web worker construction + this.workerCode += buildSingelton( 'THREE.OBJLoader2.WW.OBJLoader', 'OBJLoader', wwDef ); + this.workerCode += buildSingelton( 'THREE.OBJLoader2.WW.MeshCreator', 'MeshCreator', wwMeshCreatorDef ); + this.workerCode += 'THREE.OBJLoader2.WW.OBJLoaderRef = new THREE.OBJLoader2.WW.OBJLoader();\n\n'; + this.workerCode += buildSingelton( 'THREE.OBJLoader2.WW.OBJLoaderRunner', 'OBJLoaderRunner', wwLoaderRunnerDef ); + this.workerCode += 'new THREE.OBJLoader2.WW.OBJLoaderRunner();\n\n'; + + console.timeEnd( 'buildWebWorkerCode' ); + } + + return this.workerCode; + }; + + return WWOBJLoader2; + +})(); + +/** + * Instruction to configure {@link THREE.OBJLoader2.WWOBJLoader2}.prepareRun to load OBJ from given ArrayBuffer and MTL from given String + * + * @param {string} modelName Overall name of the model + * @param {Uint8Array} objAsArrayBuffer OBJ file content as ArrayBuffer + * @param {string} pathTexture Path to texture files + * @param {string} mtlAsString MTL file content as string + * @param {THREE.Object3D} sceneGraphBaseNode {@link THREE.Object3D} where meshes will be attached + * @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, requestTerminate: boolean}} + * @constructor + */ +THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer = function ( modelName, objAsArrayBuffer, pathTexture, mtlAsString, sceneGraphBaseNode, requestTerminate ) { + + var data = { + modelName: ( modelName == null ) ? 'none' : modelName, + dataAvailable: true, + objAsArrayBuffer: ( objAsArrayBuffer == null ) ? null : objAsArrayBuffer, + pathTexture: ( pathTexture == null ) ? null : pathTexture, + mtlAsString: ( mtlAsString == null ) ? null : mtlAsString, + sceneGraphBaseNode: ( sceneGraphBaseNode == null ) ? null : sceneGraphBaseNode, + requestTerminate: ( requestTerminate == null ) ? false : requestTerminate + }; + + return data; +}; + +/** + * 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} [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, requestTerminate: boolean}} + * @constructor + */ +THREE.OBJLoader2.WWOBJLoader2.PrepDataFile = function ( modelName, pathObj, fileObj, pathTexture, fileMtl, sceneGraphBaseNode, requestTerminate ) { + + var data = { + modelName: ( modelName == null ) ? 'none' : modelName, + 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, + requestTerminate: ( requestTerminate == null ) ? false : requestTerminate + }; + + return data; +}; +/** + * Orchestrate loading of multiple OBJ files/data from an instruction queue with a configurable amount of workers (1-16). + * Use: + * prepareWorkers + * enqueueForRun + * processQueue + * deregister + * + * @class + */ +THREE.OBJLoader2.WWOBJLoader2Director = (function () { + + var MAX_WEB_WORKER = 16; + var MAX_QUEUE_SIZE = 1024; + + function WWOBJLoader2Director() { + this.maxQueueSize = MAX_QUEUE_SIZE ; + this.maxWebWorkers = MAX_WEB_WORKER; + + this.workerDescription = { + prototypeDef: THREE.OBJLoader2.WWOBJLoader2.prototype, + callbacks: {}, + webWorkers: [], + codeBuffer: null + }; + this.objectsCompleted = 0; + this.instructionQueue = []; + } + + /** + * Returns the maximum length of the instruction queue. + * @memberOf THREE.OBJLoader2.WWOBJLoader2Director + * + * @returns {number} + */ + WWOBJLoader2Director.prototype.getMaxQueueSize = function () { + return this.maxQueueSize; + }; + + /** + * Returns the maximum number of workers. + * @memberOf THREE.OBJLoader2.WWOBJLoader2Director + * + * @returns {number} + */ + WWOBJLoader2Director.prototype.getMaxWebWorkers = function () { + return this.maxWebWorkers; + }; + + /** + * 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 {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 ]; + + } + + } + + this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE ); + this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER ); + this.objectsCompleted = 0; + this.instructionQueue = []; + + var start = this.workerDescription.webWorkers.length; + if ( start < this.maxWebWorkers ) { + + for ( i = start; i < this.maxWebWorkers; i ++ ) { + + webWorker = this._buildWebWorker(); + this.workerDescription.webWorkers[ i ] = webWorker; + + } + + } else { + + for ( var webWorker, i = start - 1; i >= this.maxWebWorkers; i-- ) { + + webWorker = this.workerDescription.webWorkers[ i ]; + webWorker.setRequestTerminate( true ); + + this.workerDescription.webWorkers.pop(); + + } + + } + }; + + /** + * Store run instructions in internal instructionQueue + * @memberOf THREE.OBJLoader2.WWOBJLoader2Director + * + * @param {Object} params Either {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer} or {@link THREE.OBJLoader2.WWOBJLoader2.PrepDataFile} + */ + WWOBJLoader2Director.prototype.enqueueForRun = function ( runParams ) { + if ( this.instructionQueue.length < this.maxQueueSize ) { + this.instructionQueue.push( runParams ); + } + }; + + /** + * Process the instructionQueue until it is depleted + * @memberOf THREE.OBJLoader2.WWOBJLoader2Director + */ + WWOBJLoader2Director.prototype.processQueue = function () { + if ( this.instructionQueue.length === 0 ) return; + + var 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.instructionQueue.shift(); + + } + }; + + WWOBJLoader2Director.prototype._buildWebWorker = function () { + var webWorker = Object.create( this.workerDescription.prototypeDef ); + webWorker._init(); + + // Ensure code string is built once and then it is just passed on to every new instance + if ( this.workerDescription.codeBuffer == null ) { + + this.workerDescription.codeBuffer = webWorker._buildWebWorkerCode(); + + } else { + + webWorker._buildWebWorkerCode( this.workerDescription.codeBuffer ); + + } + for ( var key in this.workerDescription.callbacks ) { + + if ( webWorker.callbacks.hasOwnProperty( key ) && this.workerDescription.callbacks.hasOwnProperty( key ) ) { + + webWorker.callbacks[ key ] = this.workerDescription.callbacks[ key ]; + + } + + } + var scope = this; + var managerCompletedLoading = function ( modelName, instanceNo, requestTerminate ) { + scope.objectsCompleted++; + if ( ! requestTerminate ) { + + var rekick = scope.workerDescription.webWorkers[ instanceNo ]; + var runParams = scope.instructionQueue[ 0 ]; + if ( runParams != null ) { + + rekick.prepareRun( runParams ); + rekick.run(); + scope.instructionQueue.shift(); + + } + + } + }; + + webWorker.callbacks.director[ 'completedLoading' ] = managerCompletedLoading; + webWorker.instanceNo = this.workerDescription.webWorkers.length; + this.workerDescription.webWorkers.push( webWorker ); + return webWorker; + }; + + /** + * Terminate all workers + * @memberOf THREE.OBJLoader2.WWOBJLoader2Director + */ + WWOBJLoader2Director.prototype.deregister = function () { + console.log( 'WWOBJLoader2Director received the unregister call. Terminating all workers!' ); + for ( var i = 0, webWorker, length = this.workerDescription.webWorkers.length; i < length; i++ ) { + + webWorker = this.workerDescription.webWorkers[ i ]; + webWorker.setRequestTerminate( true ); + + } + this.workerDescription.callbacks = {}; + this.workerDescription.webWorkers = []; + this.workerDescription.codeBuffer = null; + }; + + return WWOBJLoader2Director; + +})(); diff --git a/examples/js/loaders/wwobjloader2/WWOBJLoader2.min.js b/examples/js/loaders/wwobjloader2/WWOBJLoader2.min.js new file mode 100644 index 00000000000000..5783505a5f3f3b --- /dev/null +++ b/examples/js/loaders/wwobjloader2/WWOBJLoader2.min.js @@ -0,0 +1 @@ +"use strict";THREE.OBJLoader2.WWOBJLoader2=function(){function e(){this._init()}return e.prototype._init=function(){if(void 0===window.Worker)throw"This browser does not support web workers!";this.instanceNo=0,this.worker=null,this.workerCode=null,this.debug=!1,this.sceneGraphBaseNode=null,this.modelName="none",this.validated=!1,this.running=!1,this.requestTerminate=!1,this.callbacks={progress:null,completedLoading:null,errorWhileLoading:null,materialsLoaded:null,meshLoaded:null,director:{completedLoading:null,errorWhileLoading:null}},this.manager=THREE.DefaultLoadingManager,this.fileLoader=new THREE.FileLoader(this.manager),this.mtlLoader=null,this.dataAvailable=!1,this.objAsArrayBuffer=null,this.fileObj=null,this.pathObj=null,this.fileMtl=null,this.mtlAsString=null,this.texturePath=null,this.materials=[],this.counter=0},e.prototype.setDebug=function(e){this.debug=e},e.prototype.registerCallbackProgress=function(e){null!=e&&(this.callbacks.progress=e)},e.prototype.registerCallbackCompletedLoading=function(e){null!=e&&(this.callbacks.completedLoading=e)},e.prototype.registerCallbackMaterialsLoaded=function(e){null!=e&&(this.callbacks.materialsLoaded=e)},e.prototype.registerCallbackMeshLoaded=function(e){null!=e&&(this.callbacks.meshLoaded=e)},e.prototype.registerCallbackErrorWhileLoading=function(e){null!=e&&(this.callbacks.errorWhileLoading=e)},e.prototype.setRequestTerminate=function(e){this.requestTerminate=!(null==e||!e)},e.prototype._validate=function(){if(!this.validated){if(null==this.worker){this._buildWebWorkerCode();var e=new Blob([this.workerCode],{type:"text/plain"});this.worker=new Worker(window.URL.createObjectURL(e));var t=this,r=function(e){t._receiveWorkerMessage(e)};this.worker.addEventListener("message",r,!1)}this.sceneGraphBaseNode=null,this.modelName="none",this.validated=!0,this.running=!0,this.requestTerminate=!1,this.fileLoader=null==this.fileLoader?new THREE.FileLoader(this.manager):this.fileLoader,this.mtlLoader=null==this.mtlLoader?new THREE.MTLLoader:this.mtlLoader,this.dataAvailable=!1,this.fileObj=null,this.pathObj=null,this.fileMtl=null,this.texturePath=null,this.objAsArrayBuffer=null,this.mtlAsString=null,this.materials=[];var i=new THREE.MeshStandardMaterial({color:14479871});i.name="defaultMaterial",this.materials[i.name]=i,this.counter=0}},e.prototype.prepareRun=function(e){if(this._validate(),this.dataAvailable=e.dataAvailable,this.modelName=e.modelName,console.time("WWOBJLoader2"),this.dataAvailable){if(!(e.objAsArrayBuffer instanceof ArrayBuffer||e.objAsArrayBuffer instanceof Uint8Array))throw"Provided input is not of type arraybuffer! Aborting...";this.worker.postMessage({cmd:"init",debug:this.debug}),this.objAsArrayBuffer=e.objAsArrayBuffer,this.mtlAsString=e.mtlAsString}else{if(!("string"==typeof e.fileObj||e.fileObj instanceof String))throw"Provided file is not properly defined! Aborting...";this.worker.postMessage({cmd:"init",debug:this.debug}),this.fileObj=e.fileObj,this.pathObj=e.pathObj,this.fileMtl=e.fileMtl}this.setRequestTerminate(e.requestTerminate),this.pathTexture=e.pathTexture,this.sceneGraphBaseNode=e.sceneGraphBaseNode},e.prototype.run=function(){var e=this,t=function(t){var r=[],i=[];if(null!=t){t.preload(),r=t.materials;for(var o in r)r.hasOwnProperty(o)&&(i.push(o),e.materials[o]=r[o])}if(e.worker.postMessage({cmd:"setMaterials",materialNames:i}),null!=e.callbacks.materialsLoaded){var a=e.callbacks.materialsLoaded(e.materials);null!=a&&(e.materials=a)}if(e.dataAvailable&&e.objAsArrayBuffer)e.worker.postMessage({cmd:"run",objAsArrayBuffer:e.objAsArrayBuffer},[e.objAsArrayBuffer.buffer]);else{var s,n=0,l=0,u=function(t){e._announceProgress("Running web worker!"),e.objAsArrayBuffer=new Uint8Array(t),e.worker.postMessage({cmd:"run",objAsArrayBuffer:e.objAsArrayBuffer},[e.objAsArrayBuffer.buffer])},h=function(t){t.lengthComputable&&(l=Math.round(t.loaded/t.total*100),l>n&&(n=l,s='Download of "'+e.fileObj+'": '+l+"%",console.log(s),e._announceProgress(s)))},d=function(t){s='Error occurred while downloading "'+e.fileObj+'"',console.error(s+": "+t),e._announceProgress(s),e._finalize("error")};e.fileLoader.setPath(e.pathObj),e.fileLoader.setResponseType("arraybuffer"),e.fileLoader.load(e.fileObj,u,h,d)}console.timeEnd("Loading MTL textures")};this.dataAvailable?t(null!=this.mtlAsString?this.mtlLoader.parse(this.mtlAsString):null):null==this.fileMtl?t():(this.mtlLoader.setPath(this.pathTexture),this.mtlLoader.load(this.fileMtl,t))},e.prototype._receiveWorkerMessage=function(e){var t=e.data;switch(t.cmd){case"objData":this.counter++;var r=new THREE.BufferGeometry;r.addAttribute("position",new THREE.BufferAttribute(new Float32Array(t.vertices),3)),null!==t.normals?r.addAttribute("normal",new THREE.BufferAttribute(new Float32Array(t.normals),3)):r.computeVertexNormals(),null!==t.uvs&&r.addAttribute("uv",new THREE.BufferAttribute(new Float32Array(t.uvs),2));var i,o,a,s=t.materialDescriptions,n=t.multiMaterial,l=[];for(var u in s){if(i=s[u],o=this.materials[i.name],i.default)o=this.materials.defaultMaterial;else if(i.clone){a=o.name+"_flat";var h=this.materials[a];h||(h=o.clone(),h.name=a,h.shading=THREE.FlatShading,this.materials[a]=name)}else o||(o=this.materials.defaultMaterial);n&&l.push(o)}if(n){o=new THREE.MultiMaterial(l);var d,c=t.materialGroups;for(var u in c)d=c[u],r.addGroup(d.start,d.count,d.index)}if(null!==this.callbacks.meshLoaded){var f=this.callbacks.meshLoaded(t.meshName,o);null!=f&&(o=f)}var p=new THREE.Mesh(r,o);p.name=t.meshName,this.sceneGraphBaseNode.add(p);var m="("+this.counter+"): "+t.meshName;this._announceProgress("Adding mesh",m);break;case"complete":console.timeEnd("WWOBJLoader2"),null!=t.msg?this._announceProgress(t.msg):this._announceProgress(""),this._finalize("complete");break;case"report_progress":this._announceProgress("",t.output);break;default:console.error("Received unknown command: "+t.cmd)}},e.prototype._terminate=function(){if(null!=this.worker){if(this.running)throw"Unable to gracefully terminate worker as it is currently running!";this.worker.terminate(),this.worker=null,this.workerCode=null,this._finalize("terminate")}this.fileLoader=null,this.mtlLoader=null},e.prototype._finalize=function(e,t){this.running=!1,"complete"===e?(null!=this.callbacks.completedLoading&&this.callbacks.completedLoading(this.modelName,this.instanceNo,this.requestTerminate),null!=this.callbacks.director.completedLoading&&this.callbacks.director.completedLoading(this.modelName,this.instanceNo,this.requestTerminate)):"error"===e&&(null!=this.callbacks.errorWhileLoading&&this.callbacks.errorWhileLoading(this.modelName,this.instanceNo,this.requestTerminate),null!=this.callbacks.director.errorWhileLoading&&this.callbacks.director.errorWhileLoading(this.modelName,this.instanceNo,this.requestTerminate)),this.validated=!1,this.setRequestTerminate(t),this.requestTerminate&&this._terminate()},e.prototype._announceProgress=function(e,t){var r="";null!==e&&void 0!==e&&(r=e),null!==t&&void 0!==t&&(r=r+" "+t),null!==this.callbacks.progress&&this.callbacks.progress(r),this.debug&&console.log(r)},e.prototype._buildWebWorkerCode=function(e){if(null!=e&&(this.workerCode=e),null==this.workerCode){console.time("buildWebWorkerCode");var t=function(){function e(){this.meshCreator=new THREE.OBJLoader2.WW.MeshCreator,this.parser=new THREE.OBJLoader2.Parser(this.meshCreator),this.validated=!1,this.cmdState="created",this.debug=!1}return e.prototype._setDebug=function(e,t){this.parser._setDebug(e),this.meshCreator._setDebug(t)},e.prototype.parse=function(e){console.log("Parsing arrayBuffer..."),console.time("parseArrayBuffer"),this._validate(),this.parser.parseArrayBuffer(e);var t=this._finalize();return console.timeEnd("parseArrayBuffer"),t},e.prototype._validate=function(){this.validated||(this.parser._validate(),this.meshCreator._validate(),this.validated=!0)},e.prototype._finalize=function(){console.log("Global output object count: "+this.meshCreator.globalObjectCount),this.parser._finalize(),this.meshCreator._finalize(),this.validated=!1},e.prototype.init=function(e){this.cmdState="init",this._setDebug(e.debug,e.debug)},e.prototype.setMaterials=function(e){this.cmdState="setMaterials",this.meshCreator._setMaterials(e.materialNames)},e.prototype.run=function(e){this.cmdState="run",this.parse(e.objAsArrayBuffer),console.log("OBJ loading complete!"),this.cmdState="complete",self.postMessage({cmd:this.cmdState,msg:null})},e}(),r=function(){function e(){this.materials=null,this.debug=!1,this.globalObjectCount=1,this.validated=!1}return e.prototype._setMaterials=function(e){this.materials=null==e?null==this.materials?{materials:[]}:this.materials:e},e.prototype._setDebug=function(e){this.debug=null==e?this.debug:e},e.prototype._validate=function(){this.validated||(this._setMaterials(null),this._setDebug(null),this.globalObjectCount=1)},e.prototype._finalize=function(){this.materials=null,this.validated=!1},e.prototype.buildMesh=function(e,t,r,i,o){this.debug&&console.log("OBJLoader.buildMesh:\nInput object no.: "+t);var a,s,n,l,u,h=new Float32Array(r),d=i>0?new Float32Array(i):null,c=o>0?new Float32Array(o):null,f=[],p=e.length>1,m=0,b=[],g=[],k=0,w=0,L=0,W=0;for(var y in e)a=e[y],s={name:a.materialName,flat:!1,default:!1},null===this.materials[s.name]&&(s.default=!0,console.warn('object_group "'+a.objectName+"_"+a.groupName+'" was defined without material! Assigning "defaultMaterial".')),0===a.smoothingGroup&&(s.flat=!0),u=a.vertices.length,p?(n=b[s.name],n||(n=m,b[s.name]=m,f.push(s),m++),l={start:w,count:u/3,index:n},g.push(l),w+=u/3):f.push(s),h.set(a.vertices,k),k+=u,d&&(d.set(a.normals,L),L+=a.normals.length),c&&(c.set(a.uvs,W),W+=a.uvs.length),this.debug&&this.printReport(a,n);self.postMessage({cmd:"objData",meshName:a.objectName,multiMaterial:p,materialDescriptions:f,materialGroups:g,vertices:h,normals:d,uvs:c},[h.buffer],null!==d?[d.buffer]:null,null!==c?[c.buffer]:null),this.globalObjectCount++},e}(),i=function(){function e(){self.addEventListener("message",this.runner,!1)}return e.prototype.runner=function(e){var t=e.data;switch(console.log("Command state before: "+THREE.OBJLoader2.WW.OBJLoaderRef.cmdState),t.cmd){case"init":THREE.OBJLoader2.WW.OBJLoaderRef.init(t);break;case"setMaterials":THREE.OBJLoader2.WW.OBJLoaderRef.setMaterials(t);break;case"run":THREE.OBJLoader2.WW.OBJLoaderRef.run(t);break;default:console.error("OBJLoader: Received unknown command: "+t.cmd)}console.log("Command state after: "+THREE.OBJLoader2.WW.OBJLoaderRef.cmdState)},e}(),o=function(e,t){var r,i=e+" = {\n";for(var o in t)r=t[o],"string"==typeof r||r instanceof String?(r=r.replace("\n","\\n"),r=r.replace("\r","\\r"),i+="\t"+o+': "'+r+'",\n'):r instanceof Array?i+="\t"+o+": ["+r+"],\n":Number.isInteger(r)?i+="\t"+o+": "+r+",\n":"function"==typeof r&&(i+="\t"+o+": "+r+",\n");return i+="}\n\n"},a=function(e,t,r){var i=e+" = (function () {\n\n",o=r.prototype.constructor.toString();o=o.replace(/function\s[a-z]/g,"function "+t),i+="\t"+o+"\n\n";var a,s;for(var n in r.prototype)s=r.prototype[n],"function"==typeof s&&(a=s.toString(),a=a.replace(/new\s[a-z]/g,"new "+t),i+="\t"+t+".prototype."+n+" = "+a+";\n\n");return i+="\treturn "+t+";\n",i+="})();\n\n"};this.workerCode="",this.workerCode+="/**\n",this.workerCode+=" * This code was constructed by \n",this.workerCode+=" */\n\n",this.workerCode+="var THREE = {\n",this.workerCode+="\tOBJLoader2: {\n",this.workerCode+="\t\tWW: {\n",this.workerCode+="\t\t}\n",this.workerCode+="\t}\n",this.workerCode+="};\n\n",this.workerCode+="if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }\n\n",this.workerCode+=o("THREE.OBJLoader2.consts",THREE.OBJLoader2.consts),this.workerCode+=a("THREE.OBJLoader2.Parser","Parser",THREE.OBJLoader2.Parser),this.workerCode+=a("THREE.OBJLoader2._RawObject","_RawObject",THREE.OBJLoader2._RawObject),this.workerCode+=a("THREE.OBJLoader2.RawObjectDescription","RawObjectDescription",THREE.OBJLoader2.RawObjectDescription),this.workerCode+=a("THREE.OBJLoader2.WW.OBJLoader","OBJLoader",t),this.workerCode+=a("THREE.OBJLoader2.WW.MeshCreator","MeshCreator",r),this.workerCode+="THREE.OBJLoader2.WW.OBJLoaderRef = new THREE.OBJLoader2.WW.OBJLoader();\n\n",this.workerCode+=a("THREE.OBJLoader2.WW.OBJLoaderRunner","OBJLoaderRunner",i),this.workerCode+="new THREE.OBJLoader2.WW.OBJLoaderRunner();\n\n",console.timeEnd("buildWebWorkerCode")}return this.workerCode},e}(),THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer=function(e,t,r,i,o,a){var s={modelName:null==e?"none":e,dataAvailable:!0,objAsArrayBuffer:null==t?null:t,pathTexture:null==r?null:r,mtlAsString:null==i?null:i,sceneGraphBaseNode:null==o?null:o,requestTerminate:null!=a&&a};return s},THREE.OBJLoader2.WWOBJLoader2.PrepDataFile=function(e,t,r,i,o,a,s){var n={modelName:null==e?"none":e,dataAvailable:!1,pathObj:null==t?null:t,fileObj:null==r?null:r,pathTexture:null==i?null:i,fileMtl:null==o?null:o,sceneGraphBaseNode:null==a?null:a,requestTerminate:null!=s&&s};return n},THREE.OBJLoader2.WWOBJLoader2Director=function(){function e(){this.maxQueueSize=r,this.maxWebWorkers=t,this.workerDescription={prototypeDef:THREE.OBJLoader2.WWOBJLoader2.prototype,callbacks:{},webWorkers:[],codeBuffer:null},this.objectsCompleted=0,this.instructionQueue=[]}var t=16,r=1024;return e.prototype.getMaxQueueSize=function(){return this.maxQueueSize},e.prototype.getMaxWebWorkers=function(){return this.maxWebWorkers},e.prototype.prepareWorkers=function(e,i,o){if(null!=e)for(var a in e)e.hasOwnProperty(a)&&(this.workerDescription.callbacks[a]=e[a]);this.maxQueueSize=Math.min(i,r),this.maxWebWorkers=Math.min(o,t),this.objectsCompleted=0,this.instructionQueue=[];var s=this.workerDescription.webWorkers.length;if(s=this.maxWebWorkers;l--)n=this.workerDescription.webWorkers[l],n.setRequestTerminate(!0),this.workerDescription.webWorkers.pop()},e.prototype.enqueueForRun=function(e){this.instructionQueue.length + +OBJLoader2 Testbed + + + + + +
+ three.js - OBJLoader2 direct loader test +
+
+
+ + +
+ + + + + + + + diff --git a/examples/webgl_loader_wwobj2.html b/examples/webgl_loader_wwobj2.html new file mode 100644 index 00000000000000..b200cc32698cae --- /dev/null +++ b/examples/webgl_loader_wwobj2.html @@ -0,0 +1,326 @@ + + +OBJLoader2 Testbed + + + + + +
+ three.js - OBJLoader2 direct loader test +
+
+
+ + +
+ + + + + + + + + diff --git a/examples/webgl_loader_wwobj2_parallels.html b/examples/webgl_loader_wwobj2_parallels.html new file mode 100644 index 00000000000000..dae0636574d956 --- /dev/null +++ b/examples/webgl_loader_wwobj2_parallels.html @@ -0,0 +1,411 @@ + + +Web Worker Parallel Demo + + + + + +
+ +
+
+ three.js - WWDirector Parallels Demo +
+
+ +
+
+
+ + + + + + + + + +