[
  {
    "path": "1.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden}\n      /* canvas { width: 100%; height: 100% } */\n      #canvas-frame {\n        border: none;\n        cursor: pointer;\n        width: 100%;\n        height: 600px;\n        background-color: #EEEEEE;\n      }\n    </style>\n    <script src=\"../three.js\"></script>\n</head>\n<body>\n</body>\n</html>\n<script>\n  // 场景\n  let scene = new THREE.Scene()\n  // 物品\n  let geometry = new THREE.BoxGeometry(100, 100 ,100)\n  let material = new THREE.MeshLambertMaterial({color: 0xff0000})\n  // 网格（几何， 材料）\n  let mesh = new THREE.Mesh(geometry, material)\n  scene.add(mesh)\n  // 灯光\n  let light = new THREE.PointLight(0xffffff)\n  light.position.set(300, 400, 200)\n  scene.add(light)\n  // 相机\n  let camera = new THREE.PerspectiveCamera(40, 800/600, 1, 1000)\n  camera.position.set(200, 200, 200)\n  camera.lookAt(scene.position)\n  // 渲染器\n  let renderer = new THREE.WebGLRenderer()\n  renderer.setSize(800, 600)\n  document.body.appendChild(renderer.domElement)\n  // 渲染（场景， 相机）\n  renderer.render(scene, camera)\n</script>"
  },
  {
    "path": "2.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden}\n      /* canvas { width: 100%; height: 100% } */\n    </style>\n    <script src=\"../three.js\"></script>\n    <script src=\"./OrbitControls.js\"></script>\n</head>\n<body>\n</body>\n</html>\n<script>\n  // 场景\n  let scene = new THREE.Scene()\n  // 物品\n  let geometry = new THREE.BoxGeometry(100, 100 ,100)\n  let material = new THREE.MeshLambertMaterial({color: 0xff0000})\n  // 网格（几何， 材料）\n  let mesh = new THREE.Mesh(geometry, material)\n  scene.add(mesh)\n  // 灯光\n  let light = new THREE.PointLight(0xffffff)\n  light.position.set(300, 400, 200)\n  scene.add(light)\n  scene.add(new THREE.AmbientLight(0x333333))\n  // 相机\n  let camera = new THREE.PerspectiveCamera(40, 800/600, 1, 1000)\n  camera.position.set(200, 200, 200)\n  camera.lookAt(scene.position)\n  // 渲染器\n  let renderer = new THREE.WebGLRenderer()\n  renderer.setSize(800, 600)\n  document.body.appendChild(renderer.domElement)\n  // 渲染（场景， 相机\n  let render = () => {\n    renderer.render(scene, camera)\n  }\n  render()\n  let controls = new THREE.OrbitControls(camera)\n  controls.addEventListener('change', render)\n</script>"
  },
  {
    "path": "3.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden;}\n      html, body {height: 100%}\n      /* canvas { width: 100%; height: 100% } */\n    </style>\n    <script src=\"../three.min.js\"></script>\n    <script src=\"./OrbitControls.js\"></script>\n    <script src=\"./OBJLoader.js\"></script>\n    <script src=\"./MTLLoader.js\"></script>\n    <script src=\"./stats.min.js\"></script>\n</head>\n<body>\n    <div id=\"container\"></div>\n</body>\n</html>\n<script>\n  var container = document.getElementById( 'container' )\n  stats = new Stats()\n  container.appendChild( stats.dom )\n  // 场景\n  let scene = new THREE.Scene()\n  // 物品\n  let geometry = new THREE.BoxGeometry(100, 100 ,100)\n  let material = new THREE.MeshLambertMaterial({color: 0xff0000})\n  // 网格（几何， 材料）\n  let mesh = new THREE.Mesh(geometry, material)\n  // scene.add(mesh)\n  var materials = []\n  for (var i = 1; i < 7; ++i) {\n    materials.push(new THREE.MeshBasicMaterial({\n      map: THREE.ImageUtils.loadTexture('./img/' + i + '.jpg',//图片的路径  \n      {}, function() {\n        renderer.render(scene, camera)\n      }),\n      overdraw: true  \n    }))\n  }\n  function createMesh(geom, imageFile) {\n    var texture = THREE.ImageUtils.loadTexture(imageFile)\n    var mat = new THREE.MeshPhongMaterial()\n    mat.map = texture  // 材质的Map属性用于添加纹理\n    var mesh = new THREE.Mesh(geom, mat)\n    return mesh\n  }\n  var sphere = createMesh(new THREE.SphereGeometry(50, 50, 50), './img/1.jpg');\n  scene.add(sphere)\n  var cube = new THREE.Mesh(new THREE.CubeGeometry(50, 50, 50),  new THREE.MeshFaceMaterial(materials))\n  // scene.add(cube)\n  var mtlLoader = new THREE.MTLLoader()\n  mtlLoader.setPath( './obj/girl/' )\n  mtlLoader.load( 'obj.mtl', function( materials ) {\n    materials.preload()\n    var objLoader = new THREE.OBJLoader()\n    objLoader.setMaterials( materials )\n    objLoader.setPath( './obj/girl/' )\n    objLoader.load( 'obj.obj', function ( object ) {\n      console.log(object)\n      object.scale.x = 0.2\n      object.scale.y = 0.2\n      object.scale.z = 0.2\n      // setInterval(() => {\n      //   object.position.y -=1\n      // }, 100)\n      scene.add( object )\n    } )\n  })\n  // scene.background = new THREE.CubeTextureLoader()\n  //   .setPath( './obj/pisa/' )\n  //   .load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] )\n\n  // 灯光\n  // let light = new THREE.PointLight(0xffffff)\n  // light.position.set(300, 400, 200)\n  // scene.add(light)\n  scene.add(new THREE.AmbientLight(0xffffff, 1))\n  // 相机\n  let camera = new THREE.PerspectiveCamera(45, document.body.clientWidth/document.body.clientHeight, 0.1, 1000)\n  camera.position.set(document.body.clientWidth/2, document.body.clientHeight/2, 200)\n  camera.lookAt(scene.position)\n  // 渲染器\n  let renderer = new THREE.WebGLRenderer()\n  renderer.setSize(document.body.clientWidth  , document.body.clientHeight)\n  document.body.appendChild(renderer.domElement)\n  // 渲染（场景， 相机\n  let render = () => {\n    renderer.render(scene, camera)\n  }\n  render()\n  let controls = new THREE.OrbitControls(camera)\n  controls.addEventListener('change', render)\n  let animate = () => {\n    requestAnimationFrame(animate)\n    stats.update()\n    render()\n  }\n  animate()\n  var helper = new THREE.GridHelper( 500, 50 )\n  scene.add( helper )\n</script>"
  },
  {
    "path": "4.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>我会让你眼晕，哈哈哈</title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden;}\n      html, body {height: 100%}\n      /* canvas { width: 100%; height: 100% } */\n    </style>\n    <script src=\"./three.min.js\"></script>\n    <script src=\"./OrbitControls.js\"></script>\n    <script src=\"./stats.min.js\"></script>\n</head>\n<body>\n    <div id=\"container\"></div>\n</body>\n</html>\n<script>\n  // fps 监测\n  var container = document.getElementById( 'container' )\n  stats = new Stats()\n  container.appendChild( stats.dom )\n  // 场景 环境光\n  let scene = new THREE.Scene()\n  scene.add(new THREE.AmbientLight(0xffffff, 1))\n  // 相机\n  let camera = new THREE.PerspectiveCamera(45, document.body.clientWidth/document.body.clientHeight, 0.1, 1000)\n  camera.position.set(0, 130, 30)\n  // camera.position.set(0, 0, 5)\n  camera.lookAt(scene.position)\n  // 渲染器\n  let renderer = new THREE.WebGLRenderer()\n  renderer.setSize(document.body.clientWidth  , document.body.clientHeight)\n  document.body.appendChild(renderer.domElement)\n  // 渲染（场景， 相机\n  let render = () => {\n    renderer.render(scene, camera)\n  }\n  render()\n  // 相机控制\n  let controls = new THREE.OrbitControls(camera)\n  controls.addEventListener('change', render)\n  let animate = () => {\n    requestAnimationFrame(animate)\n    stats.update()\n    render()\n  }\n  animate()\n  // 地图渲染\n  var map = new THREE.GridHelper( 100, 10 )\n  scene.add( map )\n  // 方格6个面随机图片\n  let sixImg = () => {\n    let materials = []\n    for (var i = 1; i < 7; i++) {\n      materials.push(new THREE.MeshBasicMaterial({\n        map: THREE.ImageUtils.loadTexture('./img/' +  Math.ceil(Math.random() * i) + '.jpg',//图片的路径  \n        {}, function() {\n          renderer.render(scene, camera)\n        }),\n        overdraw: true  \n      }))\n    }\n    return materials\n  }\n  // 方格位置\n  let pos = (what, x, y, z) => {\n    // console.log(what, x, y, z)\n    what.position.x = x\n    what.position.y = y\n    what.position.z = z\n  }\n  class food {\n    constructor () {\n      this.fondSize = [10, 10, 10]\n      this.foodxyz = [null, 5, 5, 5]\n    }\n    show (oo) {\n      this.foodxyz = [\n        null,\n        parseInt(Math.random() * 10) * 10 - 45,\n        5,\n        parseInt(Math.random() * 10) * 10 - 45\n      ]\n      this.judge(oo)\n    }\n    // 判断食物是否刷在蛇里\n    judge (oo) {\n      oo.find(async (v, i, arr) => {\n        if (arr[i][1] === this.foodxyz[1] & arr[i][3] === this.foodxyz[3]) {\n          // 有的话重新调用\n          this.show(oo)\n        } else {\n          // 没有的话 则创建\n          this.foodxyz[0] = await new THREE.Mesh(new THREE.BoxGeometry(...this.fondSize),  new THREE.MeshFaceMaterial(sixImg()))\n          pos(...this.foodxyz)\n          scene.add(this.foodxyz[0])\n          return\n        }\n      })\n    }\n  }\n  class snake {\n    constructor () {\n      this.dir = 'left'\n      this.snakSize = [10, 10, 10]\n      this.snakexyz = [\n        [\n          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshLambertMaterial({color: 0x0000FF})),\n          5, 5, 5\n        ],\n        [\n          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshFaceMaterial(sixImg())),\n          15, 5, 5\n        ],\n        [\n          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshFaceMaterial(sixImg())),\n          25, 5, 5\n        ]\n      ]\n    }\n    show () {\n      for (let i in this.snakexyz) {\n        pos(...this.snakexyz[i])\n        scene.add(this.snakexyz[i][0])\n      }\n    }\n    move () {\n      for (let i = this.snakexyz.length - 1; i > 0; i--) {\n        this.snakexyz[i][1] = this.snakexyz[i - 1][1]\n        this.snakexyz[i][3] = this.snakexyz[i - 1][3]\n      }\n      switch (this.dir) {\n        case 'up':\n          this.snakexyz[0][3] -= 10\n        break\n        case 'left':\n          this.snakexyz[0][1] -= 10\n        break\n        case 'down':\n          this.snakexyz[0][3] += 10\n        break\n        case 'right':\n          this.snakexyz[0][1] += 10\n        break\n      }\n      this.throughWall()\n      this.eat()\n      this.show()\n    }\n    setDir (code) {\n      this.dir = this.dir.toString()\n      switch (code) {\n        case 87:\n          if (this.dir === 'down') {\n            break\n          } else {\n            this.dir = 'up'\n          }\n          break\n        case 65:\n          if (this.dir === 'right') {\n            break\n          } else {\n            this.dir = 'left'\n          }\n          break\n        case 83:\n            if (this.dir === 'up') {\n              break\n            } else {\n              this.dir = 'down'\n            }\n          break\n        case 68:\n            if (this.dir === 'left') {\n              break\n            } else {\n              this.dir = 'right'\n            }\n          break\n      }\n    }\n    eat () {\n      if (this.snakexyz[0][1] === f.foodxyz[1] & this.snakexyz[0][3] === f.foodxyz[3]) {\n        this.snakexyz.push(f.foodxyz)\n        f.show(s.snakexyz)\n      }\n    }\n    throughWall () {\n      if (this.snakexyz[0][3] < -45) {\n        this.snakexyz[0][3] = 45\n      }\n      if (this.snakexyz[0][3] > 45) {\n        this.snakexyz[0][3] = -45\n      }\n      if (this.snakexyz[0][1] < -45) {\n        this.snakexyz[0][1] = 45\n      }\n      if (this.snakexyz[0][1] > 45) {\n        this.snakexyz[0][1] = -45\n      }\n    }\n  }\n  let f = new food()\n  let s = new snake()\n  s.show()\n  f.show(s.snakexyz)\n  // s.move()\n  setInterval( 's.move()' ,200)\n  document.onkeydown = (e) => {\n    s.setDir(e.which)\n    render()\n  }\n</script>"
  },
  {
    "path": "MTLLoader.js",
    "content": "/**\n * Loads a Wavefront .mtl file specifying materials\n *\n * @author angelxuanchang\n */\n\nTHREE.MTLLoader = function ( manager ) {\n\n\tthis.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;\n\n};\n\nTHREE.MTLLoader.prototype = {\n\n\tconstructor: THREE.MTLLoader,\n\n\t/**\n\t * Loads and parses a MTL asset from a URL.\n\t *\n\t * @param {String} url - URL to the MTL file.\n\t * @param {Function} [onLoad] - Callback invoked with the loaded object.\n\t * @param {Function} [onProgress] - Callback for download progress.\n\t * @param {Function} [onError] - Callback for download errors.\n\t *\n\t * @see setPath setTexturePath\n\t *\n\t * @note In order for relative texture references to resolve correctly\n\t * you must call setPath and/or setTexturePath explicitly prior to load.\n\t */\n\tload: function ( url, onLoad, onProgress, onError ) {\n\n\t\tvar scope = this;\n\n\t\tvar loader = new THREE.FileLoader( this.manager );\n\t\tloader.setPath( this.path );\n\t\tloader.load( url, function ( text ) {\n\n\t\t\tonLoad( scope.parse( text ) );\n\n\t\t}, onProgress, onError );\n\n\t},\n\n\t/**\n\t * Set base path for resolving references.\n\t * If set this path will be prepended to each loaded and found reference.\n\t *\n\t * @see setTexturePath\n\t * @param {String} path\n\t *\n\t * @example\n\t *     mtlLoader.setPath( 'assets/obj/' );\n\t *     mtlLoader.load( 'my.mtl', ... );\n\t */\n\tsetPath: function ( path ) {\n\n\t\tthis.path = path;\n\n\t},\n\n\t/**\n\t * Set base path for resolving texture references.\n\t * If set this path will be prepended found texture reference.\n\t * If not set and setPath is, it will be used as texture base path.\n\t *\n\t * @see setPath\n\t * @param {String} path\n\t *\n\t * @example\n\t *     mtlLoader.setPath( 'assets/obj/' );\n\t *     mtlLoader.setTexturePath( 'assets/textures/' );\n\t *     mtlLoader.load( 'my.mtl', ... );\n\t */\n\tsetTexturePath: function ( path ) {\n\n\t\tthis.texturePath = path;\n\n\t},\n\n\tsetBaseUrl: function ( path ) {\n\n\t\tconsole.warn( 'THREE.MTLLoader: .setBaseUrl() is deprecated. Use .setTexturePath( path ) for texture path or .setPath( path ) for general base path instead.' );\n\n\t\tthis.setTexturePath( path );\n\n\t},\n\n\tsetCrossOrigin: function ( value ) {\n\n\t\tthis.crossOrigin = value;\n\n\t},\n\n\tsetMaterialOptions: function ( value ) {\n\n\t\tthis.materialOptions = value;\n\n\t},\n\n\t/**\n\t * Parses a MTL file.\n\t *\n\t * @param {String} text - Content of MTL file\n\t * @return {THREE.MTLLoader.MaterialCreator}\n\t *\n\t * @see setPath setTexturePath\n\t *\n\t * @note In order for relative texture references to resolve correctly\n\t * you must call setPath and/or setTexturePath explicitly prior to parse.\n\t */\n\tparse: function ( text ) {\n\n\t\tvar lines = text.split( '\\n' );\n\t\tvar info = {};\n\t\tvar delimiter_pattern = /\\s+/;\n\t\tvar materialsInfo = {};\n\n\t\tfor ( var i = 0; i < lines.length; i ++ ) {\n\n\t\t\tvar line = lines[ i ];\n\t\t\tline = line.trim();\n\n\t\t\tif ( line.length === 0 || line.charAt( 0 ) === '#' ) {\n\n\t\t\t\t// Blank line or comment ignore\n\t\t\t\tcontinue;\n\n\t\t\t}\n\n\t\t\tvar pos = line.indexOf( ' ' );\n\n\t\t\tvar key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;\n\t\t\tkey = key.toLowerCase();\n\n\t\t\tvar value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';\n\t\t\tvalue = value.trim();\n\n\t\t\tif ( key === 'newmtl' ) {\n\n\t\t\t\t// New material\n\n\t\t\t\tinfo = { name: value };\n\t\t\t\tmaterialsInfo[ value ] = info;\n\n\t\t\t} else if ( info ) {\n\n\t\t\t\tif ( key === 'ka' || key === 'kd' || key === 'ks' ) {\n\n\t\t\t\t\tvar ss = value.split( delimiter_pattern, 3 );\n\t\t\t\t\tinfo[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];\n\n\t\t\t\t} else {\n\n\t\t\t\t\tinfo[ key ] = value;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\tvar materialCreator = new THREE.MTLLoader.MaterialCreator( this.texturePath || this.path, this.materialOptions );\n\t\tmaterialCreator.setCrossOrigin( this.crossOrigin );\n\t\tmaterialCreator.setManager( this.manager );\n\t\tmaterialCreator.setMaterials( materialsInfo );\n\t\treturn materialCreator;\n\n\t}\n\n};\n\n/**\n * Create a new THREE-MTLLoader.MaterialCreator\n * @param baseUrl - Url relative to which textures are loaded\n * @param options - Set of options on how to construct the materials\n *                  side: Which side to apply the material\n *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide\n *                  wrap: What type of wrapping to apply for textures\n *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping\n *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255\n *                                Default: false, assumed to be already normalized\n *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's\n *                                  Default: false\n * @constructor\n */\n\nTHREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {\n\n\tthis.baseUrl = baseUrl || '';\n\tthis.options = options;\n\tthis.materialsInfo = {};\n\tthis.materials = {};\n\tthis.materialsArray = [];\n\tthis.nameLookup = {};\n\n\tthis.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;\n\tthis.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;\n\n};\n\nTHREE.MTLLoader.MaterialCreator.prototype = {\n\n\tconstructor: THREE.MTLLoader.MaterialCreator,\n\n\tcrossOrigin: 'Anonymous',\n\n\tsetCrossOrigin: function ( value ) {\n\n\t\tthis.crossOrigin = value;\n\n\t},\n\n\tsetManager: function ( value ) {\n\n\t\tthis.manager = value;\n\n\t},\n\n\tsetMaterials: function ( materialsInfo ) {\n\n\t\tthis.materialsInfo = this.convert( materialsInfo );\n\t\tthis.materials = {};\n\t\tthis.materialsArray = [];\n\t\tthis.nameLookup = {};\n\n\t},\n\n\tconvert: function ( materialsInfo ) {\n\n\t\tif ( ! this.options ) return materialsInfo;\n\n\t\tvar converted = {};\n\n\t\tfor ( var mn in materialsInfo ) {\n\n\t\t\t// Convert materials info into normalized form based on options\n\n\t\t\tvar mat = materialsInfo[ mn ];\n\n\t\t\tvar covmat = {};\n\n\t\t\tconverted[ mn ] = covmat;\n\n\t\t\tfor ( var prop in mat ) {\n\n\t\t\t\tvar save = true;\n\t\t\t\tvar value = mat[ prop ];\n\t\t\t\tvar lprop = prop.toLowerCase();\n\n\t\t\t\tswitch ( lprop ) {\n\n\t\t\t\t\tcase 'kd':\n\t\t\t\t\tcase 'ka':\n\t\t\t\t\tcase 'ks':\n\n\t\t\t\t\t\t// Diffuse color (color under white light) using RGB values\n\n\t\t\t\t\t\tif ( this.options && this.options.normalizeRGB ) {\n\n\t\t\t\t\t\t\tvalue = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ( this.options && this.options.ignoreZeroRGBs ) {\n\n\t\t\t\t\t\t\tif ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {\n\n\t\t\t\t\t\t\t\t// ignore\n\n\t\t\t\t\t\t\t\tsave = false;\n\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault:\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t}\n\n\t\t\t\tif ( save ) {\n\n\t\t\t\t\tcovmat[ lprop ] = value;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn converted;\n\n\t},\n\n\tpreload: function () {\n\n\t\tfor ( var mn in this.materialsInfo ) {\n\n\t\t\tthis.create( mn );\n\n\t\t}\n\n\t},\n\n\tgetIndex: function ( materialName ) {\n\n\t\treturn this.nameLookup[ materialName ];\n\n\t},\n\n\tgetAsArray: function () {\n\n\t\tvar index = 0;\n\n\t\tfor ( var mn in this.materialsInfo ) {\n\n\t\t\tthis.materialsArray[ index ] = this.create( mn );\n\t\t\tthis.nameLookup[ mn ] = index;\n\t\t\tindex ++;\n\n\t\t}\n\n\t\treturn this.materialsArray;\n\n\t},\n\n\tcreate: function ( materialName ) {\n\n\t\tif ( this.materials[ materialName ] === undefined ) {\n\n\t\t\tthis.createMaterial_( materialName );\n\n\t\t}\n\n\t\treturn this.materials[ materialName ];\n\n\t},\n\n\tcreateMaterial_: function ( materialName ) {\n\n\t\t// Create material\n\n\t\tvar scope = this;\n\t\tvar mat = this.materialsInfo[ materialName ];\n\t\tvar params = {\n\n\t\t\tname: materialName,\n\t\t\tside: this.side\n\n\t\t};\n\n\t\tfunction resolveURL( baseUrl, url ) {\n\n\t\t\tif ( typeof url !== 'string' || url === '' )\n\t\t\t\treturn '';\n\n\t\t\t// Absolute URL\n\t\t\tif ( /^https?:\\/\\//i.test( url ) ) return url;\n\n\t\t\treturn baseUrl + url;\n\n\t\t}\n\n\t\tfunction setMapForType( mapType, value ) {\n\n\t\t\tif ( params[ mapType ] ) return; // Keep the first encountered texture\n\n\t\t\tvar texParams = scope.getTextureParams( value, params );\n\t\t\tvar map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );\n\n\t\t\tmap.repeat.copy( texParams.scale );\n\t\t\tmap.offset.copy( texParams.offset );\n\n\t\t\tmap.wrapS = scope.wrap;\n\t\t\tmap.wrapT = scope.wrap;\n\n\t\t\tparams[ mapType ] = map;\n\n\t\t}\n\n\t\tfor ( var prop in mat ) {\n\n\t\t\tvar value = mat[ prop ];\n\t\t\tvar n;\n\n\t\t\tif ( value === '' ) continue;\n\n\t\t\tswitch ( prop.toLowerCase() ) {\n\n\t\t\t\t// Ns is material specular exponent\n\n\t\t\t\tcase 'kd':\n\n\t\t\t\t\t// Diffuse color (color under white light) using RGB values\n\n\t\t\t\t\tparams.color = new THREE.Color().fromArray( value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'ks':\n\n\t\t\t\t\t// Specular color (color when light is reflected from shiny surface) using RGB values\n\t\t\t\t\tparams.specular = new THREE.Color().fromArray( value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'map_kd':\n\n\t\t\t\t\t// Diffuse texture map\n\n\t\t\t\t\tsetMapForType( \"map\", value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'map_ks':\n\n\t\t\t\t\t// Specular map\n\n\t\t\t\t\tsetMapForType( \"specularMap\", value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'norm':\n\n\t\t\t\t\tsetMapForType( \"normalMap\", value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'map_bump':\n\t\t\t\tcase 'bump':\n\n\t\t\t\t\t// Bump texture map\n\n\t\t\t\t\tsetMapForType( \"bumpMap\", value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'ns':\n\n\t\t\t\t\t// The specular exponent (defines the focus of the specular highlight)\n\t\t\t\t\t// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.\n\n\t\t\t\t\tparams.shininess = parseFloat( value );\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'd':\n\t\t\t\t\tn = parseFloat( value );\n\n\t\t\t\t\tif ( n < 1 ) {\n\n\t\t\t\t\t\tparams.opacity = n;\n\t\t\t\t\t\tparams.transparent = true;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'tr':\n\t\t\t\t\tn = parseFloat( value );\n\n\t\t\t\t\tif ( n > 0 ) {\n\n\t\t\t\t\t\tparams.opacity = 1 - n;\n\t\t\t\t\t\tparams.transparent = true;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\n\t\t\t}\n\n\t\t}\n\n\t\tthis.materials[ materialName ] = new THREE.MeshPhongMaterial( params );\n\t\treturn this.materials[ materialName ];\n\n\t},\n\n\tgetTextureParams: function ( value, matParams ) {\n\n\t\tvar texParams = {\n\n\t\t\tscale: new THREE.Vector2( 1, 1 ),\n\t\t\toffset: new THREE.Vector2( 0, 0 )\n\n\t\t };\n\n\t\tvar items = value.split( /\\s+/ );\n\t\tvar pos;\n\n\t\tpos = items.indexOf( '-bm' );\n\n\t\tif ( pos >= 0 ) {\n\n\t\t\tmatParams.bumpScale = parseFloat( items[ pos + 1 ] );\n\t\t\titems.splice( pos, 2 );\n\n\t\t}\n\n\t\tpos = items.indexOf( '-s' );\n\n\t\tif ( pos >= 0 ) {\n\n\t\t\ttexParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );\n\t\t\titems.splice( pos, 4 ); // we expect 3 parameters here!\n\n\t\t}\n\n\t\tpos = items.indexOf( '-o' );\n\n\t\tif ( pos >= 0 ) {\n\n\t\t\ttexParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );\n\t\t\titems.splice( pos, 4 ); // we expect 3 parameters here!\n\n\t\t}\n\n\t\ttexParams.url = items.join( ' ' ).trim();\n\t\treturn texParams;\n\n\t},\n\n\tloadTexture: function ( url, mapping, onLoad, onProgress, onError ) {\n\n\t\tvar texture;\n\t\tvar loader = THREE.Loader.Handlers.get( url );\n\t\tvar manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;\n\n\t\tif ( loader === null ) {\n\n\t\t\tloader = new THREE.TextureLoader( manager );\n\n\t\t}\n\n\t\tif ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );\n\t\ttexture = loader.load( url, onLoad, onProgress, onError );\n\n\t\tif ( mapping !== undefined ) texture.mapping = mapping;\n\n\t\treturn texture;\n\n\t}\n\n};\n"
  },
  {
    "path": "OBJLoader.js",
    "content": "/**\n * @author mrdoob / http://mrdoob.com/\n */\n\nTHREE.OBJLoader = ( function () {\n\n\t// o object_name | g group_name\n\tvar object_pattern           = /^[og]\\s*(.+)?/;\n\t// mtllib file_reference\n\tvar material_library_pattern = /^mtllib /;\n\t// usemtl material_name\n\tvar material_use_pattern     = /^usemtl /;\n\n\tfunction ParserState() {\n\n\t\tvar state = {\n\t\t\tobjects  : [],\n\t\t\tobject   : {},\n\n\t\t\tvertices : [],\n\t\t\tnormals  : [],\n\t\t\tuvs      : [],\n\n\t\t\tmaterialLibraries : [],\n\n\t\t\tstartObject: function ( name, fromDeclaration ) {\n\n\t\t\t\t// If the current object (initial from reset) is not from a g/o declaration in the parsed\n\t\t\t\t// file. We need to use it for the first parsed g/o to keep things in sync.\n\t\t\t\tif ( this.object && this.object.fromDeclaration === false ) {\n\n\t\t\t\t\tthis.object.name = name;\n\t\t\t\t\tthis.object.fromDeclaration = ( fromDeclaration !== false );\n\t\t\t\t\treturn;\n\n\t\t\t\t}\n\n\t\t\t\tvar previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );\n\n\t\t\t\tif ( this.object && typeof this.object._finalize === 'function' ) {\n\n\t\t\t\t\tthis.object._finalize( true );\n\n\t\t\t\t}\n\n\t\t\t\tthis.object = {\n\t\t\t\t\tname : name || '',\n\t\t\t\t\tfromDeclaration : ( fromDeclaration !== false ),\n\n\t\t\t\t\tgeometry : {\n\t\t\t\t\t\tvertices : [],\n\t\t\t\t\t\tnormals  : [],\n\t\t\t\t\t\tuvs      : []\n\t\t\t\t\t},\n\t\t\t\t\tmaterials : [],\n\t\t\t\t\tsmooth : true,\n\n\t\t\t\t\tstartMaterial: function ( name, libraries ) {\n\n\t\t\t\t\t\tvar previous = this._finalize( false );\n\n\t\t\t\t\t\t// New usemtl declaration overwrites an inherited material, except if faces were declared\n\t\t\t\t\t\t// after the material, then it must be preserved for proper MultiMaterial continuation.\n\t\t\t\t\t\tif ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {\n\n\t\t\t\t\t\t\tthis.materials.splice( previous.index, 1 );\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar material = {\n\t\t\t\t\t\t\tindex      : this.materials.length,\n\t\t\t\t\t\t\tname       : name || '',\n\t\t\t\t\t\t\tmtllib     : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),\n\t\t\t\t\t\t\tsmooth     : ( previous !== undefined ? previous.smooth : this.smooth ),\n\t\t\t\t\t\t\tgroupStart : ( previous !== undefined ? previous.groupEnd : 0 ),\n\t\t\t\t\t\t\tgroupEnd   : -1,\n\t\t\t\t\t\t\tgroupCount : -1,\n\t\t\t\t\t\t\tinherited  : false,\n\n\t\t\t\t\t\t\tclone: function ( index ) {\n\t\t\t\t\t\t\t\tvar cloned = {\n\t\t\t\t\t\t\t\t\tindex      : ( typeof index === 'number' ? index : this.index ),\n\t\t\t\t\t\t\t\t\tname       : this.name,\n\t\t\t\t\t\t\t\t\tmtllib     : this.mtllib,\n\t\t\t\t\t\t\t\t\tsmooth     : this.smooth,\n\t\t\t\t\t\t\t\t\tgroupStart : 0,\n\t\t\t\t\t\t\t\t\tgroupEnd   : -1,\n\t\t\t\t\t\t\t\t\tgroupCount : -1,\n\t\t\t\t\t\t\t\t\tinherited  : false\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tcloned.clone = this.clone.bind(cloned);\n\t\t\t\t\t\t\t\treturn cloned;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tthis.materials.push( material );\n\n\t\t\t\t\t\treturn material;\n\n\t\t\t\t\t},\n\n\t\t\t\t\tcurrentMaterial: function () {\n\n\t\t\t\t\t\tif ( this.materials.length > 0 ) {\n\t\t\t\t\t\t\treturn this.materials[ this.materials.length - 1 ];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn undefined;\n\n\t\t\t\t\t},\n\n\t\t\t\t\t_finalize: function ( end ) {\n\n\t\t\t\t\t\tvar lastMultiMaterial = this.currentMaterial();\n\t\t\t\t\t\tif ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) {\n\n\t\t\t\t\t\t\tlastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;\n\t\t\t\t\t\t\tlastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;\n\t\t\t\t\t\t\tlastMultiMaterial.inherited = false;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Ignore objects tail materials if no face declarations followed them before a new o/g started.\n\t\t\t\t\t\tif ( end && this.materials.length > 1 ) {\n\n\t\t\t\t\t\t\tfor ( var mi = this.materials.length - 1; mi >= 0; mi-- ) {\n\t\t\t\t\t\t\t\tif ( this.materials[ mi ].groupCount <= 0 ) {\n\t\t\t\t\t\t\t\t\tthis.materials.splice( mi, 1 );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Guarantee at least one empty material, this makes the creation later more straight forward.\n\t\t\t\t\t\tif ( end && this.materials.length === 0 ) {\n\n\t\t\t\t\t\t\tthis.materials.push({\n\t\t\t\t\t\t\t\tname   : '',\n\t\t\t\t\t\t\t\tsmooth : this.smooth\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn lastMultiMaterial;\n\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Inherit previous objects material.\n\t\t\t\t// Spec tells us that a declared material must be set to all objects until a new material is declared.\n\t\t\t\t// If a usemtl declaration is encountered while this new object is being parsed, it will\n\t\t\t\t// overwrite the inherited material. Exception being that there was already face declarations\n\t\t\t\t// to the inherited material, then it will be preserved for proper MultiMaterial continuation.\n\n\t\t\t\tif ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {\n\n\t\t\t\t\tvar declared = previousMaterial.clone( 0 );\n\t\t\t\t\tdeclared.inherited = true;\n\t\t\t\t\tthis.object.materials.push( declared );\n\n\t\t\t\t}\n\n\t\t\t\tthis.objects.push( this.object );\n\n\t\t\t},\n\n\t\t\tfinalize: function () {\n\n\t\t\t\tif ( this.object && typeof this.object._finalize === 'function' ) {\n\n\t\t\t\t\tthis.object._finalize( true );\n\n\t\t\t\t}\n\n\t\t\t},\n\n\t\t\tparseVertexIndex: function ( value, len ) {\n\n\t\t\t\tvar index = parseInt( value, 10 );\n\t\t\t\treturn ( index >= 0 ? index - 1 : index + len / 3 ) * 3;\n\n\t\t\t},\n\n\t\t\tparseNormalIndex: function ( value, len ) {\n\n\t\t\t\tvar index = parseInt( value, 10 );\n\t\t\t\treturn ( index >= 0 ? index - 1 : index + len / 3 ) * 3;\n\n\t\t\t},\n\n\t\t\tparseUVIndex: function ( value, len ) {\n\n\t\t\t\tvar index = parseInt( value, 10 );\n\t\t\t\treturn ( index >= 0 ? index - 1 : index + len / 2 ) * 2;\n\n\t\t\t},\n\n\t\t\taddVertex: function ( a, b, c ) {\n\n\t\t\t\tvar src = this.vertices;\n\t\t\t\tvar dst = this.object.geometry.vertices;\n\n\t\t\t\tdst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );\n\t\t\t\tdst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );\n\t\t\t\tdst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );\n\n\t\t\t},\n\n\t\t\taddVertexLine: function ( a ) {\n\n\t\t\t\tvar src = this.vertices;\n\t\t\t\tvar dst = this.object.geometry.vertices;\n\n\t\t\t\tdst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );\n\n\t\t\t},\n\n\t\t\taddNormal: function ( a, b, c ) {\n\n\t\t\t\tvar src = this.normals;\n\t\t\t\tvar dst = this.object.geometry.normals;\n\n\t\t\t\tdst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );\n\t\t\t\tdst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );\n\t\t\t\tdst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );\n\n\t\t\t},\n\n\t\t\taddUV: function ( a, b, c ) {\n\n\t\t\t\tvar src = this.uvs;\n\t\t\t\tvar dst = this.object.geometry.uvs;\n\n\t\t\t\tdst.push( src[ a + 0 ], src[ a + 1 ] );\n\t\t\t\tdst.push( src[ b + 0 ], src[ b + 1 ] );\n\t\t\t\tdst.push( src[ c + 0 ], src[ c + 1 ] );\n\n\t\t\t},\n\n\t\t\taddUVLine: function ( a ) {\n\n\t\t\t\tvar src = this.uvs;\n\t\t\t\tvar dst = this.object.geometry.uvs;\n\n\t\t\t\tdst.push( src[ a + 0 ], src[ a + 1 ] );\n\n\t\t\t},\n\n\t\t\taddFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {\n\n\t\t\t\tvar vLen = this.vertices.length;\n\n\t\t\t\tvar ia = this.parseVertexIndex( a, vLen );\n\t\t\t\tvar ib = this.parseVertexIndex( b, vLen );\n\t\t\t\tvar ic = this.parseVertexIndex( c, vLen );\n\n\t\t\t\tthis.addVertex( ia, ib, ic );\n\n\t\t\t\tif ( ua !== undefined ) {\n\n\t\t\t\t\tvar uvLen = this.uvs.length;\n\n\t\t\t\t\tia = this.parseUVIndex( ua, uvLen );\n\t\t\t\t\tib = this.parseUVIndex( ub, uvLen );\n\t\t\t\t\tic = this.parseUVIndex( uc, uvLen );\n\n\t\t\t\t\tthis.addUV( ia, ib, ic );\n\n\t\t\t\t}\n\n\t\t\t\tif ( na !== undefined ) {\n\n\t\t\t\t\t// Normals are many times the same. If so, skip function call and parseInt.\n\t\t\t\t\tvar nLen = this.normals.length;\n\t\t\t\t\tia = this.parseNormalIndex( na, nLen );\n\n\t\t\t\t\tib = na === nb ? ia : this.parseNormalIndex( nb, nLen );\n\t\t\t\t\tic = na === nc ? ia : this.parseNormalIndex( nc, nLen );\n\n\t\t\t\t\tthis.addNormal( ia, ib, ic );\n\n\t\t\t\t}\n\n\t\t\t},\n\n\t\t\taddLineGeometry: function ( vertices, uvs ) {\n\n\t\t\t\tthis.object.geometry.type = 'Line';\n\n\t\t\t\tvar vLen = this.vertices.length;\n\t\t\t\tvar uvLen = this.uvs.length;\n\n\t\t\t\tfor ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {\n\n\t\t\t\t\tthis.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );\n\n\t\t\t\t}\n\n\t\t\t\tfor ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {\n\n\t\t\t\t\tthis.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t};\n\n\t\tstate.startObject( '', false );\n\n\t\treturn state;\n\n\t}\n\n\t//\n\n\tfunction OBJLoader( manager ) {\n\n\t\tthis.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;\n\n\t\tthis.materials = null;\n\n\t};\n\n\tOBJLoader.prototype = {\n\n\t\tconstructor: OBJLoader,\n\n\t\tload: function ( url, onLoad, onProgress, onError ) {\n\n\t\t\tvar scope = this;\n\n\t\t\tvar loader = new THREE.FileLoader( scope.manager );\n\t\t\tloader.setPath( this.path );\n\t\t\tloader.load( url, function ( text ) {\n\n\t\t\t\tonLoad( scope.parse( text ) );\n\n\t\t\t}, onProgress, onError );\n\n\t\t},\n\n\t\tsetPath: function ( value ) {\n\n\t\t\tthis.path = value;\n\n\t\t},\n\n\t\tsetMaterials: function ( materials ) {\n\n\t\t\tthis.materials = materials;\n\n\t\t\treturn this;\n\n\t\t},\n\n\t\tparse: function ( text ) {\n\n\t\t\tconsole.time( 'OBJLoader' );\n\n\t\t\tvar state = new ParserState();\n\n\t\t\tif ( text.indexOf( '\\r\\n' ) !== - 1 ) {\n\n\t\t\t\t// This is faster than String.split with regex that splits on both\n\t\t\t\ttext = text.replace( /\\r\\n/g, '\\n' );\n\n\t\t\t}\n\n\t\t\tif ( text.indexOf( '\\\\\\n' ) !== - 1) {\n\n\t\t\t\t// join lines separated by a line continuation character (\\)\n\t\t\t\ttext = text.replace( /\\\\\\n/g, '' );\n\n\t\t\t}\n\n\t\t\tvar lines = text.split( '\\n' );\n\t\t\tvar line = '', lineFirstChar = '';\n\t\t\tvar lineLength = 0;\n\t\t\tvar result = [];\n\n\t\t\t// Faster to just trim left side of the line. Use if available.\n\t\t\tvar trimLeft = ( typeof ''.trimLeft === 'function' );\n\n\t\t\tfor ( var i = 0, l = lines.length; i < l; i ++ ) {\n\n\t\t\t\tline = lines[ i ];\n\n\t\t\t\tline = trimLeft ? line.trimLeft() : line.trim();\n\n\t\t\t\tlineLength = line.length;\n\n\t\t\t\tif ( lineLength === 0 ) continue;\n\n\t\t\t\tlineFirstChar = line.charAt( 0 );\n\n\t\t\t\t// @todo invoke passed in handler if any\n\t\t\t\tif ( lineFirstChar === '#' ) continue;\n\n\t\t\t\tif ( lineFirstChar === 'v' ) {\n\n\t\t\t\t\tvar data = line.split( /\\s+/ );\n\n\t\t\t\t\tswitch ( data[ 0 ] ) {\n\n\t\t\t\t\t\tcase 'v':\n\t\t\t\t\t\t\tstate.vertices.push(\n\t\t\t\t\t\t\t\tparseFloat( data[ 1 ] ),\n\t\t\t\t\t\t\t\tparseFloat( data[ 2 ] ),\n\t\t\t\t\t\t\t\tparseFloat( data[ 3 ] )\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'vn':\n\t\t\t\t\t\t\tstate.normals.push(\n\t\t\t\t\t\t\t\tparseFloat( data[ 1 ] ),\n\t\t\t\t\t\t\t\tparseFloat( data[ 2 ] ),\n\t\t\t\t\t\t\t\tparseFloat( data[ 3 ] )\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'vt':\n\t\t\t\t\t\t\tstate.uvs.push(\n\t\t\t\t\t\t\t\tparseFloat( data[ 1 ] ),\n\t\t\t\t\t\t\t\tparseFloat( data[ 2 ] )\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t} else if ( lineFirstChar === 'f' ) {\n\n\t\t\t\t\tvar lineData = line.substr( 1 ).trim();\n\t\t\t\t\tvar vertexData = lineData.split( /\\s+/ );\n\t\t\t\t\tvar faceVertices = [];\n\n\t\t\t\t\t// Parse the face vertex data into an easy to work with format\n\n\t\t\t\t\tfor ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {\n\n\t\t\t\t\t\tvar vertex = vertexData[ j ];\n\n\t\t\t\t\t\tif ( vertex.length > 0 ) {\n\n\t\t\t\t\t\t\tvar vertexParts = vertex.split( '/' );\n\t\t\t\t\t\t\tfaceVertices.push( vertexParts );\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw an edge between the first vertex and all subsequent vertices to form an n-gon\n\n\t\t\t\t\tvar v1 = faceVertices[ 0 ];\n\n\t\t\t\t\tfor ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {\n\n\t\t\t\t\t\tvar v2 = faceVertices[ j ];\n\t\t\t\t\t\tvar v3 = faceVertices[ j + 1 ];\n\n\t\t\t\t\t\tstate.addFace(\n\t\t\t\t\t\t\tv1[ 0 ], v2[ 0 ], v3[ 0 ],\n\t\t\t\t\t\t\tv1[ 1 ], v2[ 1 ], v3[ 1 ],\n\t\t\t\t\t\t\tv1[ 2 ], v2[ 2 ], v3[ 2 ]\n\t\t\t\t\t\t);\n\n\t\t\t\t\t}\n\n\t\t\t\t} else if ( lineFirstChar === 'l' ) {\n\n\t\t\t\t\tvar lineParts = line.substring( 1 ).trim().split( \" \" );\n\t\t\t\t\tvar lineVertices = [], lineUVs = [];\n\n\t\t\t\t\tif ( line.indexOf( \"/\" ) === - 1 ) {\n\n\t\t\t\t\t\tlineVertices = lineParts;\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tfor ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {\n\n\t\t\t\t\t\t\tvar parts = lineParts[ li ].split( \"/\" );\n\n\t\t\t\t\t\t\tif ( parts[ 0 ] !== \"\" ) lineVertices.push( parts[ 0 ] );\n\t\t\t\t\t\t\tif ( parts[ 1 ] !== \"\" ) lineUVs.push( parts[ 1 ] );\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\tstate.addLineGeometry( lineVertices, lineUVs );\n\n\t\t\t\t} else if ( ( result = object_pattern.exec( line ) ) !== null ) {\n\n\t\t\t\t\t// o object_name\n\t\t\t\t\t// or\n\t\t\t\t\t// g group_name\n\n\t\t\t\t\t// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869\n\t\t\t\t\t// var name = result[ 0 ].substr( 1 ).trim();\n\t\t\t\t\tvar name = ( \" \" + result[ 0 ].substr( 1 ).trim() ).substr( 1 );\n\n\t\t\t\t\tstate.startObject( name );\n\n\t\t\t\t} else if ( material_use_pattern.test( line ) ) {\n\n\t\t\t\t\t// material\n\n\t\t\t\t\tstate.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );\n\n\t\t\t\t} else if ( material_library_pattern.test( line ) ) {\n\n\t\t\t\t\t// mtl file\n\n\t\t\t\t\tstate.materialLibraries.push( line.substring( 7 ).trim() );\n\n\t\t\t\t} else if ( lineFirstChar === 's' ) {\n\n\t\t\t\t\tresult = line.split( ' ' );\n\n\t\t\t\t\t// smooth shading\n\n\t\t\t\t\t// @todo Handle files that have varying smooth values for a set of faces inside one geometry,\n\t\t\t\t\t// but does not define a usemtl for each face set.\n\t\t\t\t\t// This should be detected and a dummy material created (later MultiMaterial and geometry groups).\n\t\t\t\t\t// This requires some care to not create extra material on each smooth value for \"normal\" obj files.\n\t\t\t\t\t// where explicit usemtl defines geometry groups.\n\t\t\t\t\t// Example asset: examples/models/obj/cerberus/Cerberus.obj\n\n\t\t\t\t\t/*\n\t\t\t\t\t * http://paulbourke.net/dataformats/obj/\n\t\t\t\t\t * or\n\t\t\t\t\t * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf\n\t\t\t\t\t *\n\t\t\t\t\t * From chapter \"Grouping\" Syntax explanation \"s group_number\":\n\t\t\t\t\t * \"group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.\n\t\t\t\t\t * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form\n\t\t\t\t\t * surfaces, smoothing groups are either turned on or off; there is no difference between values greater\n\t\t\t\t\t * than 0.\"\n\t\t\t\t\t */\n\t\t\t\t\tif ( result.length > 1 ) {\n\n\t\t\t\t\t\tvar value = result[ 1 ].trim().toLowerCase();\n\t\t\t\t\t\tstate.object.smooth = ( value !== '0' && value !== 'off' );\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// ZBrush can produce \"s\" lines #11707\n\t\t\t\t\t\tstate.object.smooth = true;\n\n\t\t\t\t\t}\n\t\t\t\t\tvar material = state.object.currentMaterial();\n\t\t\t\t\tif ( material ) material.smooth = state.object.smooth;\n\n\t\t\t\t} else {\n\n\t\t\t\t\t// Handle null terminated files without exception\n\t\t\t\t\tif ( line === '\\0' ) continue;\n\n\t\t\t\t\tthrow new Error( \"Unexpected line: '\" + line  + \"'\" );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tstate.finalize();\n\n\t\t\tvar container = new THREE.Group();\n\t\t\tcontainer.materialLibraries = [].concat( state.materialLibraries );\n\n\t\t\tfor ( var i = 0, l = state.objects.length; i < l; i ++ ) {\n\n\t\t\t\tvar object = state.objects[ i ];\n\t\t\t\tvar geometry = object.geometry;\n\t\t\t\tvar materials = object.materials;\n\t\t\t\tvar isLine = ( geometry.type === 'Line' );\n\n\t\t\t\t// Skip o/g line declarations that did not follow with any faces\n\t\t\t\tif ( geometry.vertices.length === 0 ) continue;\n\n\t\t\t\tvar buffergeometry = new THREE.BufferGeometry();\n\n\t\t\t\tbuffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );\n\n\t\t\t\tif ( geometry.normals.length > 0 ) {\n\n\t\t\t\t\tbuffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );\n\n\t\t\t\t} else {\n\n\t\t\t\t\tbuffergeometry.computeVertexNormals();\n\n\t\t\t\t}\n\n\t\t\t\tif ( geometry.uvs.length > 0 ) {\n\n\t\t\t\t\tbuffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );\n\n\t\t\t\t}\n\n\t\t\t\t// Create materials\n\n\t\t\t\tvar createdMaterials = [];\n\n\t\t\t\tfor ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {\n\n\t\t\t\t\tvar sourceMaterial = materials[ mi ];\n\t\t\t\t\tvar material = undefined;\n\n\t\t\t\t\tif ( this.materials !== null ) {\n\n\t\t\t\t\t\tmaterial = this.materials.create( sourceMaterial.name );\n\n\t\t\t\t\t\t// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.\n\t\t\t\t\t\tif ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {\n\n\t\t\t\t\t\t\tvar materialLine = new THREE.LineBasicMaterial();\n\t\t\t\t\t\t\tmaterialLine.copy( material );\n\t\t\t\t\t\t\tmaterial = materialLine;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( ! material ) {\n\n\t\t\t\t\t\tmaterial = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() );\n\t\t\t\t\t\tmaterial.name = sourceMaterial.name;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tmaterial.flatShading = sourceMaterial.smooth ? false : true;\n\n\t\t\t\t\tcreatedMaterials.push(material);\n\n\t\t\t\t}\n\n\t\t\t\t// Create mesh\n\n\t\t\t\tvar mesh;\n\n\t\t\t\tif ( createdMaterials.length > 1 ) {\n\n\t\t\t\t\tfor ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {\n\n\t\t\t\t\t\tvar sourceMaterial = materials[ mi ];\n\t\t\t\t\t\tbuffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );\n\n\t\t\t\t\t}\n\n\t\t\t\t\tmesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials ) : new THREE.LineSegments( buffergeometry, createdMaterials ) );\n\n\t\t\t\t} else {\n\n\t\t\t\t\tmesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) );\n\t\t\t\t}\n\n\t\t\t\tmesh.name = object.name;\n\n\t\t\t\tcontainer.add( mesh );\n\n\t\t\t}\n\n\t\t\tconsole.timeEnd( 'OBJLoader' );\n\n\t\t\treturn container;\n\n\t\t}\n\n\t};\n\n\treturn OBJLoader;\n\n} )();\n"
  },
  {
    "path": "OBJLoader2.js",
    "content": "/**\n  * @author Kai Salmen / https://kaisalmen.de\n  * Development repository: https://github.com/kaisalmen/WWOBJLoader\n  */\n\n'use strict';\n\nif ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }\n\n/**\n * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer\n * @class\n *\n * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}\n */\nTHREE.OBJLoader2 = (function () {\n\n\tvar OBJLOADER2_VERSION = '2.0.0';\n\tvar Commons = THREE.LoaderSupport.Commons;\n\tvar Validator = THREE.LoaderSupport.Validator;\n\tvar ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;\n\n\tOBJLoader2.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );\n\tOBJLoader2.prototype.constructor = OBJLoader2;\n\n\tfunction OBJLoader2( logger, manager ) {\n\t\tTHREE.LoaderSupport.Commons.call( this, logger, manager );\n\t\tthis.logger.logInfo( 'Using THREE.OBJLoader2 version: ' + OBJLOADER2_VERSION );\n\n\t\tthis.materialPerSmoothingGroup = false;\n\t\tthis.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );\n\n\t\tthis.workerSupport = null;\n\t\tthis.terminateWorkerOnLoad = true;\n\t};\n\n\t/**\n\t * Tells whether a material shall be created per smoothing group.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {boolean} materialPerSmoothingGroup=false\n\t */\n\tOBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {\n\t\tthis.materialPerSmoothingGroup = materialPerSmoothingGroup === true;\n\t};\n\n\t/**\n\t * Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {string} url URL of the file to load\n\t * @param {callback} onLoad Called after loading was successfully completed\n\t * @param {callback} onProgress Called to report progress of loading. The argument will be the XMLHttpRequest instance, which contains {integer total} and {integer loaded} bytes.\n\t * @param {callback} onError Called after an error occurred during loading\n\t * @param {callback} onMeshAlter Called after a new mesh raw data becomes available to allow alteration\n\t * @param {boolean} useAsync If true uses async loading with worker, if false loads data synchronously\n\t */\n\tOBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {\n\t\tvar scope = this;\n\t\tif ( ! Validator.isValid( onProgress ) ) {\n\t\t\tvar numericalValueRef = 0;\n\t\t\tvar numericalValue = 0;\n\t\t\tonProgress = function ( event ) {\n\t\t\t\tif ( ! event.lengthComputable ) return;\n\n\t\t\t\tnumericalValue = event.loaded / event.total;\n\t\t\t\tif ( numericalValue > numericalValueRef ) {\n\n\t\t\t\t\tnumericalValueRef = numericalValue;\n\t\t\t\t\tvar output = 'Download of \"' + url + '\": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';\n\t\t\t\t\tscope.onProgress( 'progressLoad', output, numericalValue );\n\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tif ( ! Validator.isValid( onError ) ) {\n\t\t\tonError = function ( event ) {\n\t\t\t\tvar output = 'Error occurred while downloading \"' + url + '\"';\n\t\t\t\tscope.logger.logError( output + ': ' + event );\n\t\t\t\tscope.onProgress( 'error', output, -1 );\n\t\t\t};\n\t\t}\n\n\t\tthis.fileLoader.setPath( this.path );\n\t\tthis.fileLoader.setResponseType( 'arraybuffer' );\n\t\tthis.fileLoader.load( url, function ( content ) {\n\t\t\tif ( useAsync ) {\n\n\t\t\t\tscope.parseAsync( content, onLoad );\n\n\t\t\t} else {\n\n\t\t\t\tscope._setCallbacks( null, onMeshAlter, null );\n\t\t\t\tonLoad(\n\t\t\t\t\t{\n\t\t\t\t\t\tdetail: {\n\t\t\t\t\t\t\tloaderRootNode: scope.parse( content ),\n\t\t\t\t\t\t\tmodelName: scope.modelName,\n\t\t\t\t\t\t\tinstanceNo: scope.instanceNo\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t}\n\n\t\t}, onProgress, onError );\n\n\t};\n\n\t/**\n\t * Run the loader according the provided instructions.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution\n\t * @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport\n\t */\n\tOBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {\n\t\tthis._applyPrepData( prepData );\n\t\tvar available = this._checkFiles( prepData.resources );\n\t\tif ( Validator.isValid( workerSupportExternal ) ) {\n\n\t\t\tthis.terminateWorkerOnLoad = false;\n\t\t\tthis.workerSupport = workerSupportExternal;\n\t\t\tthis.logger = workerSupportExternal.logger;\n\n\t\t} else {\n\n\t\t\tthis.terminateWorkerOnLoad = true;\n\n\t\t}\n\t\tvar scope = this;\n\t\tvar onMaterialsLoaded = function ( materials ) {\n\t\t\tscope.builder.setMaterials( materials );\n\n\t\t\tif ( Validator.isValid( available.obj.content ) ) {\n\n\t\t\t\tif ( prepData.useAsync ) {\n\n\t\t\t\t\tscope.parseAsync( available.obj.content, scope.callbacks.onLoad );\n\n\t\t\t\t} else {\n\n\t\t\t\t\tscope.parse( available.obj.content );\n\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\tscope.setPath( available.obj.path );\n\t\t\t\tscope.load( available.obj.name, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync );\n\n\t\t\t}\n\t\t};\n\n\t\tthis._loadMtl( available.mtl, onMaterialsLoaded, prepData.crossOrigin );\n\t};\n\n\tOBJLoader2.prototype._applyPrepData = function ( prepData ) {\n\t\tTHREE.LoaderSupport.Commons.prototype._applyPrepData.call( this, prepData );\n\n\t\tif ( Validator.isValid( prepData ) ) {\n\n\t\t\tthis.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup );\n\n\t\t}\n\t};\n\n\t/**\n\t * Parses OBJ data synchronously from arraybuffer or string.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {arraybuffer|string} content OBJ data as Uint8Array or String\n\t */\n\tOBJLoader2.prototype.parse = function ( content ) {\n\t\tthis.logger.logTimeStart( 'OBJLoader2 parse: ' + this.modelName );\n\n\t\tvar parser = new Parser( this.logger );\n\t\tparser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );\n\t\tparser.setUseIndices( this.useIndices );\n\t\tparser.setDisregardNormals( this.disregardNormals );\n\t\tparser.setMaterialNames( this.builder.materialNames );\n\n\t\tvar scope = this;\n\t\tvar onMeshLoaded = function ( payload ) {\n\t\t\tvar meshes = scope.builder.buildMeshes( payload );\n\t\t\tvar mesh;\n\t\t\tfor ( var i in meshes ) {\n\t\t\t\tmesh = meshes[ i ];\n\t\t\t\tscope.loaderRootNode.add( mesh );\n\t\t\t}\n\t\t};\n\t\tparser.setCallbackBuilder( onMeshLoaded );\n\t\tvar onProgressScoped = function ( text, numericalValue ) {\n\t\t\tscope.onProgress( 'progressParse', text, numericalValue );\n\t\t};\n\t\tparser.setCallbackProgress( onProgressScoped );\n\n\t\tif ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {\n\n\t\t\tthis.logger.logInfo( 'Parsing arrayBuffer...' );\n\t\t\tparser.parse( content );\n\n\t\t} else if ( typeof( content ) === 'string' || content instanceof String ) {\n\n\t\t\tthis.logger.logInfo( 'Parsing text...' );\n\t\t\tparser.parseText( content );\n\n\t\t} else {\n\n\t\t\tthrow 'Provided content was neither of type String nor Uint8Array! Aborting...';\n\n\t\t}\n\t\tthis.logger.logTimeEnd( 'OBJLoader2 parse: ' + this.modelName );\n\n\t\treturn this.loaderRootNode;\n\t};\n\n\t/**\n\t * Parses OBJ content asynchronously from arraybuffer.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {arraybuffer} content OBJ data as Uint8Array\n\t * @param {callback} onLoad Called after worker successfully completed loading\n\t */\n\tOBJLoader2.prototype.parseAsync = function ( content, onLoad ) {\n\t\tthis.logger.logTimeStart( 'OBJLoader2 parseAsync: ' + this.modelName );\n\n\t\tvar scope = this;\n\t\tvar scopedOnLoad = function () {\n\t\t\tonLoad(\n\t\t\t\t{\n\t\t\t\t\tdetail: {\n\t\t\t\t\t\tloaderRootNode: scope.loaderRootNode,\n\t\t\t\t\t\tmodelName: scope.modelName,\n\t\t\t\t\t\tinstanceNo: scope.instanceNo\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t\tif ( scope.terminateWorkerOnLoad ) scope.workerSupport.terminateWorker();\n\t\t\tscope.logger.logTimeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );\n\t\t};\n\t\tvar scopedOnMeshLoaded = function ( payload ) {\n\t\t\tvar meshes = scope.builder.buildMeshes( payload );\n\t\t\tvar mesh;\n\t\t\tfor ( var i in meshes ) {\n\t\t\t\tmesh = meshes[ i ];\n\t\t\t\tscope.loaderRootNode.add( mesh );\n\t\t\t}\n\t\t};\n\n\t\tthis.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport( this.logger ) );\n\t\tvar buildCode = function ( funcBuildObject, funcBuildSingelton ) {\n\t\t\tvar workerCode = '';\n\t\t\tworkerCode += '/**\\n';\n\t\t\tworkerCode += '  * This code was constructed by OBJLoader2 buildWorkerCode.\\n';\n\t\t\tworkerCode += '  */\\n\\n';\n\t\t\tworkerCode += funcBuildSingelton( 'Commons', 'Commons', Commons );\n\t\t\tworkerCode += funcBuildObject( 'Consts', Consts );\n\t\t\tworkerCode += funcBuildObject( 'Validator', Validator );\n\t\t\tworkerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );\n\t\t\tworkerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );\n\t\t\tworkerCode += funcBuildSingelton( 'RawMesh', 'RawMesh', RawMesh );\n\t\t\tworkerCode += funcBuildSingelton( 'RawMeshSubGroup', 'RawMeshSubGroup', RawMeshSubGroup );\n\n\t\t\treturn workerCode;\n\t\t};\n\t\tthis.workerSupport.validate( buildCode, false );\n\t\tthis.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );\n\t\tthis.workerSupport.run(\n\t\t\t{\n\t\t\t\tcmd: 'run',\n\t\t\t\tparams: {\n\t\t\t\t\tmaterialPerSmoothingGroup: this.materialPerSmoothingGroup,\n\t\t\t\t\tuseIndices: this.useIndices,\n\t\t\t\t\tdisregardNormals: this.disregardNormals\n\t\t\t\t},\n\t\t\t\tlogger: {\n\t\t\t\t\tdebug: this.logger.debug,\n\t\t\t\t\tenabled: this.logger.enabled\n\t\t\t\t},\n\t\t\t\tmaterials: {\n\t\t\t\t\tmaterialNames: this.builder.materialNames\n\t\t\t\t},\n\t\t\t\tbuffers: {\n\t\t\t\t\tinput: content\n\t\t\t\t}\n\t\t\t},\n\t\t\t[ content.buffer ]\n\t\t);\n\t};\n\n\t/**\n\t * Constants used by THREE.OBJLoader2\n\t */\n\tvar Consts = {\n\t\tCODE_LF: 10,\n\t\tCODE_CR: 13,\n\t\tCODE_SPACE: 32,\n\t\tCODE_SLASH: 47,\n\t\tSTRING_LF: '\\n',\n\t\tSTRING_CR: '\\r',\n\t\tSTRING_SPACE: ' ',\n\t\tSTRING_SLASH: '/',\n\t\tLINE_F: 'f',\n\t\tLINE_G: 'g',\n\t\tLINE_L: 'l',\n\t\tLINE_O: 'o',\n\t\tLINE_S: 's',\n\t\tLINE_V: 'v',\n\t\tLINE_VT: 'vt',\n\t\tLINE_VN: 'vn',\n\t\tLINE_MTLLIB: 'mtllib',\n\t\tLINE_USEMTL: 'usemtl'\n\t};\n\n\t/**\n\t * Parse OBJ data either from ArrayBuffer or string\n\t * @class\n\t */\n\tvar Parser = (function () {\n\n\t\tfunction Parser( logger ) {\n\t\t\tthis.callbackProgress = null;\n\t\t\tthis.callbackBuilder = null;\n\n\t\t\tthis.materialNames = [];\n\t\t\tthis.rawMesh = null;\n\t\t\tthis.materialPerSmoothingGroup = false;\n\t\t\tthis.useIndices = false;\n\t\t\tthis.disregardNormals = false;\n\n\t\t\tthis.inputObjectCount = 1;\n\t\t\tthis.outputObjectCount = 1;\n\t\t\tthis.counts = {\n\t\t\t\tvertices: 0,\n\t\t\t\tfaces: 0,\n\t\t\t\tdoubleIndicesCount: 0\n\t\t\t};\n\t\t\tthis.logger = logger;\n\t\t\tthis.totalBytes = 0;\n\t\t};\n\n\t\tParser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {\n\t\t\tthis.materialPerSmoothingGroup = materialPerSmoothingGroup;\n\t\t};\n\n\t\tParser.prototype.setUseIndices = function ( useIndices ) {\n\t\t\tthis.useIndices = useIndices;\n\t\t};\n\n\t\tParser.prototype.setDisregardNormals = function ( disregardNormals ) {\n\t\t\tthis.disregardNormals = disregardNormals;\n\t\t};\n\n\t\tParser.prototype.setMaterialNames = function ( materialNames ) {\n\t\t\tthis.materialNames = Validator.verifyInput( materialNames, this.materialNames );\n\t\t\tthis.materialNames = Validator.verifyInput( this.materialNames, [] );\n\t\t};\n\n\t\tParser.prototype.setCallbackBuilder = function ( callbackBuilder ) {\n\t\t\tthis.callbackBuilder = callbackBuilder;\n\t\t\tif ( ! Validator.isValid( this.callbackBuilder ) ) throw 'Unable to run as no \"builder\" callback is set.';\n\t\t};\n\n\t\tParser.prototype.setCallbackProgress = function ( callbackProgress ) {\n\t\t\tthis.callbackProgress = callbackProgress;\n\t\t};\n\n\t\tParser.prototype.configure = function () {\n\t\t\tthis.rawMesh = new RawMesh( this.materialPerSmoothingGroup, this.useIndices, this.disregardNormals );\n\n\t\t\tif ( this.logger.isEnabled() ) {\n\n\t\t\t\tvar matNames = ( this.materialNames.length > 0 ) ? '\\n\\tmaterialNames:\\n\\t\\t- ' + this.materialNames.join( '\\n\\t\\t- ' ) : '\\n\\tmaterialNames: None';\n\t\t\t\tvar printedConfig = 'OBJLoader2.Parser configuration:'\n\t\t\t\t\t\t+ matNames\n\t\t\t\t\t\t+ '\\n\\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup\n\t\t\t\t\t\t+ '\\n\\tuseIndices: ' + this.useIndices\n\t\t\t\t\t\t+ '\\n\\tdisregardNormals: ' + this.disregardNormals\n\t\t\t\t\t\t+ '\\n\\tcallbackBuilderName: ' + this.callbackBuilder.name\n\t\t\t\t\t\t+ '\\n\\tcallbackProgressName: ' + this.callbackProgress.name;\n\t\t\t\tthis.logger.logInfo( printedConfig );\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Parse the provided arraybuffer\n\t\t * @memberOf Parser\n\t\t *\n\t\t * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array\n\t\t */\n\t\tParser.prototype.parse = function ( arrayBuffer ) {\n\t\t\tthis.logger.logTimeStart( 'OBJLoader2.Parser.parse' );\n\t\t\tthis.configure();\n\n\t\t\tvar arrayBufferView = new Uint8Array( arrayBuffer );\n\t\t\tvar length = arrayBufferView.byteLength;\n\t\t\tthis.totalBytes = length;\n\t\t\tvar buffer = new Array( 128 );\n\t\t\tvar bufferPointer = 0;\n\t\t\tvar slashesCount = 0;\n\t\t\tvar reachedFaces = false;\n\t\t\tvar code;\n\t\t\tvar word = '';\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i ++ ) {\n\n\t\t\t\tcode = arrayBufferView[ i ];\n\t\t\t\tswitch ( code ) {\n\t\t\t\t\tcase Consts.CODE_SPACE:\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.CODE_SLASH:\n\t\t\t\t\t\tslashesCount ++;\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.CODE_LF:\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\treachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );\n\t\t\t\t\t\tbufferPointer = 0;\n\t\t\t\t\t\tslashesCount = 0;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.CODE_CR:\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tword += String.fromCharCode( code );\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.finalize( i );\n\t\t\tthis.logger.logTimeEnd( 'OBJLoader2.Parser.parse' );\n\t\t};\n\n\t\t/**\n\t\t * Parse the provided text\n\t\t * @memberOf Parser\n\t\t *\n\t\t * @param {string} text OBJ data as string\n\t\t */\n\t\tParser.prototype.parseText = function ( text ) {\n\t\t\tthis.logger.logTimeStart( 'OBJLoader2.Parser.parseText' );\n\t\t\tthis.configure();\n\n\t\t\tvar length = text.length;\n\t\t\tthis.totalBytes = length;\n\t\t\tvar buffer = new Array( 128 );\n\t\t\tvar bufferPointer = 0;\n\t\t\tvar slashesCount = 0;\n\t\t\tvar reachedFaces = false;\n\t\t\tvar char;\n\t\t\tvar word = '';\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i ++ ) {\n\n\t\t\t\tchar = text[ i ];\n\t\t\t\tswitch ( char ) {\n\t\t\t\t\tcase Consts.STRING_SPACE:\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.STRING_SLASH:\n\t\t\t\t\t\tslashesCount ++;\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.STRING_LF:\n\t\t\t\t\t\tif ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;\n\t\t\t\t\t\tword = '';\n\t\t\t\t\t\treachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );\n\t\t\t\t\t\tbufferPointer = 0;\n\t\t\t\t\t\tslashesCount = 0;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase Consts.STRING_CR:\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tword += char;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.finalize( i );\n\t\t\tthis.logger.logTimeEnd( 'OBJLoader2.Parser.parseText' );\n\t\t};\n\n\t\tParser.prototype.processLine = function ( buffer, bufferPointer, slashesCount, reachedFaces, currentByte ) {\n\t\t\tif ( bufferPointer < 1 ) return reachedFaces;\n\n\t\t\tvar bufferLength = bufferPointer - 1;\n\t\t\tvar concatBuffer;\n\t\t\tswitch ( buffer[ 0 ] ) {\n\t\t\t\tcase Consts.LINE_V:\n\n\t\t\t\t\t// object complete instance required if reached faces already (= reached next block of v)\n\t\t\t\t\tif ( reachedFaces ) {\n\n\t\t\t\t\t\tif ( this.rawMesh.colors.length > 0 && this.rawMesh.colors.length !== this.rawMesh.vertices.length ) {\n\n\t\t\t\t\t\t\tthrow 'Vertex Colors were detected, but vertex count and color count do not match!';\n\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.processCompletedObject( null, this.rawMesh.groupName, currentByte );\n\t\t\t\t\t\treachedFaces = false;\n\n\t\t\t\t\t}\n\t\t\t\t\tif ( bufferLength === 3 ) {\n\n\t\t\t\t\t\tthis.rawMesh.pushVertex( buffer )\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tthis.rawMesh.pushVertexAndVertextColors( buffer );\n\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_VT:\n\t\t\t\t\tthis.rawMesh.pushUv( buffer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_VN:\n\t\t\t\t\tthis.rawMesh.pushNormal( buffer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_F:\n\t\t\t\t\treachedFaces = true;\n\t\t\t\t\tthis.rawMesh.processFaces( buffer, bufferPointer, slashesCount );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_L:\n\t\t\t\t\tif ( bufferLength === slashesCount * 2 ) {\n\n\t\t\t\t\t\tthis.rawMesh.buildLineVvt( buffer );\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tthis.rawMesh.buildLineV( buffer );\n\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_S:\n\t\t\t\t\tthis.rawMesh.pushSmoothingGroup( buffer[ 1 ] );\n\t\t\t\t\tthis.flushStringBuffer( buffer, bufferPointer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_G:\n\t\t\t\t\tconcatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];\n\t\t\t\t\tthis.processCompletedGroup( concatBuffer, currentByte );\n\t\t\t\t\tthis.flushStringBuffer( buffer, bufferPointer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_O:\n\t\t\t\t\tconcatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];\n\t\t\t\t\tif ( this.rawMesh.vertices.length > 0 ) {\n\n\t\t\t\t\t\tthis.processCompletedObject( concatBuffer, null, currentByte );\n\t\t\t\t\t\treachedFaces = false;\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tthis.rawMesh.pushObject( concatBuffer );\n\n\t\t\t\t\t}\n\t\t\t\t\tthis.flushStringBuffer( buffer, bufferPointer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_MTLLIB:\n\t\t\t\t\tconcatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];\n\t\t\t\t\tthis.rawMesh.pushMtllib( concatBuffer );\n\t\t\t\t\tthis.flushStringBuffer( buffer, bufferPointer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Consts.LINE_USEMTL:\n\t\t\t\t\tconcatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];\n\t\t\t\t\tthis.rawMesh.pushUsemtl( concatBuffer );\n\t\t\t\t\tthis.flushStringBuffer( buffer, bufferPointer );\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn reachedFaces;\n\t\t};\n\n\t\tParser.prototype.flushStringBuffer = function ( buffer, bufferLength ) {\n\t\t\tfor ( var i = 0; i < bufferLength; i ++ ) {\n\t\t\t\tbuffer[ i ] = '';\n\t\t\t}\n\t\t};\n\n\t\tParser.prototype.createRawMeshReport = function ( rawMesh , inputObjectCount ) {\n\t\t\tvar report = rawMesh.createReport( inputObjectCount );\n\t\t\treturn 'Input Object number: ' + inputObjectCount +\n\t\t\t\t'\\n\\tObject name: ' + report.objectName +\n\t\t\t\t'\\n\\tGroup name: ' + report.groupName +\n\t\t\t\t'\\n\\tMtllib name: ' + report.mtllibName +\n\t\t\t\t'\\n\\tVertex count: ' + report.vertexCount +\n\t\t\t\t'\\n\\tNormal count: ' + report.normalCount +\n\t\t\t\t'\\n\\tUV count: ' + report.uvCount +\n\t\t\t\t'\\n\\tSmoothingGroup count: ' + report.smoothingGroupCount +\n\t\t\t\t'\\n\\tMaterial count: ' + report.mtlCount +\n\t\t\t\t'\\n\\tReal RawMeshSubGroup count: ' + report.subGroups;\n\t\t};\n\n\t\tParser.prototype.processCompletedObject = function ( objectName, groupName, currentByte ) {\n\t\t\tvar result = this.rawMesh.finalize();\n\t\t\tif ( Validator.isValid( result ) ) {\n\n\t\t\t\tthis.inputObjectCount++;\n\t\t\t\tif ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );\n\t\t\t\tthis.buildMesh( result, currentByte );\n\t\t\t\tvar progressBytesPercent = currentByte / this.totalBytes;\n\t\t\t\tthis.callbackProgress( 'Completed object: ' + objectName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );\n\n\t\t\t}\n\t\t\tthis.rawMesh = this.rawMesh.newInstanceFromObject( objectName, groupName );\n\t\t};\n\n\t\tParser.prototype.processCompletedGroup = function ( groupName, currentByte ) {\n\t\t\tvar result = this.rawMesh.finalize();\n\t\t\tif ( Validator.isValid( result ) ) {\n\n\t\t\t\tthis.inputObjectCount++;\n\t\t\t\tif ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );\n\t\t\t\tthis.buildMesh( result, currentByte );\n\t\t\t\tvar progressBytesPercent = currentByte / this.totalBytes;\n\t\t\t\tthis.callbackProgress( 'Completed group: ' + groupName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );\n\t\t\t\tthis.rawMesh = this.rawMesh.newInstanceFromGroup( groupName );\n\n\t\t\t} else {\n\n\t\t\t\t// if a group was set that did not lead to object creation in finalize, then the group name has to be updated\n\t\t\t\tthis.rawMesh.pushGroup( groupName );\n\n\t\t\t}\n\t\t};\n\n\t\tParser.prototype.finalize = function ( currentByte ) {\n\t\t\tthis.logger.logInfo( 'Global output object count: ' + this.outputObjectCount );\n\t\t\tvar result = Validator.isValid( this.rawMesh ) ? this.rawMesh.finalize() : null;\n\t\t\tif ( Validator.isValid( result ) ) {\n\n\t\t\t\tthis.inputObjectCount++;\n\t\t\t\tif ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );\n\t\t\t\tthis.buildMesh( result, currentByte );\n\n\t\t\t\tif ( this.logger.isEnabled() ) {\n\n\t\t\t\t\tvar parserFinalReport = 'Overall counts: ' +\n\t\t\t\t\t\t'\\n\\tVertices: ' + this.counts.vertices +\n\t\t\t\t\t\t'\\n\\tFaces: ' + this.counts.faces +\n\t\t\t\t\t\t'\\n\\tMultiple definitions: ' + this.counts.doubleIndicesCount;\n\t\t\t\t\tthis.logger.logInfo( parserFinalReport );\n\n\t\t\t\t}\n\t\t\t\tvar progressBytesPercent = currentByte / this.totalBytes;\n\t\t\t\tthis.callbackProgress( 'Completed Parsing: 100.00%', progressBytesPercent );\n\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * RawObjectDescriptions are transformed to too intermediate format that is forwarded to the Builder.\n\t\t * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).\n\t\t *\n\t\t * @param result\n\t\t */\n\t\tParser.prototype.buildMesh = function ( result, currentByte ) {\n\t\t\tvar rawObjectDescriptions = result.subGroups;\n\n\t\t\tvar vertexFA = new Float32Array( result.absoluteVertexCount );\n\t\t\tthis.counts.vertices += result.absoluteVertexCount / 3;\n\t\t\tthis.counts.faces += result.faceCount;\n\t\t\tthis.counts.doubleIndicesCount += result.doubleIndicesCount;\n\t\t\tvar indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;\n\t\t\tvar colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;\n\t\t\tvar normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;\n\t\t\tvar uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;\n\n\t\t\tvar rawObjectDescription;\n\t\t\tvar materialDescription;\n\t\t\tvar materialDescriptions = [];\n\n\t\t\tvar createMultiMaterial = ( rawObjectDescriptions.length > 1 );\n\t\t\tvar materialIndex = 0;\n\t\t\tvar materialIndexMapping = [];\n\t\t\tvar selectedMaterialIndex;\n\t\t\tvar materialGroup;\n\t\t\tvar materialGroups = [];\n\n\t\t\tvar vertexFAOffset = 0;\n\t\t\tvar indexUAOffset = 0;\n\t\t\tvar colorFAOffset = 0;\n\t\t\tvar normalFAOffset = 0;\n\t\t\tvar uvFAOffset = 0;\n\t\t\tvar materialGroupOffset = 0;\n\t\t\tvar materialGroupLength = 0;\n\n\t\t\tfor ( var oodIndex in rawObjectDescriptions ) {\n\t\t\t\tif ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;\n\t\t\t\trawObjectDescription = rawObjectDescriptions[ oodIndex ];\n\n\t\t\t\tmaterialDescription = {\n\t\t\t\t\tname: rawObjectDescription.materialName,\n\t\t\t\t\tflat: false,\n\t\t\t\t\tdefault: false\n\t\t\t\t};\n\t\t\t\tif ( this.materialNames[ materialDescription.name ] === null ) {\n\n\t\t\t\t\tmaterialDescription.default = true;\n\t\t\t\t\tthis.logger.logWarn( 'object_group \"' + rawObjectDescription.objectName + '_' +\n\t\t\t\t\t\trawObjectDescription.groupName +\n\t\t\t\t\t\t'\" was defined without material! Assigning \"defaultMaterial\".' );\n\n\t\t\t\t}\n\t\t\t\t// Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0\n\t\t\t\tif ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;\n\n\t\t\t\tif ( createMultiMaterial ) {\n\n\t\t\t\t\t// re-use material if already used before. Reduces materials array size and eliminates duplicates\n\n\t\t\t\t\tselectedMaterialIndex = materialIndexMapping[ materialDescription.name ];\n\t\t\t\t\tif ( ! selectedMaterialIndex ) {\n\n\t\t\t\t\t\tselectedMaterialIndex = materialIndex;\n\t\t\t\t\t\tmaterialIndexMapping[ materialDescription.name ] = materialIndex;\n\t\t\t\t\t\tmaterialDescriptions.push( materialDescription );\n\t\t\t\t\t\tmaterialIndex++;\n\n\t\t\t\t\t}\n\t\t\t\t\tmaterialGroupLength = this.useIndices ? rawObjectDescription.indices.length : rawObjectDescription.vertices.length / 3;\n\t\t\t\t\tmaterialGroup = {\n\t\t\t\t\t\tstart: materialGroupOffset,\n\t\t\t\t\t\tcount: materialGroupLength,\n\t\t\t\t\t\tindex: selectedMaterialIndex\n\t\t\t\t\t};\n\t\t\t\t\tmaterialGroups.push( materialGroup );\n\t\t\t\t\tmaterialGroupOffset += materialGroupLength;\n\n\t\t\t\t} else {\n\n\t\t\t\t\tmaterialDescriptions.push( materialDescription );\n\n\t\t\t\t}\n\n\t\t\t\tvertexFA.set( rawObjectDescription.vertices, vertexFAOffset );\n\t\t\t\tvertexFAOffset += rawObjectDescription.vertices.length;\n\n\t\t\t\tif ( indexUA ) {\n\n\t\t\t\t\tindexUA.set( rawObjectDescription.indices, indexUAOffset );\n\t\t\t\t\tindexUAOffset += rawObjectDescription.indices.length;\n\n\t\t\t\t}\n\n\t\t\t\tif ( colorFA ) {\n\n\t\t\t\t\tcolorFA.set( rawObjectDescription.colors, colorFAOffset );\n\t\t\t\t\tcolorFAOffset += rawObjectDescription.colors.length;\n\n\t\t\t\t}\n\n\t\t\t\tif ( normalFA ) {\n\n\t\t\t\t\tnormalFA.set( rawObjectDescription.normals, normalFAOffset );\n\t\t\t\t\tnormalFAOffset += rawObjectDescription.normals.length;\n\n\t\t\t\t}\n\t\t\t\tif ( uvFA ) {\n\n\t\t\t\t\tuvFA.set( rawObjectDescription.uvs, uvFAOffset );\n\t\t\t\t\tuvFAOffset += rawObjectDescription.uvs.length;\n\n\t\t\t\t}\n\n\t\t\t\tif ( this.logger.isDebug() ) {\n\t\t\t\t\tvar materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\\n\\t\\tmaterialIndex: ' + selectedMaterialIndex : '';\n\t\t\t\t\tvar createdReport = 'Output Object no.: ' + this.outputObjectCount +\n\t\t\t\t\t\t'\\n\\t\\tobjectName: ' + rawObjectDescription.objectName +\n\t\t\t\t\t\t'\\n\\t\\tgroupName: ' + rawObjectDescription.groupName +\n\t\t\t\t\t\t'\\n\\t\\tmaterialName: ' + rawObjectDescription.materialName +\n\t\t\t\t\t\tmaterialIndexLine +\n\t\t\t\t\t\t'\\n\\t\\tsmoothingGroup: ' + rawObjectDescription.smoothingGroup +\n\t\t\t\t\t\t'\\n\\t\\t#vertices: ' + rawObjectDescription.vertices.length / 3 +\n\t\t\t\t\t\t'\\n\\t\\t#indices: ' + rawObjectDescription.indices.length +\n\t\t\t\t\t\t'\\n\\t\\t#colors: ' + rawObjectDescription.colors.length / 3 +\n\t\t\t\t\t\t'\\n\\t\\t#uvs: ' + rawObjectDescription.uvs.length / 2 +\n\t\t\t\t\t\t'\\n\\t\\t#normals: ' + rawObjectDescription.normals.length / 3;\n\t\t\t\t\tthis.logger.logDebug( createdReport );\n\t\t\t\t}\n\n\n\t\t\t}\n\n\t\t\tthis.outputObjectCount++;\n\t\t\tthis.callbackBuilder(\n\t\t\t\t{\n\t\t\t\t\tcmd: 'meshData',\n\t\t\t\t\tprogress: {\n\t\t\t\t\t\tnumericalValue: currentByte / this.totalBytes\n\t\t\t\t\t},\n\t\t\t\t\tparams: {\n\t\t\t\t\t\tmeshName: result.name\n\t\t\t\t\t},\n\t\t\t\t\tmaterials: {\n\t\t\t\t\t\tmultiMaterial: createMultiMaterial,\n\t\t\t\t\t\tmaterialDescriptions: materialDescriptions,\n\t\t\t\t\t\tmaterialGroups: materialGroups\n\t\t\t\t\t},\n\t\t\t\t\tbuffers: {\n\t\t\t\t\t\tvertices: vertexFA,\n\t\t\t\t\t\tindices: indexUA,\n\t\t\t\t\t\tcolors: colorFA,\n\t\t\t\t\t\tnormals: normalFA,\n\t\t\t\t\t\tuvs: uvFA\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t[ vertexFA.buffer ],\n\t\t\t\tValidator.isValid( indexUA ) ? [ indexUA.buffer ] : null,\n\t\t\t\tValidator.isValid( colorFA ) ? [ colorFA.buffer ] : null,\n\t\t\t\tValidator.isValid( normalFA ) ? [ normalFA.buffer ] : null,\n\t\t\t\tValidator.isValid( uvFA ) ? [ uvFA.buffer ] : null\n\t\t\t);\n\t\t};\n\n\t\treturn Parser;\n\t})();\n\n\t/**\n\t * {@link RawMesh} is only used by {@link Parser}.\n\t * The user of OBJLoader2 does not need to care about this class.\n\t * It is defined publicly for inclusion in web worker based OBJ loader ({@link THREE.OBJLoader2.WWOBJLoader2})\n\t */\n\tvar RawMesh = (function () {\n\n\t\tfunction RawMesh( materialPerSmoothingGroup, useIndices, disregardNormals, objectName, groupName, activeMtlName ) {\n\t\t\tthis.globalVertexOffset = 1;\n\t\t\tthis.globalUvOffset = 1;\n\t\t\tthis.globalNormalOffset = 1;\n\n\t\t\tthis.vertices = [];\n\t\t\tthis.colors = [];\n\t\t\tthis.normals = [];\n\t\t\tthis.uvs = [];\n\n\t\t\t// faces are stored according combined index of group, material and smoothingGroup (0 or not)\n\t\t\tthis.activeMtlName = Validator.verifyInput( activeMtlName, '' );\n\t\t\tthis.objectName = Validator.verifyInput( objectName, '' );\n\t\t\tthis.groupName = Validator.verifyInput( groupName, '' );\n\t\t\tthis.mtllibName = '';\n\t\t\tthis.smoothingGroup = {\n\t\t\t\tsplitMaterials: materialPerSmoothingGroup === true,\n\t\t\t\tnormalized: -1,\n\t\t\t\treal: -1\n\t\t\t};\n\t\t\tthis.useIndices = useIndices === true;\n\t\t\tthis.disregardNormals = disregardNormals === true;\n\n\t\t\tthis.mtlCount = 0;\n\t\t\tthis.smoothingGroupCount = 0;\n\n\t\t\tthis.subGroups = [];\n\t\t\tthis.subGroupInUse = null;\n\t\t\t// this default index is required as it is possible to define faces without 'g' or 'usemtl'\n\t\t\tthis.pushSmoothingGroup( 1 );\n\n\t\t\tthis.doubleIndicesCount = 0;\n\t\t\tthis.faceCount = 0;\n\t\t}\n\n\t\tRawMesh.prototype.newInstanceFromObject = function ( objectName, groupName ) {\n\t\t\tvar newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, objectName, groupName, this.activeMtlName );\n\n\t\t\t// move indices forward\n\t\t\tnewRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3;\n\t\t\tnewRawObject.globalUvOffset = this.globalUvOffset + this.uvs.length / 2;\n\t\t\tnewRawObject.globalNormalOffset = this.globalNormalOffset + this.normals.length / 3;\n\n\t\t\treturn newRawObject;\n\t\t};\n\n\t\tRawMesh.prototype.newInstanceFromGroup = function ( groupName ) {\n\t\t\tvar newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, this.objectName, groupName, this.activeMtlName );\n\n\t\t\t// keep current buffers and indices forward\n\t\t\tnewRawObject.vertices = this.vertices;\n\t\t\tnewRawObject.colors = this.colors;\n\t\t\tnewRawObject.uvs = this.uvs;\n\t\t\tnewRawObject.normals = this.normals;\n\t\t\tnewRawObject.globalVertexOffset = this.globalVertexOffset;\n\t\t\tnewRawObject.globalUvOffset = this.globalUvOffset;\n\t\t\tnewRawObject.globalNormalOffset = this.globalNormalOffset;\n\n\t\t\treturn newRawObject;\n\t\t};\n\n\t\tRawMesh.prototype.pushVertex = function ( buffer ) {\n\t\t\tthis.vertices.push( parseFloat( buffer[ 1 ] ) );\n\t\t\tthis.vertices.push( parseFloat( buffer[ 2 ] ) );\n\t\t\tthis.vertices.push( parseFloat( buffer[ 3 ] ) );\n\t\t};\n\n\t\tRawMesh.prototype.pushVertexAndVertextColors = function ( buffer ) {\n\t\t\tthis.vertices.push( parseFloat( buffer[ 1 ] ) );\n\t\t\tthis.vertices.push( parseFloat( buffer[ 2 ] ) );\n\t\t\tthis.vertices.push( parseFloat( buffer[ 3 ] ) );\n\t\t\tthis.colors.push( parseFloat( buffer[ 4 ] ) );\n\t\t\tthis.colors.push( parseFloat( buffer[ 5 ] ) );\n\t\t\tthis.colors.push( parseFloat( buffer[ 6 ] ) );\n\t\t};\n\n\t\tRawMesh.prototype.pushUv = function ( buffer ) {\n\t\t\tthis.uvs.push( parseFloat( buffer[ 1 ] ) );\n\t\t\tthis.uvs.push( parseFloat( buffer[ 2 ] ) );\n\t\t};\n\n\t\tRawMesh.prototype.pushNormal = function ( buffer ) {\n\t\t\tthis.normals.push( parseFloat( buffer[ 1 ] ) );\n\t\t\tthis.normals.push( parseFloat( buffer[ 2 ] ) );\n\t\t\tthis.normals.push( parseFloat( buffer[ 3 ] ) );\n\t\t};\n\n\t\tRawMesh.prototype.pushObject = function ( objectName ) {\n\t\t\tthis.objectName = Validator.verifyInput( objectName, '' );\n\t\t};\n\n\t\tRawMesh.prototype.pushMtllib = function ( mtllibName ) {\n\t\t\tthis.mtllibName = Validator.verifyInput( mtllibName, '' );\n\t\t};\n\n\t\tRawMesh.prototype.pushGroup = function ( groupName ) {\n\t\t\tthis.groupName = Validator.verifyInput( groupName, '' );\n\t\t};\n\n\t\tRawMesh.prototype.pushUsemtl = function ( mtlName ) {\n\t\t\tif ( this.activeMtlName === mtlName || ! Validator.isValid( mtlName ) ) return;\n\t\t\tthis.activeMtlName = mtlName;\n\t\t\tthis.mtlCount++;\n\n\t\t\tthis.verifyIndex();\n\t\t};\n\n\t\tRawMesh.prototype.pushSmoothingGroup = function ( smoothingGroup ) {\n\t\t\tvar smoothingGroupInt = parseInt( smoothingGroup );\n\t\t\tif ( isNaN( smoothingGroupInt ) ) {\n\t\t\t\tsmoothingGroupInt = smoothingGroup === \"off\" ? 0 : 1;\n\t\t\t}\n\n\t\t\tvar smoothCheck = this.smoothingGroup.normalized;\n\t\t\tthis.smoothingGroup.normalized = this.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;\n\t\t\tthis.smoothingGroup.real = smoothingGroupInt;\n\n\t\t\tif ( smoothCheck !== smoothingGroupInt ) {\n\n\t\t\t\tthis.smoothingGroupCount++;\n\t\t\t\tthis.verifyIndex();\n\n\t\t\t}\n\t\t};\n\n\t\tRawMesh.prototype.verifyIndex = function () {\n\t\t\tvar index = this.activeMtlName + '|' + this.smoothingGroup.normalized;\n\t\t\tthis.subGroupInUse = this.subGroups[ index ];\n\t\t\tif ( ! Validator.isValid( this.subGroupInUse ) ) {\n\n\t\t\t\tthis.subGroupInUse = new RawMeshSubGroup( this.objectName, this.groupName, this.activeMtlName, this.smoothingGroup.normalized );\n\t\t\t\tthis.subGroups[ index ] = this.subGroupInUse;\n\n\t\t\t}\n\t\t};\n\n\t\tRawMesh.prototype.processFaces = function ( buffer, bufferPointer, slashesCount ) {\n\t\t\tvar bufferLength = bufferPointer - 1;\n\t\t\tvar i, length;\n\n\t\t\t// \"f vertex ...\"\n\t\t\tif ( slashesCount === 0 ) {\n\n\t\t\t\tfor ( i = 2, length = bufferLength - 1; i < length; i ++ ) {\n\n\t\t\t\t\tthis.buildFace( buffer[ 1 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i ] );\n\t\t\t\t\tthis.buildFace( buffer[ i + 1 ] );\n\n\t\t\t\t}\n\n\t\t\t\t// \"f vertex/uv ...\"\n\t\t\t} else if  ( bufferLength === slashesCount * 2 ) {\n\n\t\t\t\tfor ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {\n\n\t\t\t\t\tthis.buildFace( buffer[ 1 ], buffer[ 2 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i ], buffer[ i + 1 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );\n\n\t\t\t\t}\n\n\t\t\t\t// \"f vertex/uv/normal ...\"\n\t\t\t} else if  ( bufferLength * 2 === slashesCount * 3 ) {\n\n\t\t\t\tfor ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {\n\n\t\t\t\t\tthis.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );\n\n\t\t\t\t}\n\n\t\t\t\t// \"f vertex//normal ...\"\n\t\t\t} else {\n\n\t\t\t\tfor ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {\n\n\t\t\t\t\tthis.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );\n\t\t\t\t\tthis.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t};\n\n\t\tRawMesh.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {\n\t\t\tvar sgiu = this.subGroupInUse;\n\t\t\tif ( this.disregardNormals ) faceIndexN = undefined;\n\t\t\tvar scope = this;\n\t\t\tvar updateRawObjectDescriptionInUse = function () {\n\n\t\t\t\tvar indexPointerV = ( parseInt( faceIndexV ) - scope.globalVertexOffset ) * 3;\n\t\t\t\tvar indexPointerC = scope.colors.length > 0 ? indexPointerV : null;\n\n\t\t\t\tvar vertices = sgiu.vertices;\n\t\t\t\tvertices.push( scope.vertices[ indexPointerV++ ] );\n\t\t\t\tvertices.push( scope.vertices[ indexPointerV++ ] );\n\t\t\t\tvertices.push( scope.vertices[ indexPointerV ] );\n\n\t\t\t\tif ( indexPointerC !== null ) {\n\n\t\t\t\t\tvar colors = sgiu.colors;\n\t\t\t\t\tcolors.push( scope.colors[ indexPointerC++ ] );\n\t\t\t\t\tcolors.push( scope.colors[ indexPointerC++ ] );\n\t\t\t\t\tcolors.push( scope.colors[ indexPointerC ] );\n\n\t\t\t\t}\n\n\t\t\t\tif ( faceIndexU ) {\n\n\t\t\t\t\tvar indexPointerU = ( parseInt( faceIndexU ) - scope.globalUvOffset ) * 2;\n\t\t\t\t\tvar uvs = sgiu.uvs;\n\t\t\t\t\tuvs.push( scope.uvs[ indexPointerU++ ] );\n\t\t\t\t\tuvs.push( scope.uvs[ indexPointerU ] );\n\n\t\t\t\t}\n\t\t\t\tif ( faceIndexN ) {\n\n\t\t\t\t\tvar indexPointerN = ( parseInt( faceIndexN ) - scope.globalNormalOffset ) * 3;\n\t\t\t\t\tvar normals = sgiu.normals;\n\t\t\t\t\tnormals.push( scope.normals[ indexPointerN++ ] );\n\t\t\t\t\tnormals.push( scope.normals[ indexPointerN++ ] );\n\t\t\t\t\tnormals.push( scope.normals[ indexPointerN ] );\n\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif ( this.useIndices ) {\n\n\t\t\t\tvar mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );\n\t\t\t\tvar indicesPointer = sgiu.indexMappings[ mappingName ];\n\t\t\t\tif ( Validator.isValid( indicesPointer ) ) {\n\n\t\t\t\t\tthis.doubleIndicesCount++;\n\n\t\t\t\t} else {\n\n\t\t\t\t\tindicesPointer = sgiu.vertices.length / 3;\n\t\t\t\t\tupdateRawObjectDescriptionInUse();\n\t\t\t\t\tsgiu.indexMappings[ mappingName ] = indicesPointer;\n\t\t\t\t\tsgiu.indexMappingsCount++;\n\n\t\t\t\t}\n\t\t\t\tsgiu.indices.push( indicesPointer );\n\n\t\t\t} else {\n\n\t\t\t\tupdateRawObjectDescriptionInUse();\n\n\t\t\t}\n\t\t\tthis.faceCount++;\n\t\t};\n\n\t\t/*\n\t\t * Support for lines with or without texture. First element in indexArray is the line identification\n\t\t * 0: \"f vertex/uv\t\tvertex/uv \t\t...\"\n\t\t * 1: \"f vertex\t\t\tvertex \t\t\t...\"\n\t\t */\n\t\tRawMesh.prototype.buildLineVvt = function ( lineArray ) {\n\t\t\tfor ( var i = 1, length = lineArray.length; i < length; i ++ ) {\n\n\t\t\t\tthis.vertices.push( parseInt( lineArray[ i ] ) );\n\t\t\t\tthis.uvs.push( parseInt( lineArray[ i ] ) );\n\n\t\t\t}\n\t\t};\n\n\t\tRawMesh.prototype.buildLineV = function ( lineArray ) {\n\t\t\tfor ( var i = 1, length = lineArray.length; i < length; i++ ) {\n\n\t\t\t\tthis.vertices.push( parseInt( lineArray[ i ] ) );\n\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts\n\t\t */\n\t\tRawMesh.prototype.finalize = function () {\n\t\t\tvar rawObjectDescriptionsTemp = [];\n\t\t\tvar rawObjectDescription;\n\t\t\tvar absoluteVertexCount = 0;\n\t\t\tvar absoluteIndexMappingsCount = 0;\n\t\t\tvar absoluteIndexCount = 0;\n\t\t\tvar absoluteColorCount = 0;\n\t\t\tvar absoluteNormalCount = 0;\n\t\t\tvar absoluteUvCount = 0;\n\t\t\tvar indices;\n\t\t\tfor ( var name in this.subGroups ) {\n\n\t\t\t\trawObjectDescription = this.subGroups[ name ];\n\t\t\t\tif ( rawObjectDescription.vertices.length > 0 ) {\n\n\t\t\t\t\tindices = rawObjectDescription.indices;\n\t\t\t\t\tif ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {\n\n\t\t\t\t\t\tfor ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;\n\n\t\t\t\t\t}\n\t\t\t\t\trawObjectDescriptionsTemp.push( rawObjectDescription );\n\t\t\t\t\tabsoluteVertexCount += rawObjectDescription.vertices.length;\n\t\t\t\t\tabsoluteIndexMappingsCount += rawObjectDescription.indexMappingsCount;\n\t\t\t\t\tabsoluteIndexCount += rawObjectDescription.indices.length;\n\t\t\t\t\tabsoluteColorCount += rawObjectDescription.colors.length;\n\t\t\t\t\tabsoluteUvCount += rawObjectDescription.uvs.length;\n\t\t\t\t\tabsoluteNormalCount += rawObjectDescription.normals.length;\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// do not continue if no result\n\t\t\tvar result = null;\n\t\t\tif ( rawObjectDescriptionsTemp.length > 0 ) {\n\n\t\t\t\tresult = {\n\t\t\t\t\tname: this.groupName !== '' ? this.groupName : this.objectName,\n\t\t\t\t\tsubGroups: rawObjectDescriptionsTemp,\n\t\t\t\t\tabsoluteVertexCount: absoluteVertexCount,\n\t\t\t\t\tabsoluteIndexCount: absoluteIndexCount,\n\t\t\t\t\tabsoluteColorCount: absoluteColorCount,\n\t\t\t\t\tabsoluteNormalCount: absoluteNormalCount,\n\t\t\t\t\tabsoluteUvCount: absoluteUvCount,\n\t\t\t\t\tfaceCount: this.faceCount,\n\t\t\t\t\tdoubleIndicesCount: this.doubleIndicesCount\n\t\t\t\t};\n\n\t\t\t}\n\t\t\treturn result;\n\t\t};\n\n\t\tRawMesh.prototype.createReport = function () {\n\t\t\tvar report = {\n\t\t\t\tobjectName: this.objectName,\n\t\t\t\tgroupName: this.groupName,\n\t\t\t\tmtllibName: this.mtllibName,\n\t\t\t\tvertexCount: this.vertices.length / 3,\n\t\t\t\tnormalCount: this.normals.length / 3,\n\t\t\t\tuvCount: this.uvs.length / 2,\n\t\t\t\tsmoothingGroupCount: this.smoothingGroupCount,\n\t\t\t\tmtlCount: this.mtlCount,\n\t\t\t\tsubGroups: this.subGroups.length\n\t\t\t};\n\n\t\t\treturn report;\n\t\t};\n\n\t\treturn RawMesh;\n\t})();\n\n\t/**\n\t * Descriptive information and data (vertices, normals, uvs) to passed on to mesh building function.\n\t * @class\n\t *\n\t * @param {string} objectName Name of the mesh\n\t * @param {string} groupName Name of the group\n\t * @param {string} materialName Name of the material\n\t * @param {number} smoothingGroup Normalized smoothingGroup (0: flat shading, 1: smooth shading)\n\t */\n\tvar RawMeshSubGroup = (function () {\n\n\t\tfunction RawMeshSubGroup( objectName, groupName, materialName, smoothingGroup ) {\n\t\t\tthis.objectName = objectName;\n\t\t\tthis.groupName = groupName;\n\t\t\tthis.materialName = materialName;\n\t\t\tthis.smoothingGroup = smoothingGroup;\n\t\t\tthis.vertices = [];\n\t\t\tthis.indexMappingsCount = 0;\n\t\t\tthis.indexMappings = [];\n\t\t\tthis.indices = [];\n\t\t\tthis.colors = [];\n\t\t\tthis.uvs = [];\n\t\t\tthis.normals = [];\n\t\t}\n\n\t\treturn RawMeshSubGroup;\n\t})();\n\n\tOBJLoader2.prototype._checkFiles = function ( resources ) {\n\t\tvar resource;\n\t\tvar result = {\n\t\t\tmtl: null,\n\t\t\tobj: null\n\t\t};\n\t\tfor ( var index in resources ) {\n\n\t\t\tresource = resources[ index ];\n\t\t\tif ( ! Validator.isValid( resource.name ) ) continue;\n\t\t\tif ( Validator.isValid( resource.content ) ) {\n\n\t\t\t\tif ( resource.extension === 'OBJ' ) {\n\n\t\t\t\t\t// fast-fail on bad type\n\t\t\t\t\tif ( ! ( resource.content instanceof Uint8Array ) ) throw 'Provided content is not of type arraybuffer! Aborting...';\n\t\t\t\t\tresult.obj = resource;\n\n\t\t\t\t} else if ( resource.extension === 'MTL' && Validator.isValid( resource.name ) ) {\n\n\t\t\t\t\tif ( ! ( typeof( resource.content ) === 'string' || resource.content instanceof String ) ) throw 'Provided  content is not of type String! Aborting...';\n\t\t\t\t\tresult.mtl = resource;\n\n\t\t\t\t} else if ( resource.extension === \"ZIP\" ) {\n\t\t\t\t\t// ignore\n\n\t\t\t\t} else {\n\n\t\t\t\t\tthrow 'Unidentified resource \"' + resource.name + '\": ' + resource.url;\n\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\t// fast-fail on bad type\n\t\t\t\tif ( ! ( typeof( resource.name ) === 'string' || resource.name instanceof String ) ) throw 'Provided file is not properly defined! Aborting...';\n\t\t\t\tif ( resource.extension === 'OBJ' ) {\n\n\t\t\t\t\tresult.obj = resource;\n\n\t\t\t\t} else if ( resource.extension === 'MTL' ) {\n\n\t\t\t\t\tresult.mtl = resource;\n\n\t\t\t\t} else if ( resource.extension === \"ZIP\" ) {\n\t\t\t\t\t// ignore\n\n\t\t\t\t} else {\n\n\t\t\t\t\tthrow 'Unidentified resource \"' + resource.name + '\": ' + resource.url;\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t};\n\n\t/**\n\t * Utility method for loading an mtl file according resource description.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {string} url URL to the file\n\t * @param {string} name The name of the object\n\t * @param {Object} content The file content as arraybuffer or text\n\t * @param {function} callbackOnLoad\n\t * @param {string} [crossOrigin] CORS value\n\t */\n\tOBJLoader2.prototype.loadMtl = function ( url, name, content, callbackOnLoad, crossOrigin ) {\n\t\tvar resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );\n\t\tresource.setContent( content );\n\t\tthis._loadMtl( resource, callbackOnLoad, crossOrigin );\n\t};\n\n\t/**\n\t * Utility method for loading an mtl file according resource description.\n\t * @memberOf THREE.OBJLoader2\n\t *\n\t * @param {THREE.LoaderSupport.ResourceDescriptor} resource\n\t * @param {function} callbackOnLoad\n\t * @param {string} [crossOrigin] CORS value\n\t */\n\tOBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {\n\t\tif ( Validator.isValid( resource ) ) this.logger.logTimeStart( 'Loading MTL: ' + resource.name );\n\n\t\tvar materials = [];\n\t\tvar scope = this;\n\t\tvar processMaterials = function ( materialCreator ) {\n\t\t\tvar materialCreatorMaterials = [];\n\t\t\tif ( Validator.isValid( materialCreator ) ) {\n\n\t\t\t\tmaterialCreator.preload();\n\t\t\t\tmaterialCreatorMaterials = materialCreator.materials;\n\t\t\t\tfor ( var materialName in materialCreatorMaterials ) {\n\n\t\t\t\t\tif ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {\n\n\t\t\t\t\t\tmaterials[ materialName ] = materialCreatorMaterials[ materialName ];\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( Validator.isValid( resource ) ) scope.logger.logTimeEnd( 'Loading MTL: ' + resource.name );\n\t\t\tcallbackOnLoad( materials );\n\t\t};\n\n\t\tvar mtlLoader = new THREE.MTLLoader();\n\t\tcrossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );\n\t\tmtlLoader.setCrossOrigin( crossOrigin );\n\n\t\t// fast-fail\n\t\tif ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {\n\n\t\t\tprocessMaterials();\n\n\t\t} else {\n\n\t\t\tmtlLoader.setPath( resource.path );\n\t\t\tif ( Validator.isValid( resource.content ) ) {\n\n\t\t\t\tprocessMaterials( Validator.isValid( resource.content ) ? mtlLoader.parse( resource.content ) : null );\n\n\t\t\t} else if ( Validator.isValid( resource.url ) ) {\n\n\t\t\t\tvar onError = function ( event ) {\n\t\t\t\t\tvar output = 'Error occurred while downloading \"' + resource.url + '\"';\n\t\t\t\t\tthis.logger.logError( output + ': ' + event );\n\t\t\t\t\tthrow output;\n\t\t\t\t};\n\n\t\t\t\tmtlLoader.load( resource.name, processMaterials, undefined, onError );\n\n\t\t\t}\n\t\t}\n\t};\n\n\treturn OBJLoader2;\n})();\n"
  },
  {
    "path": "OrbitControls.js",
    "content": "/**\n * @author qiao / https://github.com/qiao\n * @author mrdoob / http://mrdoob.com\n * @author alteredq / http://alteredqualia.com/\n * @author WestLangley / http://github.com/WestLangley\n * @author erich666 / http://erichaines.com\n */\n\n// This set of controls performs orbiting, dollying (zooming), and panning.\n// Unlike TrackballControls, it maintains the \"up\" direction object.up (+Y by default).\n//\n//    Orbit - left mouse / touch: one finger move\n//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish\n//    Pan - right mouse, or arrow keys / touch: three finger swipe\n\nTHREE.OrbitControls = function ( object, domElement ) {\n\n\tthis.object = object;\n\n\tthis.domElement = ( domElement !== undefined ) ? domElement : document;\n\n\t// Set to false to disable this control\n\tthis.enabled = true;\n\n\t// \"target\" sets the location of focus, where the object orbits around\n\tthis.target = new THREE.Vector3();\n\n\t// How far you can dolly in and out ( PerspectiveCamera only )\n\tthis.minDistance = 0;\n\tthis.maxDistance = Infinity;\n\n\t// How far you can zoom in and out ( OrthographicCamera only )\n\tthis.minZoom = 0;\n\tthis.maxZoom = Infinity;\n\n\t// How far you can orbit vertically, upper and lower limits.\n\t// Range is 0 to Math.PI radians.\n\tthis.minPolarAngle = 0; // radians\n\tthis.maxPolarAngle = Math.PI; // radians\n\n\t// How far you can orbit horizontally, upper and lower limits.\n\t// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].\n\tthis.minAzimuthAngle = - Infinity; // radians\n\tthis.maxAzimuthAngle = Infinity; // radians\n\n\t// Set to true to enable damping (inertia)\n\t// If damping is enabled, you must call controls.update() in your animation loop\n\tthis.enableDamping = false;\n\tthis.dampingFactor = 0.25;\n\n\t// This option actually enables dollying in and out; left as \"zoom\" for backwards compatibility.\n\t// Set to false to disable zooming\n\tthis.enableZoom = true;\n\tthis.zoomSpeed = 1.0;\n\n\t// Set to false to disable rotating\n\tthis.enableRotate = true;\n\tthis.rotateSpeed = 1.0;\n\n\t// Set to false to disable panning\n\tthis.enablePan = true;\n\tthis.keyPanSpeed = 7.0;\t// pixels moved per arrow key push\n\n\t// Set to true to automatically rotate around the target\n\t// If auto-rotate is enabled, you must call controls.update() in your animation loop\n\tthis.autoRotate = false;\n\tthis.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60\n\n\t// Set to false to disable use of the keys\n\tthis.enableKeys = true;\n\n\t// The four arrow keys\n\tthis.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };\n\n\t// Mouse buttons\n\tthis.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };\n\n\t// for reset\n\tthis.target0 = this.target.clone();\n\tthis.position0 = this.object.position.clone();\n\tthis.zoom0 = this.object.zoom;\n\n\t//\n\t// public methods\n\t//\n\n\tthis.getPolarAngle = function () {\n\n\t\treturn spherical.phi;\n\n\t};\n\n\tthis.getAzimuthalAngle = function () {\n\n\t\treturn spherical.theta;\n\n\t};\n\n\tthis.saveState = function () {\n\n\t\tscope.target0.copy( scope.target );\n\t\tscope.position0.copy( scope.object.position );\n\t\tscope.zoom0 = scope.object.zoom;\n\n\t};\n\n\tthis.reset = function () {\n\n\t\tscope.target.copy( scope.target0 );\n\t\tscope.object.position.copy( scope.position0 );\n\t\tscope.object.zoom = scope.zoom0;\n\n\t\tscope.object.updateProjectionMatrix();\n\t\tscope.dispatchEvent( changeEvent );\n\n\t\tscope.update();\n\n\t\tstate = STATE.NONE;\n\n\t};\n\n\t// this method is exposed, but perhaps it would be better if we can make it private...\n\tthis.update = function () {\n\n\t\tvar offset = new THREE.Vector3();\n\n\t\t// so camera.up is the orbit axis\n\t\tvar quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );\n\t\tvar quatInverse = quat.clone().inverse();\n\n\t\tvar lastPosition = new THREE.Vector3();\n\t\tvar lastQuaternion = new THREE.Quaternion();\n\n\t\treturn function update() {\n\n\t\t\tvar position = scope.object.position;\n\n\t\t\toffset.copy( position ).sub( scope.target );\n\n\t\t\t// rotate offset to \"y-axis-is-up\" space\n\t\t\toffset.applyQuaternion( quat );\n\n\t\t\t// angle from z-axis around y-axis\n\t\t\tspherical.setFromVector3( offset );\n\n\t\t\tif ( scope.autoRotate && state === STATE.NONE ) {\n\n\t\t\t\trotateLeft( getAutoRotationAngle() );\n\n\t\t\t}\n\n\t\t\tspherical.theta += sphericalDelta.theta;\n\t\t\tspherical.phi += sphericalDelta.phi;\n\n\t\t\t// restrict theta to be between desired limits\n\t\t\tspherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );\n\n\t\t\t// restrict phi to be between desired limits\n\t\t\tspherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );\n\n\t\t\tspherical.makeSafe();\n\n\n\t\t\tspherical.radius *= scale;\n\n\t\t\t// restrict radius to be between desired limits\n\t\t\tspherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );\n\n\t\t\t// move target to panned location\n\t\t\tscope.target.add( panOffset );\n\n\t\t\toffset.setFromSpherical( spherical );\n\n\t\t\t// rotate offset back to \"camera-up-vector-is-up\" space\n\t\t\toffset.applyQuaternion( quatInverse );\n\n\t\t\tposition.copy( scope.target ).add( offset );\n\n\t\t\tscope.object.lookAt( scope.target );\n\n\t\t\tif ( scope.enableDamping === true ) {\n\n\t\t\t\tsphericalDelta.theta *= ( 1 - scope.dampingFactor );\n\t\t\t\tsphericalDelta.phi *= ( 1 - scope.dampingFactor );\n\n\t\t\t} else {\n\n\t\t\t\tsphericalDelta.set( 0, 0, 0 );\n\n\t\t\t}\n\n\t\t\tscale = 1;\n\t\t\tpanOffset.set( 0, 0, 0 );\n\n\t\t\t// update condition is:\n\t\t\t// min(camera displacement, camera rotation in radians)^2 > EPS\n\t\t\t// using small-angle approximation cos(x/2) = 1 - x^2 / 8\n\n\t\t\tif ( zoomChanged ||\n\t\t\t\tlastPosition.distanceToSquared( scope.object.position ) > EPS ||\n\t\t\t\t8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {\n\n\t\t\t\tscope.dispatchEvent( changeEvent );\n\n\t\t\t\tlastPosition.copy( scope.object.position );\n\t\t\t\tlastQuaternion.copy( scope.object.quaternion );\n\t\t\t\tzoomChanged = false;\n\n\t\t\t\treturn true;\n\n\t\t\t}\n\n\t\t\treturn false;\n\n\t\t};\n\n\t}();\n\n\tthis.dispose = function () {\n\n\t\tscope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );\n\t\tscope.domElement.removeEventListener( 'mousedown', onMouseDown, false );\n\t\tscope.domElement.removeEventListener( 'wheel', onMouseWheel, false );\n\n\t\tscope.domElement.removeEventListener( 'touchstart', onTouchStart, false );\n\t\tscope.domElement.removeEventListener( 'touchend', onTouchEnd, false );\n\t\tscope.domElement.removeEventListener( 'touchmove', onTouchMove, false );\n\n\t\tdocument.removeEventListener( 'mousemove', onMouseMove, false );\n\t\tdocument.removeEventListener( 'mouseup', onMouseUp, false );\n\n\t\twindow.removeEventListener( 'keydown', onKeyDown, false );\n\n\t\t//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?\n\n\t};\n\n\t//\n\t// internals\n\t//\n\n\tvar scope = this;\n\n\tvar changeEvent = { type: 'change' };\n\tvar startEvent = { type: 'start' };\n\tvar endEvent = { type: 'end' };\n\n\tvar STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };\n\n\tvar state = STATE.NONE;\n\n\tvar EPS = 0.000001;\n\n\t// current position in spherical coordinates\n\tvar spherical = new THREE.Spherical();\n\tvar sphericalDelta = new THREE.Spherical();\n\n\tvar scale = 1;\n\tvar panOffset = new THREE.Vector3();\n\tvar zoomChanged = false;\n\n\tvar rotateStart = new THREE.Vector2();\n\tvar rotateEnd = new THREE.Vector2();\n\tvar rotateDelta = new THREE.Vector2();\n\n\tvar panStart = new THREE.Vector2();\n\tvar panEnd = new THREE.Vector2();\n\tvar panDelta = new THREE.Vector2();\n\n\tvar dollyStart = new THREE.Vector2();\n\tvar dollyEnd = new THREE.Vector2();\n\tvar dollyDelta = new THREE.Vector2();\n\n\tfunction getAutoRotationAngle() {\n\n\t\treturn 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;\n\n\t}\n\n\tfunction getZoomScale() {\n\n\t\treturn Math.pow( 0.95, scope.zoomSpeed );\n\n\t}\n\n\tfunction rotateLeft( angle ) {\n\n\t\tsphericalDelta.theta -= angle;\n\n\t}\n\n\tfunction rotateUp( angle ) {\n\n\t\tsphericalDelta.phi -= angle;\n\n\t}\n\n\tvar panLeft = function () {\n\n\t\tvar v = new THREE.Vector3();\n\n\t\treturn function panLeft( distance, objectMatrix ) {\n\n\t\t\tv.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix\n\t\t\tv.multiplyScalar( - distance );\n\n\t\t\tpanOffset.add( v );\n\n\t\t};\n\n\t}();\n\n\tvar panUp = function () {\n\n\t\tvar v = new THREE.Vector3();\n\n\t\treturn function panUp( distance, objectMatrix ) {\n\n\t\t\tv.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix\n\t\t\tv.multiplyScalar( distance );\n\n\t\t\tpanOffset.add( v );\n\n\t\t};\n\n\t}();\n\n\t// deltaX and deltaY are in pixels; right and down are positive\n\tvar pan = function () {\n\n\t\tvar offset = new THREE.Vector3();\n\n\t\treturn function pan( deltaX, deltaY ) {\n\n\t\t\tvar element = scope.domElement === document ? scope.domElement.body : scope.domElement;\n\n\t\t\tif ( scope.object instanceof THREE.PerspectiveCamera ) {\n\n\t\t\t\t// perspective\n\t\t\t\tvar position = scope.object.position;\n\t\t\t\toffset.copy( position ).sub( scope.target );\n\t\t\t\tvar targetDistance = offset.length();\n\n\t\t\t\t// half of the fov is center to top of screen\n\t\t\t\ttargetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );\n\n\t\t\t\t// we actually don't use screenWidth, since perspective camera is fixed to screen height\n\t\t\t\tpanLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );\n\t\t\t\tpanUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );\n\n\t\t\t} else if ( scope.object instanceof THREE.OrthographicCamera ) {\n\n\t\t\t\t// orthographic\n\t\t\t\tpanLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );\n\t\t\t\tpanUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );\n\n\t\t\t} else {\n\n\t\t\t\t// camera neither orthographic nor perspective\n\t\t\t\tconsole.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );\n\t\t\t\tscope.enablePan = false;\n\n\t\t\t}\n\n\t\t};\n\n\t}();\n\n\tfunction dollyIn( dollyScale ) {\n\n\t\tif ( scope.object instanceof THREE.PerspectiveCamera ) {\n\n\t\t\tscale /= dollyScale;\n\n\t\t} else if ( scope.object instanceof THREE.OrthographicCamera ) {\n\n\t\t\tscope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );\n\t\t\tscope.object.updateProjectionMatrix();\n\t\t\tzoomChanged = true;\n\n\t\t} else {\n\n\t\t\tconsole.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );\n\t\t\tscope.enableZoom = false;\n\n\t\t}\n\n\t}\n\n\tfunction dollyOut( dollyScale ) {\n\n\t\tif ( scope.object instanceof THREE.PerspectiveCamera ) {\n\n\t\t\tscale *= dollyScale;\n\n\t\t} else if ( scope.object instanceof THREE.OrthographicCamera ) {\n\n\t\t\tscope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );\n\t\t\tscope.object.updateProjectionMatrix();\n\t\t\tzoomChanged = true;\n\n\t\t} else {\n\n\t\t\tconsole.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );\n\t\t\tscope.enableZoom = false;\n\n\t\t}\n\n\t}\n\n\t//\n\t// event callbacks - update the object state\n\t//\n\n\tfunction handleMouseDownRotate( event ) {\n\n\t\t//console.log( 'handleMouseDownRotate' );\n\n\t\trotateStart.set( event.clientX, event.clientY );\n\n\t}\n\n\tfunction handleMouseDownDolly( event ) {\n\n\t\t//console.log( 'handleMouseDownDolly' );\n\n\t\tdollyStart.set( event.clientX, event.clientY );\n\n\t}\n\n\tfunction handleMouseDownPan( event ) {\n\n\t\t//console.log( 'handleMouseDownPan' );\n\n\t\tpanStart.set( event.clientX, event.clientY );\n\n\t}\n\n\tfunction handleMouseMoveRotate( event ) {\n\n\t\t//console.log( 'handleMouseMoveRotate' );\n\n\t\trotateEnd.set( event.clientX, event.clientY );\n\t\trotateDelta.subVectors( rotateEnd, rotateStart );\n\n\t\tvar element = scope.domElement === document ? scope.domElement.body : scope.domElement;\n\n\t\t// rotating across whole screen goes 360 degrees around\n\t\trotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );\n\n\t\t// rotating up and down along whole screen attempts to go 360, but limited to 180\n\t\trotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );\n\n\t\trotateStart.copy( rotateEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleMouseMoveDolly( event ) {\n\n\t\t//console.log( 'handleMouseMoveDolly' );\n\n\t\tdollyEnd.set( event.clientX, event.clientY );\n\n\t\tdollyDelta.subVectors( dollyEnd, dollyStart );\n\n\t\tif ( dollyDelta.y > 0 ) {\n\n\t\t\tdollyIn( getZoomScale() );\n\n\t\t} else if ( dollyDelta.y < 0 ) {\n\n\t\t\tdollyOut( getZoomScale() );\n\n\t\t}\n\n\t\tdollyStart.copy( dollyEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleMouseMovePan( event ) {\n\n\t\t//console.log( 'handleMouseMovePan' );\n\n\t\tpanEnd.set( event.clientX, event.clientY );\n\n\t\tpanDelta.subVectors( panEnd, panStart );\n\n\t\tpan( panDelta.x, panDelta.y );\n\n\t\tpanStart.copy( panEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleMouseUp( event ) {\n\n\t\t// console.log( 'handleMouseUp' );\n\n\t}\n\n\tfunction handleMouseWheel( event ) {\n\n\t\t// console.log( 'handleMouseWheel' );\n\n\t\tif ( event.deltaY < 0 ) {\n\n\t\t\tdollyOut( getZoomScale() );\n\n\t\t} else if ( event.deltaY > 0 ) {\n\n\t\t\tdollyIn( getZoomScale() );\n\n\t\t}\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleKeyDown( event ) {\n\n\t\t//console.log( 'handleKeyDown' );\n\n\t\tswitch ( event.keyCode ) {\n\n\t\t\tcase scope.keys.UP:\n\t\t\t\tpan( 0, scope.keyPanSpeed );\n\t\t\t\tscope.update();\n\t\t\t\tbreak;\n\n\t\t\tcase scope.keys.BOTTOM:\n\t\t\t\tpan( 0, - scope.keyPanSpeed );\n\t\t\t\tscope.update();\n\t\t\t\tbreak;\n\n\t\t\tcase scope.keys.LEFT:\n\t\t\t\tpan( scope.keyPanSpeed, 0 );\n\t\t\t\tscope.update();\n\t\t\t\tbreak;\n\n\t\t\tcase scope.keys.RIGHT:\n\t\t\t\tpan( - scope.keyPanSpeed, 0 );\n\t\t\t\tscope.update();\n\t\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n\tfunction handleTouchStartRotate( event ) {\n\n\t\t//console.log( 'handleTouchStartRotate' );\n\n\t\trotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\n\t}\n\n\tfunction handleTouchStartDolly( event ) {\n\n\t\t//console.log( 'handleTouchStartDolly' );\n\n\t\tvar dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;\n\t\tvar dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;\n\n\t\tvar distance = Math.sqrt( dx * dx + dy * dy );\n\n\t\tdollyStart.set( 0, distance );\n\n\t}\n\n\tfunction handleTouchStartPan( event ) {\n\n\t\t//console.log( 'handleTouchStartPan' );\n\n\t\tpanStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\n\t}\n\n\tfunction handleTouchMoveRotate( event ) {\n\n\t\t//console.log( 'handleTouchMoveRotate' );\n\n\t\trotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\t\trotateDelta.subVectors( rotateEnd, rotateStart );\n\n\t\tvar element = scope.domElement === document ? scope.domElement.body : scope.domElement;\n\n\t\t// rotating across whole screen goes 360 degrees around\n\t\trotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );\n\n\t\t// rotating up and down along whole screen attempts to go 360, but limited to 180\n\t\trotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );\n\n\t\trotateStart.copy( rotateEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleTouchMoveDolly( event ) {\n\n\t\t//console.log( 'handleTouchMoveDolly' );\n\n\t\tvar dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;\n\t\tvar dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;\n\n\t\tvar distance = Math.sqrt( dx * dx + dy * dy );\n\n\t\tdollyEnd.set( 0, distance );\n\n\t\tdollyDelta.subVectors( dollyEnd, dollyStart );\n\n\t\tif ( dollyDelta.y > 0 ) {\n\n\t\t\tdollyOut( getZoomScale() );\n\n\t\t} else if ( dollyDelta.y < 0 ) {\n\n\t\t\tdollyIn( getZoomScale() );\n\n\t\t}\n\n\t\tdollyStart.copy( dollyEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleTouchMovePan( event ) {\n\n\t\t//console.log( 'handleTouchMovePan' );\n\n\t\tpanEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\n\t\tpanDelta.subVectors( panEnd, panStart );\n\n\t\tpan( panDelta.x, panDelta.y );\n\n\t\tpanStart.copy( panEnd );\n\n\t\tscope.update();\n\n\t}\n\n\tfunction handleTouchEnd( event ) {\n\n\t\t//console.log( 'handleTouchEnd' );\n\n\t}\n\n\t//\n\t// event handlers - FSM: listen for events and reset state\n\t//\n\n\tfunction onMouseDown( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\tevent.preventDefault();\n\n\t\tswitch ( event.button ) {\n\n\t\t\tcase scope.mouseButtons.ORBIT:\n\n\t\t\t\tif ( scope.enableRotate === false ) return;\n\n\t\t\t\thandleMouseDownRotate( event );\n\n\t\t\t\tstate = STATE.ROTATE;\n\n\t\t\t\tbreak;\n\n\t\t\tcase scope.mouseButtons.ZOOM:\n\n\t\t\t\tif ( scope.enableZoom === false ) return;\n\n\t\t\t\thandleMouseDownDolly( event );\n\n\t\t\t\tstate = STATE.DOLLY;\n\n\t\t\t\tbreak;\n\n\t\t\tcase scope.mouseButtons.PAN:\n\n\t\t\t\tif ( scope.enablePan === false ) return;\n\n\t\t\t\thandleMouseDownPan( event );\n\n\t\t\t\tstate = STATE.PAN;\n\n\t\t\t\tbreak;\n\n\t\t}\n\n\t\tif ( state !== STATE.NONE ) {\n\n\t\t\tdocument.addEventListener( 'mousemove', onMouseMove, false );\n\t\t\tdocument.addEventListener( 'mouseup', onMouseUp, false );\n\n\t\t\tscope.dispatchEvent( startEvent );\n\n\t\t}\n\n\t}\n\n\tfunction onMouseMove( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\tevent.preventDefault();\n\n\t\tswitch ( state ) {\n\n\t\t\tcase STATE.ROTATE:\n\n\t\t\t\tif ( scope.enableRotate === false ) return;\n\n\t\t\t\thandleMouseMoveRotate( event );\n\n\t\t\t\tbreak;\n\n\t\t\tcase STATE.DOLLY:\n\n\t\t\t\tif ( scope.enableZoom === false ) return;\n\n\t\t\t\thandleMouseMoveDolly( event );\n\n\t\t\t\tbreak;\n\n\t\t\tcase STATE.PAN:\n\n\t\t\t\tif ( scope.enablePan === false ) return;\n\n\t\t\t\thandleMouseMovePan( event );\n\n\t\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n\tfunction onMouseUp( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\thandleMouseUp( event );\n\n\t\tdocument.removeEventListener( 'mousemove', onMouseMove, false );\n\t\tdocument.removeEventListener( 'mouseup', onMouseUp, false );\n\n\t\tscope.dispatchEvent( endEvent );\n\n\t\tstate = STATE.NONE;\n\n\t}\n\n\tfunction onMouseWheel( event ) {\n\n\t\tif ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;\n\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\n\t\thandleMouseWheel( event );\n\n\t\tscope.dispatchEvent( startEvent ); // not sure why these are here...\n\t\tscope.dispatchEvent( endEvent );\n\n\t}\n\n\tfunction onKeyDown( event ) {\n\n\t\tif ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;\n\n\t\thandleKeyDown( event );\n\n\t}\n\n\tfunction onTouchStart( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\tswitch ( event.touches.length ) {\n\n\t\t\tcase 1:\t// one-fingered touch: rotate\n\n\t\t\t\tif ( scope.enableRotate === false ) return;\n\n\t\t\t\thandleTouchStartRotate( event );\n\n\t\t\t\tstate = STATE.TOUCH_ROTATE;\n\n\t\t\t\tbreak;\n\n\t\t\tcase 2:\t// two-fingered touch: dolly\n\n\t\t\t\tif ( scope.enableZoom === false ) return;\n\n\t\t\t\thandleTouchStartDolly( event );\n\n\t\t\t\tstate = STATE.TOUCH_DOLLY;\n\n\t\t\t\tbreak;\n\n\t\t\tcase 3: // three-fingered touch: pan\n\n\t\t\t\tif ( scope.enablePan === false ) return;\n\n\t\t\t\thandleTouchStartPan( event );\n\n\t\t\t\tstate = STATE.TOUCH_PAN;\n\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\n\t\t\t\tstate = STATE.NONE;\n\n\t\t}\n\n\t\tif ( state !== STATE.NONE ) {\n\n\t\t\tscope.dispatchEvent( startEvent );\n\n\t\t}\n\n\t}\n\n\tfunction onTouchMove( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\n\t\tswitch ( event.touches.length ) {\n\n\t\t\tcase 1: // one-fingered touch: rotate\n\n\t\t\t\tif ( scope.enableRotate === false ) return;\n\t\t\t\tif ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...\n\n\t\t\t\thandleTouchMoveRotate( event );\n\n\t\t\t\tbreak;\n\n\t\t\tcase 2: // two-fingered touch: dolly\n\n\t\t\t\tif ( scope.enableZoom === false ) return;\n\t\t\t\tif ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...\n\n\t\t\t\thandleTouchMoveDolly( event );\n\n\t\t\t\tbreak;\n\n\t\t\tcase 3: // three-fingered touch: pan\n\n\t\t\t\tif ( scope.enablePan === false ) return;\n\t\t\t\tif ( state !== STATE.TOUCH_PAN ) return; // is this needed?...\n\n\t\t\t\thandleTouchMovePan( event );\n\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\n\t\t\t\tstate = STATE.NONE;\n\n\t\t}\n\n\t}\n\n\tfunction onTouchEnd( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\thandleTouchEnd( event );\n\n\t\tscope.dispatchEvent( endEvent );\n\n\t\tstate = STATE.NONE;\n\n\t}\n\n\tfunction onContextMenu( event ) {\n\n\t\tif ( scope.enabled === false ) return;\n\n\t\tevent.preventDefault();\n\n\t}\n\n\t//\n\n\tscope.domElement.addEventListener( 'contextmenu', onContextMenu, false );\n\n\tscope.domElement.addEventListener( 'mousedown', onMouseDown, false );\n\tscope.domElement.addEventListener( 'wheel', onMouseWheel, false );\n\n\tscope.domElement.addEventListener( 'touchstart', onTouchStart, false );\n\tscope.domElement.addEventListener( 'touchend', onTouchEnd, false );\n\tscope.domElement.addEventListener( 'touchmove', onTouchMove, false );\n\n\twindow.addEventListener( 'keydown', onKeyDown, false );\n\n\t// force an update at start\n\n\tthis.update();\n\n};\n\nTHREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );\nTHREE.OrbitControls.prototype.constructor = THREE.OrbitControls;\n\nObject.defineProperties( THREE.OrbitControls.prototype, {\n\n\tcenter: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .center has been renamed to .target' );\n\t\t\treturn this.target;\n\n\t\t}\n\n\t},\n\n\t// backward compatibility\n\n\tnoZoom: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );\n\t\t\treturn ! this.enableZoom;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );\n\t\t\tthis.enableZoom = ! value;\n\n\t\t}\n\n\t},\n\n\tnoRotate: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );\n\t\t\treturn ! this.enableRotate;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );\n\t\t\tthis.enableRotate = ! value;\n\n\t\t}\n\n\t},\n\n\tnoPan: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );\n\t\t\treturn ! this.enablePan;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );\n\t\t\tthis.enablePan = ! value;\n\n\t\t}\n\n\t},\n\n\tnoKeys: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );\n\t\t\treturn ! this.enableKeys;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );\n\t\t\tthis.enableKeys = ! value;\n\n\t\t}\n\n\t},\n\n\tstaticMoving: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );\n\t\t\treturn ! this.enableDamping;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );\n\t\t\tthis.enableDamping = ! value;\n\n\t\t}\n\n\t},\n\n\tdynamicDampingFactor: {\n\n\t\tget: function () {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );\n\t\t\treturn this.dampingFactor;\n\n\t\t},\n\n\t\tset: function ( value ) {\n\n\t\t\tconsole.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );\n\t\t\tthis.dampingFactor = value;\n\n\t\t}\n\n\t}\n\n} );\n"
  },
  {
    "path": "README.md",
    "content": ""
  },
  {
    "path": "obj/girl/obj.mtl",
    "content": "# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware\r\n# �������ļ�:23.03.2015 22:13:02\r\n\r\nnewmtl wire_154215229\r\n\tNs 32\r\n\td 1\r\n\tTr 0\r\n\tTf 1 1 1\r\n\tillum 2\r\n\tKa 0.6039 0.8431 0.8980\r\n\tKd 0.6039 0.8431 0.8980\r\n\tKs 0.3500 0.3500 0.3500\r\n\r\nnewmtl SkinHip_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinTorso_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl PubicHair_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\r\nnewmtl SkinNeck_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinHead_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka Cindy_MakeUpblue.jpg\r\n\tmap_Kd Cindy_MakeUpblue.jpg\r\n\r\nnewmtl SkinScalp_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyHeadMap_natural.jpg\r\n\tmap_Kd CindyHeadMap_natural.jpg\r\n\r\nnewmtl Lips_1\r\n\tNs 200.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka Cindy_Lipsvioletred.jpg\r\n\tmap_Kd Cindy_Lipsvioletred.jpg\r\n\r\nnewmtl EyeSocket_1\r\n\tNs 800.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka Cindy_MakeUpblue.jpg\r\n\tmap_Kd Cindy_MakeUpblue.jpg\r\n\r\nnewmtl InnerMouth_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyHeadMap_natural.jpg\r\n\tmap_Kd CindyHeadMap_natural.jpg\r\n\r\nnewmtl Nostrils_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 0.0000 0.0000 0.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyHeadMap_natural.jpg\r\n\tmap_Kd CindyHeadMap_natural.jpg\r\n\r\nnewmtl Lacrimal_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyHeadMap_natural.jpg\r\n\tmap_Kd CindyHeadMap_natural.jpg\r\n\r\nnewmtl Eyelashes_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 0.0588 0.0588 0.0588\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\r\nnewmtl Eyebrows_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\r\nnewmtl Teeth_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\r\nnewmtl Gums_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 0.6078 0.0000 0.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\r\nnewmtl Tongue_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyHeadMap_natural.jpg\r\n\tmap_Kd CindyHeadMap_natural.jpg\r\n\r\nnewmtl LCornea_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeLeft_darkblue.jpg\r\n\tmap_Kd EyeLeft_darkblue.jpg\r\n\r\nnewmtl LEyewhite_1\r\n\tNs 650.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeLeft_darkblue.jpg\r\n\tmap_Kd EyeLeft_darkblue.jpg\r\n\r\nnewmtl LPupil_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeLeft_darkblue.jpg\r\n\tmap_Kd EyeLeft_darkblue.jpg\r\n\r\nnewmtl LIris_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeLeft_darkblue.jpg\r\n\tmap_Kd EyeLeft_darkblue.jpg\r\n\r\nnewmtl RCornea_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeRight_darkblue.jpg\r\n\tmap_Kd EyeRight_darkblue.jpg\r\n\r\nnewmtl REyewhite_1\r\n\tNs 650.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeRight_darkblue.jpg\r\n\tmap_Kd EyeRight_darkblue.jpg\r\n\r\nnewmtl RPupil_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeRight_darkblue.jpg\r\n\tmap_Kd EyeRight_darkblue.jpg\r\n\r\nnewmtl RIris_1\r\n\tNs 1000.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka EyeRight_darkblue.jpg\r\n\tmap_Kd EyeRight_darkblue.jpg\r\n\r\nnewmtl Nipples_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinForearm_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinArm_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinHand_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl Fingernails_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0118 0.0118 0.0118\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyNailsMap_violetred.jpg\r\n\tmap_Kd CindyNailsMap_violetred.jpg\r\n\r\nnewmtl SkinLeg_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl SkinFeet_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyBodyMap_natural.jpg\r\n\tmap_Kd CindyBodyMap_natural.jpg\r\n\r\nnewmtl Toenails_1\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0118 0.0118 0.0118\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka CindyNailsMap_violetred.jpg\r\n\tmap_Kd CindyNailsMap_violetred.jpg\r\n\r\nnewmtl Outer_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n\r\nnewmtl OuterBangs_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n\r\nnewmtl InnerBangs_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n\r\nnewmtl Middle_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n\r\nnewmtl Inner_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n\r\nnewmtl Scull_2\r\n\tNs 300.0000\r\n\tNi 1.5000\r\n\td 1.0000\r\n\tTr 0.0000\r\n\tTf 1.0000 1.0000 1.0000 \r\n\tillum 2\r\n\tKa 0.0000 0.0000 0.0000\r\n\tKd 1.0000 1.0000 1.0000\r\n\tKs 0.0000 0.0000 0.0000\r\n\tKe 0.0000 0.0000 0.0000\r\n\tmap_Ka ErikaHR_03.jpg\r\n\tmap_Kd ErikaHR_03.jpg\r\n"
  }
]