From f01e50f561fa857f42b1ff24b1fbb577c40488b6 Mon Sep 17 00:00:00 2001 From: Michael Aschauer Date: Mon, 22 Oct 2018 19:06:22 +0200 Subject: [PATCH] add missing files --- stitchcode/assets/turtle.mtl | 12 + stitchcode/three-newer/MTLLoader.js | 568 ++++++++++++++++++++ stitchcode/three-newer/OBJLoader.js | 793 ++++++++++++++++++++++++++++ 3 files changed, 1373 insertions(+) create mode 100644 stitchcode/assets/turtle.mtl create mode 100644 stitchcode/three-newer/MTLLoader.js create mode 100644 stitchcode/three-newer/OBJLoader.js diff --git a/stitchcode/assets/turtle.mtl b/stitchcode/assets/turtle.mtl new file mode 100644 index 00000000..2464ea33 --- /dev/null +++ b/stitchcode/assets/turtle.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'turtle.obj' +# Material Count: 1 + +newmtl SVGMat +Ns 96.078431 +Ka 1.000000 1.000000 1.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.7 +illum 2 diff --git a/stitchcode/three-newer/MTLLoader.js b/stitchcode/three-newer/MTLLoader.js new file mode 100644 index 00000000..9c39b245 --- /dev/null +++ b/stitchcode/three-newer/MTLLoader.js @@ -0,0 +1,568 @@ +/** + * Loads a Wavefront .mtl file specifying materials + * + * @author angelxuanchang + */ + +THREE.MTLLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.MTLLoader.prototype = { + + constructor: THREE.MTLLoader, + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; + + var loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text, path ) ); + + }, onProgress, onError ); + + }, + + /** + * Set base path for resolving references. + * If set this path will be prepended to each loaded and found reference. + * + * @see setResourcePath + * @param {String} path + * @return {THREE.MTLLoader} + * + * @example + * mtlLoader.setPath( 'assets/obj/' ); + * mtlLoader.load( 'my.mtl', ... ); + */ + setPath: function ( path ) { + + this.path = path; + return this; + + }, + + /** + * Set base path for additional resources like textures. + * + * @see setPath + * @param {String} path + * @return {THREE.MTLLoader} + * + * @example + * mtlLoader.setPath( 'assets/obj/' ); + * mtlLoader.setResourcePath( 'assets/textures/' ); + * mtlLoader.load( 'my.mtl', ... ); + */ + setResourcePath: function ( path ) { + + this.resourcePath = path; + return this; + + }, + + setTexturePath: function ( path ) { + + console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' ); + return this.setResourcePath( path ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setMaterialOptions: function ( value ) { + + this.materialOptions = value; + return this; + + }, + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {THREE.MTLLoader.MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse: function ( text, path ) { + + var lines = text.split( '\n' ); + var info = {}; + var delimiter_pattern = /\s+/; + var materialsInfo = {}; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + var pos = line.indexOf( ' ' ); + + var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' ) { + + var ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +}; + +/** + * Create a new THREE-MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) { + + this.baseUrl = baseUrl || ''; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; + this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; + +}; + +THREE.MTLLoader.MaterialCreator.prototype = { + + constructor: THREE.MTLLoader.MaterialCreator, + + crossOrigin: 'anonymous', + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setManager: function ( value ) { + + this.manager = value; + + }, + + setMaterials: function ( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + }, + + convert: function ( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + var converted = {}; + + for ( var mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + var mat = materialsInfo[ mn ]; + + var covmat = {}; + + converted[ mn ] = covmat; + + for ( var prop in mat ) { + + var save = true; + var value = mat[ prop ]; + var lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + }, + + preload: function () { + + for ( var mn in this.materialsInfo ) { + + this.create( mn ); + + } + + }, + + getIndex: function ( materialName ) { + + return this.nameLookup[ materialName ]; + + }, + + getAsArray: function () { + + var index = 0; + + for ( var mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + }, + + create: function ( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + }, + + createMaterial_: function ( materialName ) { + + // Create material + + var scope = this; + var mat = this.materialsInfo[ materialName ]; + var params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + var texParams = scope.getTextureParams( value, params ); + var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + params[ mapType ] = map; + + } + + for ( var prop in mat ) { + + var value = mat[ prop ]; + var n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new THREE.Color().fromArray( value ); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new THREE.Color().fromArray( value ); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( "map", value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( "specularMap", value ); + + break; + + case 'norm': + + setMapForType( "normalMap", value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( "bumpMap", value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( "alphaMap", value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + }, + + getTextureParams: function ( value, matParams ) { + + var texParams = { + + scale: new THREE.Vector2( 1, 1 ), + offset: new THREE.Vector2( 0, 0 ) + + }; + + var items = value.split( /\s+/ ); + var pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + }, + + loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + + var texture; + var loader = THREE.Loader.Handlers.get( url ); + var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; + + if ( loader === null ) { + + loader = new THREE.TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +}; diff --git a/stitchcode/three-newer/OBJLoader.js b/stitchcode/three-newer/OBJLoader.js new file mode 100644 index 00000000..15b1f90b --- /dev/null +++ b/stitchcode/three-newer/OBJLoader.js @@ -0,0 +1,793 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.OBJLoader = ( function () { + + // o object_name | g group_name + var object_pattern = /^[og]\s*(.+)?/; + // mtllib file_reference + var material_library_pattern = /^mtllib /; + // usemtl material_name + var material_use_pattern = /^usemtl /; + + function ParserState() { + + var state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [] + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + var previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + var material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + var cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + var lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + var declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + var src = this.normals; + var dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addColor: function ( a, b, c ) { + + var src = this.colors; + var dst = this.object.geometry.colors; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addUVLine: function ( a ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + var vLen = this.vertices.length; + + var ia = this.parseVertexIndex( a, vLen ); + var ib = this.parseVertexIndex( b, vLen ); + var ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + + if ( ua !== undefined && ua !== '' ) { + + var uvLen = this.uvs.length; + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + this.addUV( ia, ib, ic ); + + } + + if ( na !== undefined && na !== '' ) { + + // Normals are many times the same. If so, skip function call and parseInt. + var nLen = this.normals.length; + ia = this.parseNormalIndex( na, nLen ); + + ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); + ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } + + if ( this.colors.length > 0 ) { + + this.addColor( ia, ib, ic ); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + var vLen = this.vertices.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + var vLen = this.vertices.length; + var uvLen = this.uvs.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + + } + + // + + function OBJLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + + this.materials = null; + + } + + OBJLoader.prototype = { + + constructor: OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.FileLoader( scope.manager ); + loader.setPath( this.path ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text ) ); + + }, onProgress, onError ); + + }, + + setPath: function ( value ) { + + this.path = value; + + return this; + + }, + + setMaterials: function ( materials ) { + + this.materials = materials; + + return this; + + }, + + parse: function ( text ) { + + console.time( 'OBJLoader' ); + + var state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + var lines = text.split( '\n' ); + var line = '', lineFirstChar = ''; + var lineLength = 0; + var result = []; + + // Faster to just trim left side of the line. Use if available. + var trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( var i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + var data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length === 8 ) { + + state.colors.push( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + + ); + + } + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + var lineData = line.substr( 1 ).trim(); + var vertexData = lineData.split( /\s+/ ); + var faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + + var vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + var vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + var v1 = faceVertices[ 0 ]; + + for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + var v2 = faceVertices[ j ]; + var v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + var lineParts = line.substring( 1 ).trim().split( " " ); + var lineVertices = [], lineUVs = []; + + if ( line.indexOf( "/" ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + + var parts = lineParts[ li ].split( "/" ); + + if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); + + } + + } + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + var lineData = line.substr( 1 ).trim(); + var pointData = lineData.split( " " ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // var name = result[ 0 ].substr( 1 ).trim(); + var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + + state.startObject( name ); + + } else if ( material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * or + * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + var value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + var material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + var container = new THREE.Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + + var object = state.objects[ i ]; + var geometry = object.geometry; + var materials = object.materials; + var isLine = ( geometry.type === 'Line' ); + var isPoints = ( geometry.type === 'Points' ); + var hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); + + } else { + + buffergeometry.computeVertexNormals(); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.uvs.length > 0 ) { + + buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + var createdMaterials = []; + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + var material = undefined; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + + var materialLine = new THREE.LineBasicMaterial(); + materialLine.copy( material ); + materialLine.lights = false; // TOFIX + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { + + var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); + materialLine.copy( material ); + material = materialPoints; + + } + + } + + if ( ! material ) { + + if ( isLine ) { + + material = new THREE.LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new THREE.MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + + } + + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors; + + createdMaterials.push( material ); + + } + + // Create mesh + + var mesh; + + if ( createdMaterials.length > 1 ) { + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + console.timeEnd( 'OBJLoader' ); + + return container; + + } + + }; + + return OBJLoader; + +} )();