master 5f02817ce206 cached
11 files
16.1 MB
34.1k tokens
39 symbols
1 requests
Download .txt
Repository: WrenchDE/dizzyRetroSnake-three.js
Branch: master
Commit: 5f02817ce206
Files: 11
Total size: 16.1 MB

Directory structure:
gitextract_1p4v6ikq/

├── 1.html
├── 2.html
├── 3.html
├── 4.html
├── MTLLoader.js
├── OBJLoader.js
├── OBJLoader2.js
├── OrbitControls.js
├── README.md
└── obj/
    └── girl/
        ├── obj.mtl
        └── obj.obj

================================================
FILE CONTENTS
================================================

================================================
FILE: 1.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
      html,body,*{padding: 0;margin: 0;overflow: hidden}
      /* canvas { width: 100%; height: 100% } */
      #canvas-frame {
        border: none;
        cursor: pointer;
        width: 100%;
        height: 600px;
        background-color: #EEEEEE;
      }
    </style>
    <script src="../three.js"></script>
</head>
<body>
</body>
</html>
<script>
  // 场景
  let scene = new THREE.Scene()
  // 物品
  let geometry = new THREE.BoxGeometry(100, 100 ,100)
  let material = new THREE.MeshLambertMaterial({color: 0xff0000})
  // 网格(几何, 材料)
  let mesh = new THREE.Mesh(geometry, material)
  scene.add(mesh)
  // 灯光
  let light = new THREE.PointLight(0xffffff)
  light.position.set(300, 400, 200)
  scene.add(light)
  // 相机
  let camera = new THREE.PerspectiveCamera(40, 800/600, 1, 1000)
  camera.position.set(200, 200, 200)
  camera.lookAt(scene.position)
  // 渲染器
  let renderer = new THREE.WebGLRenderer()
  renderer.setSize(800, 600)
  document.body.appendChild(renderer.domElement)
  // 渲染(场景, 相机)
  renderer.render(scene, camera)
</script>

================================================
FILE: 2.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
      html,body,*{padding: 0;margin: 0;overflow: hidden}
      /* canvas { width: 100%; height: 100% } */
    </style>
    <script src="../three.js"></script>
    <script src="./OrbitControls.js"></script>
</head>
<body>
</body>
</html>
<script>
  // 场景
  let scene = new THREE.Scene()
  // 物品
  let geometry = new THREE.BoxGeometry(100, 100 ,100)
  let material = new THREE.MeshLambertMaterial({color: 0xff0000})
  // 网格(几何, 材料)
  let mesh = new THREE.Mesh(geometry, material)
  scene.add(mesh)
  // 灯光
  let light = new THREE.PointLight(0xffffff)
  light.position.set(300, 400, 200)
  scene.add(light)
  scene.add(new THREE.AmbientLight(0x333333))
  // 相机
  let camera = new THREE.PerspectiveCamera(40, 800/600, 1, 1000)
  camera.position.set(200, 200, 200)
  camera.lookAt(scene.position)
  // 渲染器
  let renderer = new THREE.WebGLRenderer()
  renderer.setSize(800, 600)
  document.body.appendChild(renderer.domElement)
  // 渲染(场景, 相机
  let render = () => {
    renderer.render(scene, camera)
  }
  render()
  let controls = new THREE.OrbitControls(camera)
  controls.addEventListener('change', render)
</script>

================================================
FILE: 3.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
      html,body,*{padding: 0;margin: 0;overflow: hidden;}
      html, body {height: 100%}
      /* canvas { width: 100%; height: 100% } */
    </style>
    <script src="../three.min.js"></script>
    <script src="./OrbitControls.js"></script>
    <script src="./OBJLoader.js"></script>
    <script src="./MTLLoader.js"></script>
    <script src="./stats.min.js"></script>
</head>
<body>
    <div id="container"></div>
</body>
</html>
<script>
  var container = document.getElementById( 'container' )
  stats = new Stats()
  container.appendChild( stats.dom )
  // 场景
  let scene = new THREE.Scene()
  // 物品
  let geometry = new THREE.BoxGeometry(100, 100 ,100)
  let material = new THREE.MeshLambertMaterial({color: 0xff0000})
  // 网格(几何, 材料)
  let mesh = new THREE.Mesh(geometry, material)
  // scene.add(mesh)
  var materials = []
  for (var i = 1; i < 7; ++i) {
    materials.push(new THREE.MeshBasicMaterial({
      map: THREE.ImageUtils.loadTexture('./img/' + i + '.jpg',//图片的路径  
      {}, function() {
        renderer.render(scene, camera)
      }),
      overdraw: true  
    }))
  }
  function createMesh(geom, imageFile) {
    var texture = THREE.ImageUtils.loadTexture(imageFile)
    var mat = new THREE.MeshPhongMaterial()
    mat.map = texture  // 材质的Map属性用于添加纹理
    var mesh = new THREE.Mesh(geom, mat)
    return mesh
  }
  var sphere = createMesh(new THREE.SphereGeometry(50, 50, 50), './img/1.jpg');
  scene.add(sphere)
  var cube = new THREE.Mesh(new THREE.CubeGeometry(50, 50, 50),  new THREE.MeshFaceMaterial(materials))
  // scene.add(cube)
  var mtlLoader = new THREE.MTLLoader()
  mtlLoader.setPath( './obj/girl/' )
  mtlLoader.load( 'obj.mtl', function( materials ) {
    materials.preload()
    var objLoader = new THREE.OBJLoader()
    objLoader.setMaterials( materials )
    objLoader.setPath( './obj/girl/' )
    objLoader.load( 'obj.obj', function ( object ) {
      console.log(object)
      object.scale.x = 0.2
      object.scale.y = 0.2
      object.scale.z = 0.2
      // setInterval(() => {
      //   object.position.y -=1
      // }, 100)
      scene.add( object )
    } )
  })
  // scene.background = new THREE.CubeTextureLoader()
  //   .setPath( './obj/pisa/' )
  //   .load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] )

  // 灯光
  // let light = new THREE.PointLight(0xffffff)
  // light.position.set(300, 400, 200)
  // scene.add(light)
  scene.add(new THREE.AmbientLight(0xffffff, 1))
  // 相机
  let camera = new THREE.PerspectiveCamera(45, document.body.clientWidth/document.body.clientHeight, 0.1, 1000)
  camera.position.set(document.body.clientWidth/2, document.body.clientHeight/2, 200)
  camera.lookAt(scene.position)
  // 渲染器
  let renderer = new THREE.WebGLRenderer()
  renderer.setSize(document.body.clientWidth  , document.body.clientHeight)
  document.body.appendChild(renderer.domElement)
  // 渲染(场景, 相机
  let render = () => {
    renderer.render(scene, camera)
  }
  render()
  let controls = new THREE.OrbitControls(camera)
  controls.addEventListener('change', render)
  let animate = () => {
    requestAnimationFrame(animate)
    stats.update()
    render()
  }
  animate()
  var helper = new THREE.GridHelper( 500, 50 )
  scene.add( helper )
</script>

================================================
FILE: 4.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>我会让你眼晕,哈哈哈</title>
    <style>
      html,body,*{padding: 0;margin: 0;overflow: hidden;}
      html, body {height: 100%}
      /* canvas { width: 100%; height: 100% } */
    </style>
    <script src="./three.min.js"></script>
    <script src="./OrbitControls.js"></script>
    <script src="./stats.min.js"></script>
</head>
<body>
    <div id="container"></div>
</body>
</html>
<script>
  // fps 监测
  var container = document.getElementById( 'container' )
  stats = new Stats()
  container.appendChild( stats.dom )
  // 场景 环境光
  let scene = new THREE.Scene()
  scene.add(new THREE.AmbientLight(0xffffff, 1))
  // 相机
  let camera = new THREE.PerspectiveCamera(45, document.body.clientWidth/document.body.clientHeight, 0.1, 1000)
  camera.position.set(0, 130, 30)
  // camera.position.set(0, 0, 5)
  camera.lookAt(scene.position)
  // 渲染器
  let renderer = new THREE.WebGLRenderer()
  renderer.setSize(document.body.clientWidth  , document.body.clientHeight)
  document.body.appendChild(renderer.domElement)
  // 渲染(场景, 相机
  let render = () => {
    renderer.render(scene, camera)
  }
  render()
  // 相机控制
  let controls = new THREE.OrbitControls(camera)
  controls.addEventListener('change', render)
  let animate = () => {
    requestAnimationFrame(animate)
    stats.update()
    render()
  }
  animate()
  // 地图渲染
  var map = new THREE.GridHelper( 100, 10 )
  scene.add( map )
  // 方格6个面随机图片
  let sixImg = () => {
    let materials = []
    for (var i = 1; i < 7; i++) {
      materials.push(new THREE.MeshBasicMaterial({
        map: THREE.ImageUtils.loadTexture('./img/' +  Math.ceil(Math.random() * i) + '.jpg',//图片的路径  
        {}, function() {
          renderer.render(scene, camera)
        }),
        overdraw: true  
      }))
    }
    return materials
  }
  // 方格位置
  let pos = (what, x, y, z) => {
    // console.log(what, x, y, z)
    what.position.x = x
    what.position.y = y
    what.position.z = z
  }
  class food {
    constructor () {
      this.fondSize = [10, 10, 10]
      this.foodxyz = [null, 5, 5, 5]
    }
    show (oo) {
      this.foodxyz = [
        null,
        parseInt(Math.random() * 10) * 10 - 45,
        5,
        parseInt(Math.random() * 10) * 10 - 45
      ]
      this.judge(oo)
    }
    // 判断食物是否刷在蛇里
    judge (oo) {
      oo.find(async (v, i, arr) => {
        if (arr[i][1] === this.foodxyz[1] & arr[i][3] === this.foodxyz[3]) {
          // 有的话重新调用
          this.show(oo)
        } else {
          // 没有的话 则创建
          this.foodxyz[0] = await new THREE.Mesh(new THREE.BoxGeometry(...this.fondSize),  new THREE.MeshFaceMaterial(sixImg()))
          pos(...this.foodxyz)
          scene.add(this.foodxyz[0])
          return
        }
      })
    }
  }
  class snake {
    constructor () {
      this.dir = 'left'
      this.snakSize = [10, 10, 10]
      this.snakexyz = [
        [
          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshLambertMaterial({color: 0x0000FF})),
          5, 5, 5
        ],
        [
          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshFaceMaterial(sixImg())),
          15, 5, 5
        ],
        [
          new THREE.Mesh(new THREE.BoxGeometry(...this.snakSize),  new THREE.MeshFaceMaterial(sixImg())),
          25, 5, 5
        ]
      ]
    }
    show () {
      for (let i in this.snakexyz) {
        pos(...this.snakexyz[i])
        scene.add(this.snakexyz[i][0])
      }
    }
    move () {
      for (let i = this.snakexyz.length - 1; i > 0; i--) {
        this.snakexyz[i][1] = this.snakexyz[i - 1][1]
        this.snakexyz[i][3] = this.snakexyz[i - 1][3]
      }
      switch (this.dir) {
        case 'up':
          this.snakexyz[0][3] -= 10
        break
        case 'left':
          this.snakexyz[0][1] -= 10
        break
        case 'down':
          this.snakexyz[0][3] += 10
        break
        case 'right':
          this.snakexyz[0][1] += 10
        break
      }
      this.throughWall()
      this.eat()
      this.show()
    }
    setDir (code) {
      this.dir = this.dir.toString()
      switch (code) {
        case 87:
          if (this.dir === 'down') {
            break
          } else {
            this.dir = 'up'
          }
          break
        case 65:
          if (this.dir === 'right') {
            break
          } else {
            this.dir = 'left'
          }
          break
        case 83:
            if (this.dir === 'up') {
              break
            } else {
              this.dir = 'down'
            }
          break
        case 68:
            if (this.dir === 'left') {
              break
            } else {
              this.dir = 'right'
            }
          break
      }
    }
    eat () {
      if (this.snakexyz[0][1] === f.foodxyz[1] & this.snakexyz[0][3] === f.foodxyz[3]) {
        this.snakexyz.push(f.foodxyz)
        f.show(s.snakexyz)
      }
    }
    throughWall () {
      if (this.snakexyz[0][3] < -45) {
        this.snakexyz[0][3] = 45
      }
      if (this.snakexyz[0][3] > 45) {
        this.snakexyz[0][3] = -45
      }
      if (this.snakexyz[0][1] < -45) {
        this.snakexyz[0][1] = 45
      }
      if (this.snakexyz[0][1] > 45) {
        this.snakexyz[0][1] = -45
      }
    }
  }
  let f = new food()
  let s = new snake()
  s.show()
  f.show(s.snakexyz)
  // s.move()
  setInterval( 's.move()' ,200)
  document.onkeydown = (e) => {
    s.setDir(e.which)
    render()
  }
</script>

================================================
FILE: MTLLoader.js
================================================
/**
 * Loads a Wavefront .mtl file specifying materials
 *
 * @author angelxuanchang
 */

THREE.MTLLoader = function ( manager ) {

	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

THREE.MTLLoader.prototype = {

	constructor: THREE.MTLLoader,

	/**
	 * Loads and parses a MTL asset from a URL.
	 *
	 * @param {String} url - URL to the MTL file.
	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
	 * @param {Function} [onProgress] - Callback for download progress.
	 * @param {Function} [onError] - Callback for download errors.
	 *
	 * @see setPath setTexturePath
	 *
	 * @note In order for relative texture references to resolve correctly
	 * you must call setPath and/or setTexturePath explicitly prior to load.
	 */
	load: function ( url, onLoad, onProgress, onError ) {

		var scope = this;

		var loader = new THREE.FileLoader( this.manager );
		loader.setPath( this.path );
		loader.load( url, function ( text ) {

			onLoad( scope.parse( text ) );

		}, onProgress, onError );

	},

	/**
	 * Set base path for resolving references.
	 * If set this path will be prepended to each loaded and found reference.
	 *
	 * @see setTexturePath
	 * @param {String} path
	 *
	 * @example
	 *     mtlLoader.setPath( 'assets/obj/' );
	 *     mtlLoader.load( 'my.mtl', ... );
	 */
	setPath: function ( path ) {

		this.path = path;

	},

	/**
	 * Set base path for resolving texture references.
	 * If set this path will be prepended found texture reference.
	 * If not set and setPath is, it will be used as texture base path.
	 *
	 * @see setPath
	 * @param {String} path
	 *
	 * @example
	 *     mtlLoader.setPath( 'assets/obj/' );
	 *     mtlLoader.setTexturePath( 'assets/textures/' );
	 *     mtlLoader.load( 'my.mtl', ... );
	 */
	setTexturePath: function ( path ) {

		this.texturePath = path;

	},

	setBaseUrl: function ( path ) {

		console.warn( 'THREE.MTLLoader: .setBaseUrl() is deprecated. Use .setTexturePath( path ) for texture path or .setPath( path ) for general base path instead.' );

		this.setTexturePath( path );

	},

	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;

	},

	setMaterialOptions: function ( value ) {

		this.materialOptions = value;

	},

	/**
	 * Parses a MTL file.
	 *
	 * @param {String} text - Content of MTL file
	 * @return {THREE.MTLLoader.MaterialCreator}
	 *
	 * @see setPath setTexturePath
	 *
	 * @note In order for relative texture references to resolve correctly
	 * you must call setPath and/or setTexturePath explicitly prior to parse.
	 */
	parse: function ( text ) {

		var lines = text.split( '\n' );
		var info = {};
		var delimiter_pattern = /\s+/;
		var materialsInfo = {};

		for ( var i = 0; i < lines.length; i ++ ) {

			var line = lines[ i ];
			line = line.trim();

			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {

				// Blank line or comment ignore
				continue;

			}

			var pos = line.indexOf( ' ' );

			var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
			key = key.toLowerCase();

			var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
			value = value.trim();

			if ( key === 'newmtl' ) {

				// New material

				info = { name: value };
				materialsInfo[ value ] = info;

			} else if ( info ) {

				if ( key === 'ka' || key === 'kd' || key === 'ks' ) {

					var ss = value.split( delimiter_pattern, 3 );
					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];

				} else {

					info[ key ] = value;

				}

			}

		}

		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.texturePath || this.path, this.materialOptions );
		materialCreator.setCrossOrigin( this.crossOrigin );
		materialCreator.setManager( this.manager );
		materialCreator.setMaterials( materialsInfo );
		return materialCreator;

	}

};

/**
 * Create a new THREE-MTLLoader.MaterialCreator
 * @param baseUrl - Url relative to which textures are loaded
 * @param options - Set of options on how to construct the materials
 *                  side: Which side to apply the material
 *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
 *                  wrap: What type of wrapping to apply for textures
 *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
 *                                Default: false, assumed to be already normalized
 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
 *                                  Default: false
 * @constructor
 */

THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {

	this.baseUrl = baseUrl || '';
	this.options = options;
	this.materialsInfo = {};
	this.materials = {};
	this.materialsArray = [];
	this.nameLookup = {};

	this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
	this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;

};

THREE.MTLLoader.MaterialCreator.prototype = {

	constructor: THREE.MTLLoader.MaterialCreator,

	crossOrigin: 'Anonymous',

	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;

	},

	setManager: function ( value ) {

		this.manager = value;

	},

	setMaterials: function ( materialsInfo ) {

		this.materialsInfo = this.convert( materialsInfo );
		this.materials = {};
		this.materialsArray = [];
		this.nameLookup = {};

	},

	convert: function ( materialsInfo ) {

		if ( ! this.options ) return materialsInfo;

		var converted = {};

		for ( var mn in materialsInfo ) {

			// Convert materials info into normalized form based on options

			var mat = materialsInfo[ mn ];

			var covmat = {};

			converted[ mn ] = covmat;

			for ( var prop in mat ) {

				var save = true;
				var value = mat[ prop ];
				var lprop = prop.toLowerCase();

				switch ( lprop ) {

					case 'kd':
					case 'ka':
					case 'ks':

						// Diffuse color (color under white light) using RGB values

						if ( this.options && this.options.normalizeRGB ) {

							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];

						}

						if ( this.options && this.options.ignoreZeroRGBs ) {

							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {

								// ignore

								save = false;

							}

						}

						break;

					default:

						break;

				}

				if ( save ) {

					covmat[ lprop ] = value;

				}

			}

		}

		return converted;

	},

	preload: function () {

		for ( var mn in this.materialsInfo ) {

			this.create( mn );

		}

	},

	getIndex: function ( materialName ) {

		return this.nameLookup[ materialName ];

	},

	getAsArray: function () {

		var index = 0;

		for ( var mn in this.materialsInfo ) {

			this.materialsArray[ index ] = this.create( mn );
			this.nameLookup[ mn ] = index;
			index ++;

		}

		return this.materialsArray;

	},

	create: function ( materialName ) {

		if ( this.materials[ materialName ] === undefined ) {

			this.createMaterial_( materialName );

		}

		return this.materials[ materialName ];

	},

	createMaterial_: function ( materialName ) {

		// Create material

		var scope = this;
		var mat = this.materialsInfo[ materialName ];
		var params = {

			name: materialName,
			side: this.side

		};

		function resolveURL( baseUrl, url ) {

			if ( typeof url !== 'string' || url === '' )
				return '';

			// Absolute URL
			if ( /^https?:\/\//i.test( url ) ) return url;

			return baseUrl + url;

		}

		function setMapForType( mapType, value ) {

			if ( params[ mapType ] ) return; // Keep the first encountered texture

			var texParams = scope.getTextureParams( value, params );
			var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );

			map.repeat.copy( texParams.scale );
			map.offset.copy( texParams.offset );

			map.wrapS = scope.wrap;
			map.wrapT = scope.wrap;

			params[ mapType ] = map;

		}

		for ( var prop in mat ) {

			var value = mat[ prop ];
			var n;

			if ( value === '' ) continue;

			switch ( prop.toLowerCase() ) {

				// Ns is material specular exponent

				case 'kd':

					// Diffuse color (color under white light) using RGB values

					params.color = new THREE.Color().fromArray( value );

					break;

				case 'ks':

					// Specular color (color when light is reflected from shiny surface) using RGB values
					params.specular = new THREE.Color().fromArray( value );

					break;

				case 'map_kd':

					// Diffuse texture map

					setMapForType( "map", value );

					break;

				case 'map_ks':

					// Specular map

					setMapForType( "specularMap", value );

					break;

				case 'norm':

					setMapForType( "normalMap", value );

					break;

				case 'map_bump':
				case 'bump':

					// Bump texture map

					setMapForType( "bumpMap", value );

					break;

				case 'ns':

					// The specular exponent (defines the focus of the specular highlight)
					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.

					params.shininess = parseFloat( value );

					break;

				case 'd':
					n = parseFloat( value );

					if ( n < 1 ) {

						params.opacity = n;
						params.transparent = true;

					}

					break;

				case 'tr':
					n = parseFloat( value );

					if ( n > 0 ) {

						params.opacity = 1 - n;
						params.transparent = true;

					}

					break;

				default:
					break;

			}

		}

		this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
		return this.materials[ materialName ];

	},

	getTextureParams: function ( value, matParams ) {

		var texParams = {

			scale: new THREE.Vector2( 1, 1 ),
			offset: new THREE.Vector2( 0, 0 )

		 };

		var items = value.split( /\s+/ );
		var pos;

		pos = items.indexOf( '-bm' );

		if ( pos >= 0 ) {

			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
			items.splice( pos, 2 );

		}

		pos = items.indexOf( '-s' );

		if ( pos >= 0 ) {

			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
			items.splice( pos, 4 ); // we expect 3 parameters here!

		}

		pos = items.indexOf( '-o' );

		if ( pos >= 0 ) {

			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
			items.splice( pos, 4 ); // we expect 3 parameters here!

		}

		texParams.url = items.join( ' ' ).trim();
		return texParams;

	},

	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {

		var texture;
		var loader = THREE.Loader.Handlers.get( url );
		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;

		if ( loader === null ) {

			loader = new THREE.TextureLoader( manager );

		}

		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
		texture = loader.load( url, onLoad, onProgress, onError );

		if ( mapping !== undefined ) texture.mapping = mapping;

		return texture;

	}

};


================================================
FILE: OBJLoader.js
================================================
/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.OBJLoader = ( function () {

	// o object_name | g group_name
	var object_pattern           = /^[og]\s*(.+)?/;
	// mtllib file_reference
	var material_library_pattern = /^mtllib /;
	// usemtl material_name
	var material_use_pattern     = /^usemtl /;

	function ParserState() {

		var state = {
			objects  : [],
			object   : {},

			vertices : [],
			normals  : [],
			uvs      : [],

			materialLibraries : [],

			startObject: function ( name, fromDeclaration ) {

				// If the current object (initial from reset) is not from a g/o declaration in the parsed
				// file. We need to use it for the first parsed g/o to keep things in sync.
				if ( this.object && this.object.fromDeclaration === false ) {

					this.object.name = name;
					this.object.fromDeclaration = ( fromDeclaration !== false );
					return;

				}

				var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );

				if ( this.object && typeof this.object._finalize === 'function' ) {

					this.object._finalize( true );

				}

				this.object = {
					name : name || '',
					fromDeclaration : ( fromDeclaration !== false ),

					geometry : {
						vertices : [],
						normals  : [],
						uvs      : []
					},
					materials : [],
					smooth : true,

					startMaterial: function ( name, libraries ) {

						var previous = this._finalize( false );

						// New usemtl declaration overwrites an inherited material, except if faces were declared
						// after the material, then it must be preserved for proper MultiMaterial continuation.
						if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {

							this.materials.splice( previous.index, 1 );

						}

						var material = {
							index      : this.materials.length,
							name       : name || '',
							mtllib     : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
							smooth     : ( previous !== undefined ? previous.smooth : this.smooth ),
							groupStart : ( previous !== undefined ? previous.groupEnd : 0 ),
							groupEnd   : -1,
							groupCount : -1,
							inherited  : false,

							clone: function ( index ) {
								var cloned = {
									index      : ( typeof index === 'number' ? index : this.index ),
									name       : this.name,
									mtllib     : this.mtllib,
									smooth     : this.smooth,
									groupStart : 0,
									groupEnd   : -1,
									groupCount : -1,
									inherited  : false
								};
								cloned.clone = this.clone.bind(cloned);
								return cloned;
							}
						};

						this.materials.push( material );

						return material;

					},

					currentMaterial: function () {

						if ( this.materials.length > 0 ) {
							return this.materials[ this.materials.length - 1 ];
						}

						return undefined;

					},

					_finalize: function ( end ) {

						var lastMultiMaterial = this.currentMaterial();
						if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) {

							lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
							lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
							lastMultiMaterial.inherited = false;

						}

						// Ignore objects tail materials if no face declarations followed them before a new o/g started.
						if ( end && this.materials.length > 1 ) {

							for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) {
								if ( this.materials[ mi ].groupCount <= 0 ) {
									this.materials.splice( mi, 1 );
								}
							}

						}

						// Guarantee at least one empty material, this makes the creation later more straight forward.
						if ( end && this.materials.length === 0 ) {

							this.materials.push({
								name   : '',
								smooth : this.smooth
							});

						}

						return lastMultiMaterial;

					}
				};

				// Inherit previous objects material.
				// Spec tells us that a declared material must be set to all objects until a new material is declared.
				// If a usemtl declaration is encountered while this new object is being parsed, it will
				// overwrite the inherited material. Exception being that there was already face declarations
				// to the inherited material, then it will be preserved for proper MultiMaterial continuation.

				if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {

					var declared = previousMaterial.clone( 0 );
					declared.inherited = true;
					this.object.materials.push( declared );

				}

				this.objects.push( this.object );

			},

			finalize: function () {

				if ( this.object && typeof this.object._finalize === 'function' ) {

					this.object._finalize( true );

				}

			},

			parseVertexIndex: function ( value, len ) {

				var index = parseInt( value, 10 );
				return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;

			},

			parseNormalIndex: function ( value, len ) {

				var index = parseInt( value, 10 );
				return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;

			},

			parseUVIndex: function ( value, len ) {

				var index = parseInt( value, 10 );
				return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;

			},

			addVertex: function ( a, b, c ) {

				var src = this.vertices;
				var dst = this.object.geometry.vertices;

				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
				dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
				dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );

			},

			addVertexLine: function ( a ) {

				var src = this.vertices;
				var dst = this.object.geometry.vertices;

				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );

			},

			addNormal: function ( a, b, c ) {

				var src = this.normals;
				var dst = this.object.geometry.normals;

				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
				dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
				dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );

			},

			addUV: function ( a, b, c ) {

				var src = this.uvs;
				var dst = this.object.geometry.uvs;

				dst.push( src[ a + 0 ], src[ a + 1 ] );
				dst.push( src[ b + 0 ], src[ b + 1 ] );
				dst.push( src[ c + 0 ], src[ c + 1 ] );

			},

			addUVLine: function ( a ) {

				var src = this.uvs;
				var dst = this.object.geometry.uvs;

				dst.push( src[ a + 0 ], src[ a + 1 ] );

			},

			addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {

				var vLen = this.vertices.length;

				var ia = this.parseVertexIndex( a, vLen );
				var ib = this.parseVertexIndex( b, vLen );
				var ic = this.parseVertexIndex( c, vLen );

				this.addVertex( ia, ib, ic );

				if ( ua !== undefined ) {

					var uvLen = this.uvs.length;

					ia = this.parseUVIndex( ua, uvLen );
					ib = this.parseUVIndex( ub, uvLen );
					ic = this.parseUVIndex( uc, uvLen );

					this.addUV( ia, ib, ic );

				}

				if ( na !== undefined ) {

					// Normals are many times the same. If so, skip function call and parseInt.
					var nLen = this.normals.length;
					ia = this.parseNormalIndex( na, nLen );

					ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
					ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );

					this.addNormal( ia, ib, ic );

				}

			},

			addLineGeometry: function ( vertices, uvs ) {

				this.object.geometry.type = 'Line';

				var vLen = this.vertices.length;
				var uvLen = this.uvs.length;

				for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {

					this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );

				}

				for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {

					this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );

				}

			}

		};

		state.startObject( '', false );

		return state;

	}

	//

	function OBJLoader( manager ) {

		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

		this.materials = null;

	};

	OBJLoader.prototype = {

		constructor: OBJLoader,

		load: function ( url, onLoad, onProgress, onError ) {

			var scope = this;

			var loader = new THREE.FileLoader( scope.manager );
			loader.setPath( this.path );
			loader.load( url, function ( text ) {

				onLoad( scope.parse( text ) );

			}, onProgress, onError );

		},

		setPath: function ( value ) {

			this.path = value;

		},

		setMaterials: function ( materials ) {

			this.materials = materials;

			return this;

		},

		parse: function ( text ) {

			console.time( 'OBJLoader' );

			var state = new ParserState();

			if ( text.indexOf( '\r\n' ) !== - 1 ) {

				// This is faster than String.split with regex that splits on both
				text = text.replace( /\r\n/g, '\n' );

			}

			if ( text.indexOf( '\\\n' ) !== - 1) {

				// join lines separated by a line continuation character (\)
				text = text.replace( /\\\n/g, '' );

			}

			var lines = text.split( '\n' );
			var line = '', lineFirstChar = '';
			var lineLength = 0;
			var result = [];

			// Faster to just trim left side of the line. Use if available.
			var trimLeft = ( typeof ''.trimLeft === 'function' );

			for ( var i = 0, l = lines.length; i < l; i ++ ) {

				line = lines[ i ];

				line = trimLeft ? line.trimLeft() : line.trim();

				lineLength = line.length;

				if ( lineLength === 0 ) continue;

				lineFirstChar = line.charAt( 0 );

				// @todo invoke passed in handler if any
				if ( lineFirstChar === '#' ) continue;

				if ( lineFirstChar === 'v' ) {

					var data = line.split( /\s+/ );

					switch ( data[ 0 ] ) {

						case 'v':
							state.vertices.push(
								parseFloat( data[ 1 ] ),
								parseFloat( data[ 2 ] ),
								parseFloat( data[ 3 ] )
							);
							break;
						case 'vn':
							state.normals.push(
								parseFloat( data[ 1 ] ),
								parseFloat( data[ 2 ] ),
								parseFloat( data[ 3 ] )
							);
							break;
						case 'vt':
							state.uvs.push(
								parseFloat( data[ 1 ] ),
								parseFloat( data[ 2 ] )
							);
							break;
					}

				} else if ( lineFirstChar === 'f' ) {

					var lineData = line.substr( 1 ).trim();
					var vertexData = lineData.split( /\s+/ );
					var faceVertices = [];

					// Parse the face vertex data into an easy to work with format

					for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {

						var vertex = vertexData[ j ];

						if ( vertex.length > 0 ) {

							var vertexParts = vertex.split( '/' );
							faceVertices.push( vertexParts );

						}

					}

					// Draw an edge between the first vertex and all subsequent vertices to form an n-gon

					var v1 = faceVertices[ 0 ];

					for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {

						var v2 = faceVertices[ j ];
						var v3 = faceVertices[ j + 1 ];

						state.addFace(
							v1[ 0 ], v2[ 0 ], v3[ 0 ],
							v1[ 1 ], v2[ 1 ], v3[ 1 ],
							v1[ 2 ], v2[ 2 ], v3[ 2 ]
						);

					}

				} else if ( lineFirstChar === 'l' ) {

					var lineParts = line.substring( 1 ).trim().split( " " );
					var lineVertices = [], lineUVs = [];

					if ( line.indexOf( "/" ) === - 1 ) {

						lineVertices = lineParts;

					} else {

						for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {

							var parts = lineParts[ li ].split( "/" );

							if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
							if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );

						}

					}
					state.addLineGeometry( lineVertices, lineUVs );

				} else if ( ( result = object_pattern.exec( line ) ) !== null ) {

					// o object_name
					// or
					// g group_name

					// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
					// var name = result[ 0 ].substr( 1 ).trim();
					var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );

					state.startObject( name );

				} else if ( material_use_pattern.test( line ) ) {

					// material

					state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );

				} else if ( material_library_pattern.test( line ) ) {

					// mtl file

					state.materialLibraries.push( line.substring( 7 ).trim() );

				} else if ( lineFirstChar === 's' ) {

					result = line.split( ' ' );

					// smooth shading

					// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
					// but does not define a usemtl for each face set.
					// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
					// This requires some care to not create extra material on each smooth value for "normal" obj files.
					// where explicit usemtl defines geometry groups.
					// Example asset: examples/models/obj/cerberus/Cerberus.obj

					/*
					 * http://paulbourke.net/dataformats/obj/
					 * or
					 * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
					 *
					 * From chapter "Grouping" Syntax explanation "s group_number":
					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
					 * than 0."
					 */
					if ( result.length > 1 ) {

						var value = result[ 1 ].trim().toLowerCase();
						state.object.smooth = ( value !== '0' && value !== 'off' );

					} else {

						// ZBrush can produce "s" lines #11707
						state.object.smooth = true;

					}
					var material = state.object.currentMaterial();
					if ( material ) material.smooth = state.object.smooth;

				} else {

					// Handle null terminated files without exception
					if ( line === '\0' ) continue;

					throw new Error( "Unexpected line: '" + line  + "'" );

				}

			}

			state.finalize();

			var container = new THREE.Group();
			container.materialLibraries = [].concat( state.materialLibraries );

			for ( var i = 0, l = state.objects.length; i < l; i ++ ) {

				var object = state.objects[ i ];
				var geometry = object.geometry;
				var materials = object.materials;
				var isLine = ( geometry.type === 'Line' );

				// Skip o/g line declarations that did not follow with any faces
				if ( geometry.vertices.length === 0 ) continue;

				var buffergeometry = new THREE.BufferGeometry();

				buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );

				if ( geometry.normals.length > 0 ) {

					buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );

				} else {

					buffergeometry.computeVertexNormals();

				}

				if ( geometry.uvs.length > 0 ) {

					buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );

				}

				// Create materials

				var createdMaterials = [];

				for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {

					var sourceMaterial = materials[ mi ];
					var material = undefined;

					if ( this.materials !== null ) {

						material = this.materials.create( sourceMaterial.name );

						// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
						if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {

							var materialLine = new THREE.LineBasicMaterial();
							materialLine.copy( material );
							material = materialLine;

						}

					}

					if ( ! material ) {

						material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() );
						material.name = sourceMaterial.name;

					}

					material.flatShading = sourceMaterial.smooth ? false : true;

					createdMaterials.push(material);

				}

				// Create mesh

				var mesh;

				if ( createdMaterials.length > 1 ) {

					for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {

						var sourceMaterial = materials[ mi ];
						buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );

					}

					mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials ) : new THREE.LineSegments( buffergeometry, createdMaterials ) );

				} else {

					mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) );
				}

				mesh.name = object.name;

				container.add( mesh );

			}

			console.timeEnd( 'OBJLoader' );

			return container;

		}

	};

	return OBJLoader;

} )();


================================================
FILE: OBJLoader2.js
================================================
/**
  * @author Kai Salmen / https://kaisalmen.de
  * Development repository: https://github.com/kaisalmen/WWOBJLoader
  */

'use strict';

if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }

/**
 * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
 * @class
 *
 * @param {THREE.DefaultLoadingManager} [manager] The loadingManager for the loader to use. Default is {@link THREE.DefaultLoadingManager}
 */
THREE.OBJLoader2 = (function () {

	var OBJLOADER2_VERSION = '2.0.0';
	var Commons = THREE.LoaderSupport.Commons;
	var Validator = THREE.LoaderSupport.Validator;
	var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;

	OBJLoader2.prototype = Object.create( THREE.LoaderSupport.Commons.prototype );
	OBJLoader2.prototype.constructor = OBJLoader2;

	function OBJLoader2( logger, manager ) {
		THREE.LoaderSupport.Commons.call( this, logger, manager );
		this.logger.logInfo( 'Using THREE.OBJLoader2 version: ' + OBJLOADER2_VERSION );

		this.materialPerSmoothingGroup = false;
		this.fileLoader = Validator.verifyInput( this.fileLoader, new THREE.FileLoader( this.manager ) );

		this.workerSupport = null;
		this.terminateWorkerOnLoad = true;
	};

	/**
	 * Tells whether a material shall be created per smoothing group.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {boolean} materialPerSmoothingGroup=false
	 */
	OBJLoader2.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
		this.materialPerSmoothingGroup = materialPerSmoothingGroup === true;
	};

	/**
	 * Use this convenient method to load an OBJ file at the given URL. Per default the fileLoader uses an arraybuffer.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {string} url URL of the file to load
	 * @param {callback} onLoad Called after loading was successfully completed
	 * @param {callback} onProgress Called to report progress of loading. The argument will be the XMLHttpRequest instance, which contains {integer total} and {integer loaded} bytes.
	 * @param {callback} onError Called after an error occurred during loading
	 * @param {callback} onMeshAlter Called after a new mesh raw data becomes available to allow alteration
	 * @param {boolean} useAsync If true uses async loading with worker, if false loads data synchronously
	 */
	OBJLoader2.prototype.load = function ( url, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
		var scope = this;
		if ( ! Validator.isValid( onProgress ) ) {
			var numericalValueRef = 0;
			var numericalValue = 0;
			onProgress = function ( event ) {
				if ( ! event.lengthComputable ) return;

				numericalValue = event.loaded / event.total;
				if ( numericalValue > numericalValueRef ) {

					numericalValueRef = numericalValue;
					var output = 'Download of "' + url + '": ' + ( numericalValue * 100 ).toFixed( 2 ) + '%';
					scope.onProgress( 'progressLoad', output, numericalValue );

				}
			};
		}

		if ( ! Validator.isValid( onError ) ) {
			onError = function ( event ) {
				var output = 'Error occurred while downloading "' + url + '"';
				scope.logger.logError( output + ': ' + event );
				scope.onProgress( 'error', output, -1 );
			};
		}

		this.fileLoader.setPath( this.path );
		this.fileLoader.setResponseType( 'arraybuffer' );
		this.fileLoader.load( url, function ( content ) {
			if ( useAsync ) {

				scope.parseAsync( content, onLoad );

			} else {

				scope._setCallbacks( null, onMeshAlter, null );
				onLoad(
					{
						detail: {
							loaderRootNode: scope.parse( content ),
							modelName: scope.modelName,
							instanceNo: scope.instanceNo
						}
					}
				);

			}

		}, onProgress, onError );

	};

	/**
	 * Run the loader according the provided instructions.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {THREE.LoaderSupport.PrepData} prepData All parameters and resources required for execution
	 * @param {THREE.LoaderSupport.WorkerSupport} [workerSupportExternal] Use pre-existing WorkerSupport
	 */
	OBJLoader2.prototype.run = function ( prepData, workerSupportExternal ) {
		this._applyPrepData( prepData );
		var available = this._checkFiles( prepData.resources );
		if ( Validator.isValid( workerSupportExternal ) ) {

			this.terminateWorkerOnLoad = false;
			this.workerSupport = workerSupportExternal;
			this.logger = workerSupportExternal.logger;

		} else {

			this.terminateWorkerOnLoad = true;

		}
		var scope = this;
		var onMaterialsLoaded = function ( materials ) {
			scope.builder.setMaterials( materials );

			if ( Validator.isValid( available.obj.content ) ) {

				if ( prepData.useAsync ) {

					scope.parseAsync( available.obj.content, scope.callbacks.onLoad );

				} else {

					scope.parse( available.obj.content );

				}
			} else {

				scope.setPath( available.obj.path );
				scope.load( available.obj.name, scope.callbacks.onLoad, null, null, scope.callbacks.onMeshAlter, prepData.useAsync );

			}
		};

		this._loadMtl( available.mtl, onMaterialsLoaded, prepData.crossOrigin );
	};

	OBJLoader2.prototype._applyPrepData = function ( prepData ) {
		THREE.LoaderSupport.Commons.prototype._applyPrepData.call( this, prepData );

		if ( Validator.isValid( prepData ) ) {

			this.setMaterialPerSmoothingGroup( prepData.materialPerSmoothingGroup );

		}
	};

	/**
	 * Parses OBJ data synchronously from arraybuffer or string.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {arraybuffer|string} content OBJ data as Uint8Array or String
	 */
	OBJLoader2.prototype.parse = function ( content ) {
		this.logger.logTimeStart( 'OBJLoader2 parse: ' + this.modelName );

		var parser = new Parser( this.logger );
		parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
		parser.setUseIndices( this.useIndices );
		parser.setDisregardNormals( this.disregardNormals );
		parser.setMaterialNames( this.builder.materialNames );

		var scope = this;
		var onMeshLoaded = function ( payload ) {
			var meshes = scope.builder.buildMeshes( payload );
			var mesh;
			for ( var i in meshes ) {
				mesh = meshes[ i ];
				scope.loaderRootNode.add( mesh );
			}
		};
		parser.setCallbackBuilder( onMeshLoaded );
		var onProgressScoped = function ( text, numericalValue ) {
			scope.onProgress( 'progressParse', text, numericalValue );
		};
		parser.setCallbackProgress( onProgressScoped );

		if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {

			this.logger.logInfo( 'Parsing arrayBuffer...' );
			parser.parse( content );

		} else if ( typeof( content ) === 'string' || content instanceof String ) {

			this.logger.logInfo( 'Parsing text...' );
			parser.parseText( content );

		} else {

			throw 'Provided content was neither of type String nor Uint8Array! Aborting...';

		}
		this.logger.logTimeEnd( 'OBJLoader2 parse: ' + this.modelName );

		return this.loaderRootNode;
	};

	/**
	 * Parses OBJ content asynchronously from arraybuffer.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {arraybuffer} content OBJ data as Uint8Array
	 * @param {callback} onLoad Called after worker successfully completed loading
	 */
	OBJLoader2.prototype.parseAsync = function ( content, onLoad ) {
		this.logger.logTimeStart( 'OBJLoader2 parseAsync: ' + this.modelName );

		var scope = this;
		var scopedOnLoad = function () {
			onLoad(
				{
					detail: {
						loaderRootNode: scope.loaderRootNode,
						modelName: scope.modelName,
						instanceNo: scope.instanceNo
					}
				}
			);
			if ( scope.terminateWorkerOnLoad ) scope.workerSupport.terminateWorker();
			scope.logger.logTimeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
		};
		var scopedOnMeshLoaded = function ( payload ) {
			var meshes = scope.builder.buildMeshes( payload );
			var mesh;
			for ( var i in meshes ) {
				mesh = meshes[ i ];
				scope.loaderRootNode.add( mesh );
			}
		};

		this.workerSupport = Validator.verifyInput( this.workerSupport, new THREE.LoaderSupport.WorkerSupport( this.logger ) );
		var buildCode = function ( funcBuildObject, funcBuildSingelton ) {
			var workerCode = '';
			workerCode += '/**\n';
			workerCode += '  * This code was constructed by OBJLoader2 buildWorkerCode.\n';
			workerCode += '  */\n\n';
			workerCode += funcBuildSingelton( 'Commons', 'Commons', Commons );
			workerCode += funcBuildObject( 'Consts', Consts );
			workerCode += funcBuildObject( 'Validator', Validator );
			workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
			workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
			workerCode += funcBuildSingelton( 'RawMesh', 'RawMesh', RawMesh );
			workerCode += funcBuildSingelton( 'RawMeshSubGroup', 'RawMeshSubGroup', RawMeshSubGroup );

			return workerCode;
		};
		this.workerSupport.validate( buildCode, false );
		this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
		this.workerSupport.run(
			{
				cmd: 'run',
				params: {
					materialPerSmoothingGroup: this.materialPerSmoothingGroup,
					useIndices: this.useIndices,
					disregardNormals: this.disregardNormals
				},
				logger: {
					debug: this.logger.debug,
					enabled: this.logger.enabled
				},
				materials: {
					materialNames: this.builder.materialNames
				},
				buffers: {
					input: content
				}
			},
			[ content.buffer ]
		);
	};

	/**
	 * Constants used by THREE.OBJLoader2
	 */
	var Consts = {
		CODE_LF: 10,
		CODE_CR: 13,
		CODE_SPACE: 32,
		CODE_SLASH: 47,
		STRING_LF: '\n',
		STRING_CR: '\r',
		STRING_SPACE: ' ',
		STRING_SLASH: '/',
		LINE_F: 'f',
		LINE_G: 'g',
		LINE_L: 'l',
		LINE_O: 'o',
		LINE_S: 's',
		LINE_V: 'v',
		LINE_VT: 'vt',
		LINE_VN: 'vn',
		LINE_MTLLIB: 'mtllib',
		LINE_USEMTL: 'usemtl'
	};

	/**
	 * Parse OBJ data either from ArrayBuffer or string
	 * @class
	 */
	var Parser = (function () {

		function Parser( logger ) {
			this.callbackProgress = null;
			this.callbackBuilder = null;

			this.materialNames = [];
			this.rawMesh = null;
			this.materialPerSmoothingGroup = false;
			this.useIndices = false;
			this.disregardNormals = false;

			this.inputObjectCount = 1;
			this.outputObjectCount = 1;
			this.counts = {
				vertices: 0,
				faces: 0,
				doubleIndicesCount: 0
			};
			this.logger = logger;
			this.totalBytes = 0;
		};

		Parser.prototype.setMaterialPerSmoothingGroup = function ( materialPerSmoothingGroup ) {
			this.materialPerSmoothingGroup = materialPerSmoothingGroup;
		};

		Parser.prototype.setUseIndices = function ( useIndices ) {
			this.useIndices = useIndices;
		};

		Parser.prototype.setDisregardNormals = function ( disregardNormals ) {
			this.disregardNormals = disregardNormals;
		};

		Parser.prototype.setMaterialNames = function ( materialNames ) {
			this.materialNames = Validator.verifyInput( materialNames, this.materialNames );
			this.materialNames = Validator.verifyInput( this.materialNames, [] );
		};

		Parser.prototype.setCallbackBuilder = function ( callbackBuilder ) {
			this.callbackBuilder = callbackBuilder;
			if ( ! Validator.isValid( this.callbackBuilder ) ) throw 'Unable to run as no "builder" callback is set.';
		};

		Parser.prototype.setCallbackProgress = function ( callbackProgress ) {
			this.callbackProgress = callbackProgress;
		};

		Parser.prototype.configure = function () {
			this.rawMesh = new RawMesh( this.materialPerSmoothingGroup, this.useIndices, this.disregardNormals );

			if ( this.logger.isEnabled() ) {

				var matNames = ( this.materialNames.length > 0 ) ? '\n\tmaterialNames:\n\t\t- ' + this.materialNames.join( '\n\t\t- ' ) : '\n\tmaterialNames: None';
				var printedConfig = 'OBJLoader2.Parser configuration:'
						+ matNames
						+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
						+ '\n\tuseIndices: ' + this.useIndices
						+ '\n\tdisregardNormals: ' + this.disregardNormals
						+ '\n\tcallbackBuilderName: ' + this.callbackBuilder.name
						+ '\n\tcallbackProgressName: ' + this.callbackProgress.name;
				this.logger.logInfo( printedConfig );
			}
		};

		/**
		 * Parse the provided arraybuffer
		 * @memberOf Parser
		 *
		 * @param {Uint8Array} arrayBuffer OBJ data as Uint8Array
		 */
		Parser.prototype.parse = function ( arrayBuffer ) {
			this.logger.logTimeStart( 'OBJLoader2.Parser.parse' );
			this.configure();

			var arrayBufferView = new Uint8Array( arrayBuffer );
			var length = arrayBufferView.byteLength;
			this.totalBytes = length;
			var buffer = new Array( 128 );
			var bufferPointer = 0;
			var slashesCount = 0;
			var reachedFaces = false;
			var code;
			var word = '';
			var i = 0;
			for ( ; i < length; i ++ ) {

				code = arrayBufferView[ i ];
				switch ( code ) {
					case Consts.CODE_SPACE:
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						break;

					case Consts.CODE_SLASH:
						slashesCount ++;
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						break;

					case Consts.CODE_LF:
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );
						bufferPointer = 0;
						slashesCount = 0;
						break;

					case Consts.CODE_CR:
						break;

					default:
						word += String.fromCharCode( code );
						break;
				}
			}
			this.finalize( i );
			this.logger.logTimeEnd( 'OBJLoader2.Parser.parse' );
		};

		/**
		 * Parse the provided text
		 * @memberOf Parser
		 *
		 * @param {string} text OBJ data as string
		 */
		Parser.prototype.parseText = function ( text ) {
			this.logger.logTimeStart( 'OBJLoader2.Parser.parseText' );
			this.configure();

			var length = text.length;
			this.totalBytes = length;
			var buffer = new Array( 128 );
			var bufferPointer = 0;
			var slashesCount = 0;
			var reachedFaces = false;
			var char;
			var word = '';
			var i = 0;
			for ( ; i < length; i ++ ) {

				char = text[ i ];
				switch ( char ) {
					case Consts.STRING_SPACE:
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						break;

					case Consts.STRING_SLASH:
						slashesCount ++;
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						break;

					case Consts.STRING_LF:
						if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
						word = '';
						reachedFaces = this.processLine( buffer, bufferPointer, slashesCount, reachedFaces, i );
						bufferPointer = 0;
						slashesCount = 0;
						break;

					case Consts.STRING_CR:
						break;

					default:
						word += char;
				}
			}
			this.finalize( i );
			this.logger.logTimeEnd( 'OBJLoader2.Parser.parseText' );
		};

		Parser.prototype.processLine = function ( buffer, bufferPointer, slashesCount, reachedFaces, currentByte ) {
			if ( bufferPointer < 1 ) return reachedFaces;

			var bufferLength = bufferPointer - 1;
			var concatBuffer;
			switch ( buffer[ 0 ] ) {
				case Consts.LINE_V:

					// object complete instance required if reached faces already (= reached next block of v)
					if ( reachedFaces ) {

						if ( this.rawMesh.colors.length > 0 && this.rawMesh.colors.length !== this.rawMesh.vertices.length ) {

							throw 'Vertex Colors were detected, but vertex count and color count do not match!';

						}
						this.processCompletedObject( null, this.rawMesh.groupName, currentByte );
						reachedFaces = false;

					}
					if ( bufferLength === 3 ) {

						this.rawMesh.pushVertex( buffer )

					} else {

						this.rawMesh.pushVertexAndVertextColors( buffer );

					}
					break;

				case Consts.LINE_VT:
					this.rawMesh.pushUv( buffer );
					break;

				case Consts.LINE_VN:
					this.rawMesh.pushNormal( buffer );
					break;

				case Consts.LINE_F:
					reachedFaces = true;
					this.rawMesh.processFaces( buffer, bufferPointer, slashesCount );
					break;

				case Consts.LINE_L:
					if ( bufferLength === slashesCount * 2 ) {

						this.rawMesh.buildLineVvt( buffer );

					} else {

						this.rawMesh.buildLineV( buffer );

					}
					break;

				case Consts.LINE_S:
					this.rawMesh.pushSmoothingGroup( buffer[ 1 ] );
					this.flushStringBuffer( buffer, bufferPointer );
					break;

				case Consts.LINE_G:
					concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
					this.processCompletedGroup( concatBuffer, currentByte );
					this.flushStringBuffer( buffer, bufferPointer );
					break;

				case Consts.LINE_O:
					concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
					if ( this.rawMesh.vertices.length > 0 ) {

						this.processCompletedObject( concatBuffer, null, currentByte );
						reachedFaces = false;

					} else {

						this.rawMesh.pushObject( concatBuffer );

					}
					this.flushStringBuffer( buffer, bufferPointer );
					break;

				case Consts.LINE_MTLLIB:
					concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
					this.rawMesh.pushMtllib( concatBuffer );
					this.flushStringBuffer( buffer, bufferPointer );
					break;

				case Consts.LINE_USEMTL:
					concatBuffer = bufferLength > 1 ? buffer.slice( 1, bufferPointer ).join( ' ' ) : buffer[ 1 ];
					this.rawMesh.pushUsemtl( concatBuffer );
					this.flushStringBuffer( buffer, bufferPointer );
					break;

				default:
					break;
			}
			return reachedFaces;
		};

		Parser.prototype.flushStringBuffer = function ( buffer, bufferLength ) {
			for ( var i = 0; i < bufferLength; i ++ ) {
				buffer[ i ] = '';
			}
		};

		Parser.prototype.createRawMeshReport = function ( rawMesh , inputObjectCount ) {
			var report = rawMesh.createReport( inputObjectCount );
			return 'Input Object number: ' + inputObjectCount +
				'\n\tObject name: ' + report.objectName +
				'\n\tGroup name: ' + report.groupName +
				'\n\tMtllib name: ' + report.mtllibName +
				'\n\tVertex count: ' + report.vertexCount +
				'\n\tNormal count: ' + report.normalCount +
				'\n\tUV count: ' + report.uvCount +
				'\n\tSmoothingGroup count: ' + report.smoothingGroupCount +
				'\n\tMaterial count: ' + report.mtlCount +
				'\n\tReal RawMeshSubGroup count: ' + report.subGroups;
		};

		Parser.prototype.processCompletedObject = function ( objectName, groupName, currentByte ) {
			var result = this.rawMesh.finalize();
			if ( Validator.isValid( result ) ) {

				this.inputObjectCount++;
				if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
				this.buildMesh( result, currentByte );
				var progressBytesPercent = currentByte / this.totalBytes;
				this.callbackProgress( 'Completed object: ' + objectName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );

			}
			this.rawMesh = this.rawMesh.newInstanceFromObject( objectName, groupName );
		};

		Parser.prototype.processCompletedGroup = function ( groupName, currentByte ) {
			var result = this.rawMesh.finalize();
			if ( Validator.isValid( result ) ) {

				this.inputObjectCount++;
				if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
				this.buildMesh( result, currentByte );
				var progressBytesPercent = currentByte / this.totalBytes;
				this.callbackProgress( 'Completed group: ' + groupName + ' Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
				this.rawMesh = this.rawMesh.newInstanceFromGroup( groupName );

			} else {

				// if a group was set that did not lead to object creation in finalize, then the group name has to be updated
				this.rawMesh.pushGroup( groupName );

			}
		};

		Parser.prototype.finalize = function ( currentByte ) {
			this.logger.logInfo( 'Global output object count: ' + this.outputObjectCount );
			var result = Validator.isValid( this.rawMesh ) ? this.rawMesh.finalize() : null;
			if ( Validator.isValid( result ) ) {

				this.inputObjectCount++;
				if ( this.logger.isDebug() ) this.logger.logDebug( this.createRawMeshReport( this.rawMesh, this.inputObjectCount ) );
				this.buildMesh( result, currentByte );

				if ( this.logger.isEnabled() ) {

					var parserFinalReport = 'Overall counts: ' +
						'\n\tVertices: ' + this.counts.vertices +
						'\n\tFaces: ' + this.counts.faces +
						'\n\tMultiple definitions: ' + this.counts.doubleIndicesCount;
					this.logger.logInfo( parserFinalReport );

				}
				var progressBytesPercent = currentByte / this.totalBytes;
				this.callbackProgress( 'Completed Parsing: 100.00%', progressBytesPercent );

			}
		};

		/**
		 * RawObjectDescriptions are transformed to too intermediate format that is forwarded to the Builder.
		 * It is ensured that rawObjectDescriptions only contain objects with vertices (no need to check).
		 *
		 * @param result
		 */
		Parser.prototype.buildMesh = function ( result, currentByte ) {
			var rawObjectDescriptions = result.subGroups;

			var vertexFA = new Float32Array( result.absoluteVertexCount );
			this.counts.vertices += result.absoluteVertexCount / 3;
			this.counts.faces += result.faceCount;
			this.counts.doubleIndicesCount += result.doubleIndicesCount;
			var indexUA = ( result.absoluteIndexCount > 0 ) ? new Uint32Array( result.absoluteIndexCount ) : null;
			var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
			var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
			var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;

			var rawObjectDescription;
			var materialDescription;
			var materialDescriptions = [];

			var createMultiMaterial = ( rawObjectDescriptions.length > 1 );
			var materialIndex = 0;
			var materialIndexMapping = [];
			var selectedMaterialIndex;
			var materialGroup;
			var materialGroups = [];

			var vertexFAOffset = 0;
			var indexUAOffset = 0;
			var colorFAOffset = 0;
			var normalFAOffset = 0;
			var uvFAOffset = 0;
			var materialGroupOffset = 0;
			var materialGroupLength = 0;

			for ( var oodIndex in rawObjectDescriptions ) {
				if ( ! rawObjectDescriptions.hasOwnProperty( oodIndex ) ) continue;
				rawObjectDescription = rawObjectDescriptions[ oodIndex ];

				materialDescription = {
					name: rawObjectDescription.materialName,
					flat: false,
					default: false
				};
				if ( this.materialNames[ materialDescription.name ] === null ) {

					materialDescription.default = true;
					this.logger.logWarn( 'object_group "' + rawObjectDescription.objectName + '_' +
						rawObjectDescription.groupName +
						'" was defined without material! Assigning "defaultMaterial".' );

				}
				// Attach '_flat' to materialName in case flat shading is needed due to smoothingGroup 0
				if ( rawObjectDescription.smoothingGroup === 0 ) materialDescription.flat = true;

				if ( createMultiMaterial ) {

					// re-use material if already used before. Reduces materials array size and eliminates duplicates

					selectedMaterialIndex = materialIndexMapping[ materialDescription.name ];
					if ( ! selectedMaterialIndex ) {

						selectedMaterialIndex = materialIndex;
						materialIndexMapping[ materialDescription.name ] = materialIndex;
						materialDescriptions.push( materialDescription );
						materialIndex++;

					}
					materialGroupLength = this.useIndices ? rawObjectDescription.indices.length : rawObjectDescription.vertices.length / 3;
					materialGroup = {
						start: materialGroupOffset,
						count: materialGroupLength,
						index: selectedMaterialIndex
					};
					materialGroups.push( materialGroup );
					materialGroupOffset += materialGroupLength;

				} else {

					materialDescriptions.push( materialDescription );

				}

				vertexFA.set( rawObjectDescription.vertices, vertexFAOffset );
				vertexFAOffset += rawObjectDescription.vertices.length;

				if ( indexUA ) {

					indexUA.set( rawObjectDescription.indices, indexUAOffset );
					indexUAOffset += rawObjectDescription.indices.length;

				}

				if ( colorFA ) {

					colorFA.set( rawObjectDescription.colors, colorFAOffset );
					colorFAOffset += rawObjectDescription.colors.length;

				}

				if ( normalFA ) {

					normalFA.set( rawObjectDescription.normals, normalFAOffset );
					normalFAOffset += rawObjectDescription.normals.length;

				}
				if ( uvFA ) {

					uvFA.set( rawObjectDescription.uvs, uvFAOffset );
					uvFAOffset += rawObjectDescription.uvs.length;

				}

				if ( this.logger.isDebug() ) {
					var materialIndexLine = Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : '';
					var createdReport = 'Output Object no.: ' + this.outputObjectCount +
						'\n\t\tobjectName: ' + rawObjectDescription.objectName +
						'\n\t\tgroupName: ' + rawObjectDescription.groupName +
						'\n\t\tmaterialName: ' + rawObjectDescription.materialName +
						materialIndexLine +
						'\n\t\tsmoothingGroup: ' + rawObjectDescription.smoothingGroup +
						'\n\t\t#vertices: ' + rawObjectDescription.vertices.length / 3 +
						'\n\t\t#indices: ' + rawObjectDescription.indices.length +
						'\n\t\t#colors: ' + rawObjectDescription.colors.length / 3 +
						'\n\t\t#uvs: ' + rawObjectDescription.uvs.length / 2 +
						'\n\t\t#normals: ' + rawObjectDescription.normals.length / 3;
					this.logger.logDebug( createdReport );
				}


			}

			this.outputObjectCount++;
			this.callbackBuilder(
				{
					cmd: 'meshData',
					progress: {
						numericalValue: currentByte / this.totalBytes
					},
					params: {
						meshName: result.name
					},
					materials: {
						multiMaterial: createMultiMaterial,
						materialDescriptions: materialDescriptions,
						materialGroups: materialGroups
					},
					buffers: {
						vertices: vertexFA,
						indices: indexUA,
						colors: colorFA,
						normals: normalFA,
						uvs: uvFA
					}
				},
				[ vertexFA.buffer ],
				Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
				Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
				Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
				Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
			);
		};

		return Parser;
	})();

	/**
	 * {@link RawMesh} is only used by {@link Parser}.
	 * The user of OBJLoader2 does not need to care about this class.
	 * It is defined publicly for inclusion in web worker based OBJ loader ({@link THREE.OBJLoader2.WWOBJLoader2})
	 */
	var RawMesh = (function () {

		function RawMesh( materialPerSmoothingGroup, useIndices, disregardNormals, objectName, groupName, activeMtlName ) {
			this.globalVertexOffset = 1;
			this.globalUvOffset = 1;
			this.globalNormalOffset = 1;

			this.vertices = [];
			this.colors = [];
			this.normals = [];
			this.uvs = [];

			// faces are stored according combined index of group, material and smoothingGroup (0 or not)
			this.activeMtlName = Validator.verifyInput( activeMtlName, '' );
			this.objectName = Validator.verifyInput( objectName, '' );
			this.groupName = Validator.verifyInput( groupName, '' );
			this.mtllibName = '';
			this.smoothingGroup = {
				splitMaterials: materialPerSmoothingGroup === true,
				normalized: -1,
				real: -1
			};
			this.useIndices = useIndices === true;
			this.disregardNormals = disregardNormals === true;

			this.mtlCount = 0;
			this.smoothingGroupCount = 0;

			this.subGroups = [];
			this.subGroupInUse = null;
			// this default index is required as it is possible to define faces without 'g' or 'usemtl'
			this.pushSmoothingGroup( 1 );

			this.doubleIndicesCount = 0;
			this.faceCount = 0;
		}

		RawMesh.prototype.newInstanceFromObject = function ( objectName, groupName ) {
			var newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, objectName, groupName, this.activeMtlName );

			// move indices forward
			newRawObject.globalVertexOffset = this.globalVertexOffset + this.vertices.length / 3;
			newRawObject.globalUvOffset = this.globalUvOffset + this.uvs.length / 2;
			newRawObject.globalNormalOffset = this.globalNormalOffset + this.normals.length / 3;

			return newRawObject;
		};

		RawMesh.prototype.newInstanceFromGroup = function ( groupName ) {
			var newRawObject = new RawMesh( this.smoothingGroup.splitMaterials, this.useIndices, this.disregardNormals, this.objectName, groupName, this.activeMtlName );

			// keep current buffers and indices forward
			newRawObject.vertices = this.vertices;
			newRawObject.colors = this.colors;
			newRawObject.uvs = this.uvs;
			newRawObject.normals = this.normals;
			newRawObject.globalVertexOffset = this.globalVertexOffset;
			newRawObject.globalUvOffset = this.globalUvOffset;
			newRawObject.globalNormalOffset = this.globalNormalOffset;

			return newRawObject;
		};

		RawMesh.prototype.pushVertex = function ( buffer ) {
			this.vertices.push( parseFloat( buffer[ 1 ] ) );
			this.vertices.push( parseFloat( buffer[ 2 ] ) );
			this.vertices.push( parseFloat( buffer[ 3 ] ) );
		};

		RawMesh.prototype.pushVertexAndVertextColors = function ( buffer ) {
			this.vertices.push( parseFloat( buffer[ 1 ] ) );
			this.vertices.push( parseFloat( buffer[ 2 ] ) );
			this.vertices.push( parseFloat( buffer[ 3 ] ) );
			this.colors.push( parseFloat( buffer[ 4 ] ) );
			this.colors.push( parseFloat( buffer[ 5 ] ) );
			this.colors.push( parseFloat( buffer[ 6 ] ) );
		};

		RawMesh.prototype.pushUv = function ( buffer ) {
			this.uvs.push( parseFloat( buffer[ 1 ] ) );
			this.uvs.push( parseFloat( buffer[ 2 ] ) );
		};

		RawMesh.prototype.pushNormal = function ( buffer ) {
			this.normals.push( parseFloat( buffer[ 1 ] ) );
			this.normals.push( parseFloat( buffer[ 2 ] ) );
			this.normals.push( parseFloat( buffer[ 3 ] ) );
		};

		RawMesh.prototype.pushObject = function ( objectName ) {
			this.objectName = Validator.verifyInput( objectName, '' );
		};

		RawMesh.prototype.pushMtllib = function ( mtllibName ) {
			this.mtllibName = Validator.verifyInput( mtllibName, '' );
		};

		RawMesh.prototype.pushGroup = function ( groupName ) {
			this.groupName = Validator.verifyInput( groupName, '' );
		};

		RawMesh.prototype.pushUsemtl = function ( mtlName ) {
			if ( this.activeMtlName === mtlName || ! Validator.isValid( mtlName ) ) return;
			this.activeMtlName = mtlName;
			this.mtlCount++;

			this.verifyIndex();
		};

		RawMesh.prototype.pushSmoothingGroup = function ( smoothingGroup ) {
			var smoothingGroupInt = parseInt( smoothingGroup );
			if ( isNaN( smoothingGroupInt ) ) {
				smoothingGroupInt = smoothingGroup === "off" ? 0 : 1;
			}

			var smoothCheck = this.smoothingGroup.normalized;
			this.smoothingGroup.normalized = this.smoothingGroup.splitMaterials ? smoothingGroupInt : ( smoothingGroupInt === 0 ) ? 0 : 1;
			this.smoothingGroup.real = smoothingGroupInt;

			if ( smoothCheck !== smoothingGroupInt ) {

				this.smoothingGroupCount++;
				this.verifyIndex();

			}
		};

		RawMesh.prototype.verifyIndex = function () {
			var index = this.activeMtlName + '|' + this.smoothingGroup.normalized;
			this.subGroupInUse = this.subGroups[ index ];
			if ( ! Validator.isValid( this.subGroupInUse ) ) {

				this.subGroupInUse = new RawMeshSubGroup( this.objectName, this.groupName, this.activeMtlName, this.smoothingGroup.normalized );
				this.subGroups[ index ] = this.subGroupInUse;

			}
		};

		RawMesh.prototype.processFaces = function ( buffer, bufferPointer, slashesCount ) {
			var bufferLength = bufferPointer - 1;
			var i, length;

			// "f vertex ..."
			if ( slashesCount === 0 ) {

				for ( i = 2, length = bufferLength - 1; i < length; i ++ ) {

					this.buildFace( buffer[ 1 ] );
					this.buildFace( buffer[ i ] );
					this.buildFace( buffer[ i + 1 ] );

				}

				// "f vertex/uv ..."
			} else if  ( bufferLength === slashesCount * 2 ) {

				for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {

					this.buildFace( buffer[ 1 ], buffer[ 2 ] );
					this.buildFace( buffer[ i ], buffer[ i + 1 ] );
					this.buildFace( buffer[ i + 2 ], buffer[ i + 3 ] );

				}

				// "f vertex/uv/normal ..."
			} else if  ( bufferLength * 2 === slashesCount * 3 ) {

				for ( i = 4, length = bufferLength - 3; i < length; i += 3 ) {

					this.buildFace( buffer[ 1 ], buffer[ 2 ], buffer[ 3 ] );
					this.buildFace( buffer[ i ], buffer[ i + 1 ], buffer[ i + 2 ] );
					this.buildFace( buffer[ i + 3 ], buffer[ i + 4 ], buffer[ i + 5 ] );

				}

				// "f vertex//normal ..."
			} else {

				for ( i = 3, length = bufferLength - 2; i < length; i += 2 ) {

					this.buildFace( buffer[ 1 ], undefined, buffer[ 2 ] );
					this.buildFace( buffer[ i ], undefined, buffer[ i + 1 ] );
					this.buildFace( buffer[ i + 2 ], undefined, buffer[ i + 3 ] );

				}

			}
		};

		RawMesh.prototype.buildFace = function ( faceIndexV, faceIndexU, faceIndexN ) {
			var sgiu = this.subGroupInUse;
			if ( this.disregardNormals ) faceIndexN = undefined;
			var scope = this;
			var updateRawObjectDescriptionInUse = function () {

				var indexPointerV = ( parseInt( faceIndexV ) - scope.globalVertexOffset ) * 3;
				var indexPointerC = scope.colors.length > 0 ? indexPointerV : null;

				var vertices = sgiu.vertices;
				vertices.push( scope.vertices[ indexPointerV++ ] );
				vertices.push( scope.vertices[ indexPointerV++ ] );
				vertices.push( scope.vertices[ indexPointerV ] );

				if ( indexPointerC !== null ) {

					var colors = sgiu.colors;
					colors.push( scope.colors[ indexPointerC++ ] );
					colors.push( scope.colors[ indexPointerC++ ] );
					colors.push( scope.colors[ indexPointerC ] );

				}

				if ( faceIndexU ) {

					var indexPointerU = ( parseInt( faceIndexU ) - scope.globalUvOffset ) * 2;
					var uvs = sgiu.uvs;
					uvs.push( scope.uvs[ indexPointerU++ ] );
					uvs.push( scope.uvs[ indexPointerU ] );

				}
				if ( faceIndexN ) {

					var indexPointerN = ( parseInt( faceIndexN ) - scope.globalNormalOffset ) * 3;
					var normals = sgiu.normals;
					normals.push( scope.normals[ indexPointerN++ ] );
					normals.push( scope.normals[ indexPointerN++ ] );
					normals.push( scope.normals[ indexPointerN ] );

				}
			};

			if ( this.useIndices ) {

				var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
				var indicesPointer = sgiu.indexMappings[ mappingName ];
				if ( Validator.isValid( indicesPointer ) ) {

					this.doubleIndicesCount++;

				} else {

					indicesPointer = sgiu.vertices.length / 3;
					updateRawObjectDescriptionInUse();
					sgiu.indexMappings[ mappingName ] = indicesPointer;
					sgiu.indexMappingsCount++;

				}
				sgiu.indices.push( indicesPointer );

			} else {

				updateRawObjectDescriptionInUse();

			}
			this.faceCount++;
		};

		/*
		 * Support for lines with or without texture. First element in indexArray is the line identification
		 * 0: "f vertex/uv		vertex/uv 		..."
		 * 1: "f vertex			vertex 			..."
		 */
		RawMesh.prototype.buildLineVvt = function ( lineArray ) {
			for ( var i = 1, length = lineArray.length; i < length; i ++ ) {

				this.vertices.push( parseInt( lineArray[ i ] ) );
				this.uvs.push( parseInt( lineArray[ i ] ) );

			}
		};

		RawMesh.prototype.buildLineV = function ( lineArray ) {
			for ( var i = 1, length = lineArray.length; i < length; i++ ) {

				this.vertices.push( parseInt( lineArray[ i ] ) );

			}
		};

		/**
		 * Clear any empty rawObjectDescription and calculate absolute vertex, normal and uv counts
		 */
		RawMesh.prototype.finalize = function () {
			var rawObjectDescriptionsTemp = [];
			var rawObjectDescription;
			var absoluteVertexCount = 0;
			var absoluteIndexMappingsCount = 0;
			var absoluteIndexCount = 0;
			var absoluteColorCount = 0;
			var absoluteNormalCount = 0;
			var absoluteUvCount = 0;
			var indices;
			for ( var name in this.subGroups ) {

				rawObjectDescription = this.subGroups[ name ];
				if ( rawObjectDescription.vertices.length > 0 ) {

					indices = rawObjectDescription.indices;
					if ( indices.length > 0 && absoluteIndexMappingsCount > 0 ) {

						for ( var i in indices ) indices[ i ] = indices[ i ] + absoluteIndexMappingsCount;

					}
					rawObjectDescriptionsTemp.push( rawObjectDescription );
					absoluteVertexCount += rawObjectDescription.vertices.length;
					absoluteIndexMappingsCount += rawObjectDescription.indexMappingsCount;
					absoluteIndexCount += rawObjectDescription.indices.length;
					absoluteColorCount += rawObjectDescription.colors.length;
					absoluteUvCount += rawObjectDescription.uvs.length;
					absoluteNormalCount += rawObjectDescription.normals.length;

				}
			}

			// do not continue if no result
			var result = null;
			if ( rawObjectDescriptionsTemp.length > 0 ) {

				result = {
					name: this.groupName !== '' ? this.groupName : this.objectName,
					subGroups: rawObjectDescriptionsTemp,
					absoluteVertexCount: absoluteVertexCount,
					absoluteIndexCount: absoluteIndexCount,
					absoluteColorCount: absoluteColorCount,
					absoluteNormalCount: absoluteNormalCount,
					absoluteUvCount: absoluteUvCount,
					faceCount: this.faceCount,
					doubleIndicesCount: this.doubleIndicesCount
				};

			}
			return result;
		};

		RawMesh.prototype.createReport = function () {
			var report = {
				objectName: this.objectName,
				groupName: this.groupName,
				mtllibName: this.mtllibName,
				vertexCount: this.vertices.length / 3,
				normalCount: this.normals.length / 3,
				uvCount: this.uvs.length / 2,
				smoothingGroupCount: this.smoothingGroupCount,
				mtlCount: this.mtlCount,
				subGroups: this.subGroups.length
			};

			return report;
		};

		return RawMesh;
	})();

	/**
	 * Descriptive information and data (vertices, normals, uvs) to passed on to mesh building function.
	 * @class
	 *
	 * @param {string} objectName Name of the mesh
	 * @param {string} groupName Name of the group
	 * @param {string} materialName Name of the material
	 * @param {number} smoothingGroup Normalized smoothingGroup (0: flat shading, 1: smooth shading)
	 */
	var RawMeshSubGroup = (function () {

		function RawMeshSubGroup( objectName, groupName, materialName, smoothingGroup ) {
			this.objectName = objectName;
			this.groupName = groupName;
			this.materialName = materialName;
			this.smoothingGroup = smoothingGroup;
			this.vertices = [];
			this.indexMappingsCount = 0;
			this.indexMappings = [];
			this.indices = [];
			this.colors = [];
			this.uvs = [];
			this.normals = [];
		}

		return RawMeshSubGroup;
	})();

	OBJLoader2.prototype._checkFiles = function ( resources ) {
		var resource;
		var result = {
			mtl: null,
			obj: null
		};
		for ( var index in resources ) {

			resource = resources[ index ];
			if ( ! Validator.isValid( resource.name ) ) continue;
			if ( Validator.isValid( resource.content ) ) {

				if ( resource.extension === 'OBJ' ) {

					// fast-fail on bad type
					if ( ! ( resource.content instanceof Uint8Array ) ) throw 'Provided content is not of type arraybuffer! Aborting...';
					result.obj = resource;

				} else if ( resource.extension === 'MTL' && Validator.isValid( resource.name ) ) {

					if ( ! ( typeof( resource.content ) === 'string' || resource.content instanceof String ) ) throw 'Provided  content is not of type String! Aborting...';
					result.mtl = resource;

				} else if ( resource.extension === "ZIP" ) {
					// ignore

				} else {

					throw 'Unidentified resource "' + resource.name + '": ' + resource.url;

				}

			} else {

				// fast-fail on bad type
				if ( ! ( typeof( resource.name ) === 'string' || resource.name instanceof String ) ) throw 'Provided file is not properly defined! Aborting...';
				if ( resource.extension === 'OBJ' ) {

					result.obj = resource;

				} else if ( resource.extension === 'MTL' ) {

					result.mtl = resource;

				} else if ( resource.extension === "ZIP" ) {
					// ignore

				} else {

					throw 'Unidentified resource "' + resource.name + '": ' + resource.url;

				}
			}
		}

		return result;
	};

	/**
	 * Utility method for loading an mtl file according resource description.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {string} url URL to the file
	 * @param {string} name The name of the object
	 * @param {Object} content The file content as arraybuffer or text
	 * @param {function} callbackOnLoad
	 * @param {string} [crossOrigin] CORS value
	 */
	OBJLoader2.prototype.loadMtl = function ( url, name, content, callbackOnLoad, crossOrigin ) {
		var resource = new THREE.LoaderSupport.ResourceDescriptor( url, 'MTL' );
		resource.setContent( content );
		this._loadMtl( resource, callbackOnLoad, crossOrigin );
	};

	/**
	 * Utility method for loading an mtl file according resource description.
	 * @memberOf THREE.OBJLoader2
	 *
	 * @param {THREE.LoaderSupport.ResourceDescriptor} resource
	 * @param {function} callbackOnLoad
	 * @param {string} [crossOrigin] CORS value
	 */
	OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
		if ( Validator.isValid( resource ) ) this.logger.logTimeStart( 'Loading MTL: ' + resource.name );

		var materials = [];
		var scope = this;
		var processMaterials = function ( materialCreator ) {
			var materialCreatorMaterials = [];
			if ( Validator.isValid( materialCreator ) ) {

				materialCreator.preload();
				materialCreatorMaterials = materialCreator.materials;
				for ( var materialName in materialCreatorMaterials ) {

					if ( materialCreatorMaterials.hasOwnProperty( materialName ) ) {

						materials[ materialName ] = materialCreatorMaterials[ materialName ];

					}
				}
			}

			if ( Validator.isValid( resource ) ) scope.logger.logTimeEnd( 'Loading MTL: ' + resource.name );
			callbackOnLoad( materials );
		};

		var mtlLoader = new THREE.MTLLoader();
		crossOrigin = Validator.verifyInput( crossOrigin, 'anonymous' );
		mtlLoader.setCrossOrigin( crossOrigin );

		// fast-fail
		if ( ! Validator.isValid( resource ) || ( ! Validator.isValid( resource.content ) && ! Validator.isValid( resource.url ) ) ) {

			processMaterials();

		} else {

			mtlLoader.setPath( resource.path );
			if ( Validator.isValid( resource.content ) ) {

				processMaterials( Validator.isValid( resource.content ) ? mtlLoader.parse( resource.content ) : null );

			} else if ( Validator.isValid( resource.url ) ) {

				var onError = function ( event ) {
					var output = 'Error occurred while downloading "' + resource.url + '"';
					this.logger.logError( output + ': ' + event );
					throw output;
				};

				mtlLoader.load( resource.name, processMaterials, undefined, onError );

			}
		}
	};

	return OBJLoader2;
})();


================================================
FILE: OrbitControls.js
================================================
/**
 * @author qiao / https://github.com/qiao
 * @author mrdoob / http://mrdoob.com
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 * @author erich666 / http://erichaines.com
 */

// This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
//    Orbit - left mouse / touch: one finger move
//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
//    Pan - right mouse, or arrow keys / touch: three finger swipe

THREE.OrbitControls = function ( object, domElement ) {

	this.object = object;

	this.domElement = ( domElement !== undefined ) ? domElement : document;

	// Set to false to disable this control
	this.enabled = true;

	// "target" sets the location of focus, where the object orbits around
	this.target = new THREE.Vector3();

	// How far you can dolly in and out ( PerspectiveCamera only )
	this.minDistance = 0;
	this.maxDistance = Infinity;

	// How far you can zoom in and out ( OrthographicCamera only )
	this.minZoom = 0;
	this.maxZoom = Infinity;

	// How far you can orbit vertically, upper and lower limits.
	// Range is 0 to Math.PI radians.
	this.minPolarAngle = 0; // radians
	this.maxPolarAngle = Math.PI; // radians

	// How far you can orbit horizontally, upper and lower limits.
	// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
	this.minAzimuthAngle = - Infinity; // radians
	this.maxAzimuthAngle = Infinity; // radians

	// Set to true to enable damping (inertia)
	// If damping is enabled, you must call controls.update() in your animation loop
	this.enableDamping = false;
	this.dampingFactor = 0.25;

	// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
	// Set to false to disable zooming
	this.enableZoom = true;
	this.zoomSpeed = 1.0;

	// Set to false to disable rotating
	this.enableRotate = true;
	this.rotateSpeed = 1.0;

	// Set to false to disable panning
	this.enablePan = true;
	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push

	// Set to true to automatically rotate around the target
	// If auto-rotate is enabled, you must call controls.update() in your animation loop
	this.autoRotate = false;
	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60

	// Set to false to disable use of the keys
	this.enableKeys = true;

	// The four arrow keys
	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };

	// Mouse buttons
	this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };

	// for reset
	this.target0 = this.target.clone();
	this.position0 = this.object.position.clone();
	this.zoom0 = this.object.zoom;

	//
	// public methods
	//

	this.getPolarAngle = function () {

		return spherical.phi;

	};

	this.getAzimuthalAngle = function () {

		return spherical.theta;

	};

	this.saveState = function () {

		scope.target0.copy( scope.target );
		scope.position0.copy( scope.object.position );
		scope.zoom0 = scope.object.zoom;

	};

	this.reset = function () {

		scope.target.copy( scope.target0 );
		scope.object.position.copy( scope.position0 );
		scope.object.zoom = scope.zoom0;

		scope.object.updateProjectionMatrix();
		scope.dispatchEvent( changeEvent );

		scope.update();

		state = STATE.NONE;

	};

	// this method is exposed, but perhaps it would be better if we can make it private...
	this.update = function () {

		var offset = new THREE.Vector3();

		// so camera.up is the orbit axis
		var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
		var quatInverse = quat.clone().inverse();

		var lastPosition = new THREE.Vector3();
		var lastQuaternion = new THREE.Quaternion();

		return function update() {

			var position = scope.object.position;

			offset.copy( position ).sub( scope.target );

			// rotate offset to "y-axis-is-up" space
			offset.applyQuaternion( quat );

			// angle from z-axis around y-axis
			spherical.setFromVector3( offset );

			if ( scope.autoRotate && state === STATE.NONE ) {

				rotateLeft( getAutoRotationAngle() );

			}

			spherical.theta += sphericalDelta.theta;
			spherical.phi += sphericalDelta.phi;

			// restrict theta to be between desired limits
			spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );

			// restrict phi to be between desired limits
			spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );

			spherical.makeSafe();


			spherical.radius *= scale;

			// restrict radius to be between desired limits
			spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );

			// move target to panned location
			scope.target.add( panOffset );

			offset.setFromSpherical( spherical );

			// rotate offset back to "camera-up-vector-is-up" space
			offset.applyQuaternion( quatInverse );

			position.copy( scope.target ).add( offset );

			scope.object.lookAt( scope.target );

			if ( scope.enableDamping === true ) {

				sphericalDelta.theta *= ( 1 - scope.dampingFactor );
				sphericalDelta.phi *= ( 1 - scope.dampingFactor );

			} else {

				sphericalDelta.set( 0, 0, 0 );

			}

			scale = 1;
			panOffset.set( 0, 0, 0 );

			// update condition is:
			// min(camera displacement, camera rotation in radians)^2 > EPS
			// using small-angle approximation cos(x/2) = 1 - x^2 / 8

			if ( zoomChanged ||
				lastPosition.distanceToSquared( scope.object.position ) > EPS ||
				8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {

				scope.dispatchEvent( changeEvent );

				lastPosition.copy( scope.object.position );
				lastQuaternion.copy( scope.object.quaternion );
				zoomChanged = false;

				return true;

			}

			return false;

		};

	}();

	this.dispose = function () {

		scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
		scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
		scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );

		scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
		scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
		scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );

		document.removeEventListener( 'mousemove', onMouseMove, false );
		document.removeEventListener( 'mouseup', onMouseUp, false );

		window.removeEventListener( 'keydown', onKeyDown, false );

		//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?

	};

	//
	// internals
	//

	var scope = this;

	var changeEvent = { type: 'change' };
	var startEvent = { type: 'start' };
	var endEvent = { type: 'end' };

	var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };

	var state = STATE.NONE;

	var EPS = 0.000001;

	// current position in spherical coordinates
	var spherical = new THREE.Spherical();
	var sphericalDelta = new THREE.Spherical();

	var scale = 1;
	var panOffset = new THREE.Vector3();
	var zoomChanged = false;

	var rotateStart = new THREE.Vector2();
	var rotateEnd = new THREE.Vector2();
	var rotateDelta = new THREE.Vector2();

	var panStart = new THREE.Vector2();
	var panEnd = new THREE.Vector2();
	var panDelta = new THREE.Vector2();

	var dollyStart = new THREE.Vector2();
	var dollyEnd = new THREE.Vector2();
	var dollyDelta = new THREE.Vector2();

	function getAutoRotationAngle() {

		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;

	}

	function getZoomScale() {

		return Math.pow( 0.95, scope.zoomSpeed );

	}

	function rotateLeft( angle ) {

		sphericalDelta.theta -= angle;

	}

	function rotateUp( angle ) {

		sphericalDelta.phi -= angle;

	}

	var panLeft = function () {

		var v = new THREE.Vector3();

		return function panLeft( distance, objectMatrix ) {

			v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
			v.multiplyScalar( - distance );

			panOffset.add( v );

		};

	}();

	var panUp = function () {

		var v = new THREE.Vector3();

		return function panUp( distance, objectMatrix ) {

			v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
			v.multiplyScalar( distance );

			panOffset.add( v );

		};

	}();

	// deltaX and deltaY are in pixels; right and down are positive
	var pan = function () {

		var offset = new THREE.Vector3();

		return function pan( deltaX, deltaY ) {

			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

			if ( scope.object instanceof THREE.PerspectiveCamera ) {

				// perspective
				var position = scope.object.position;
				offset.copy( position ).sub( scope.target );
				var targetDistance = offset.length();

				// half of the fov is center to top of screen
				targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );

				// we actually don't use screenWidth, since perspective camera is fixed to screen height
				panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
				panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );

			} else if ( scope.object instanceof THREE.OrthographicCamera ) {

				// orthographic
				panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
				panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );

			} else {

				// camera neither orthographic nor perspective
				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
				scope.enablePan = false;

			}

		};

	}();

	function dollyIn( dollyScale ) {

		if ( scope.object instanceof THREE.PerspectiveCamera ) {

			scale /= dollyScale;

		} else if ( scope.object instanceof THREE.OrthographicCamera ) {

			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
			scope.object.updateProjectionMatrix();
			zoomChanged = true;

		} else {

			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
			scope.enableZoom = false;

		}

	}

	function dollyOut( dollyScale ) {

		if ( scope.object instanceof THREE.PerspectiveCamera ) {

			scale *= dollyScale;

		} else if ( scope.object instanceof THREE.OrthographicCamera ) {

			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
			scope.object.updateProjectionMatrix();
			zoomChanged = true;

		} else {

			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
			scope.enableZoom = false;

		}

	}

	//
	// event callbacks - update the object state
	//

	function handleMouseDownRotate( event ) {

		//console.log( 'handleMouseDownRotate' );

		rotateStart.set( event.clientX, event.clientY );

	}

	function handleMouseDownDolly( event ) {

		//console.log( 'handleMouseDownDolly' );

		dollyStart.set( event.clientX, event.clientY );

	}

	function handleMouseDownPan( event ) {

		//console.log( 'handleMouseDownPan' );

		panStart.set( event.clientX, event.clientY );

	}

	function handleMouseMoveRotate( event ) {

		//console.log( 'handleMouseMoveRotate' );

		rotateEnd.set( event.clientX, event.clientY );
		rotateDelta.subVectors( rotateEnd, rotateStart );

		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

		// rotating across whole screen goes 360 degrees around
		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );

		// rotating up and down along whole screen attempts to go 360, but limited to 180
		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );

		rotateStart.copy( rotateEnd );

		scope.update();

	}

	function handleMouseMoveDolly( event ) {

		//console.log( 'handleMouseMoveDolly' );

		dollyEnd.set( event.clientX, event.clientY );

		dollyDelta.subVectors( dollyEnd, dollyStart );

		if ( dollyDelta.y > 0 ) {

			dollyIn( getZoomScale() );

		} else if ( dollyDelta.y < 0 ) {

			dollyOut( getZoomScale() );

		}

		dollyStart.copy( dollyEnd );

		scope.update();

	}

	function handleMouseMovePan( event ) {

		//console.log( 'handleMouseMovePan' );

		panEnd.set( event.clientX, event.clientY );

		panDelta.subVectors( panEnd, panStart );

		pan( panDelta.x, panDelta.y );

		panStart.copy( panEnd );

		scope.update();

	}

	function handleMouseUp( event ) {

		// console.log( 'handleMouseUp' );

	}

	function handleMouseWheel( event ) {

		// console.log( 'handleMouseWheel' );

		if ( event.deltaY < 0 ) {

			dollyOut( getZoomScale() );

		} else if ( event.deltaY > 0 ) {

			dollyIn( getZoomScale() );

		}

		scope.update();

	}

	function handleKeyDown( event ) {

		//console.log( 'handleKeyDown' );

		switch ( event.keyCode ) {

			case scope.keys.UP:
				pan( 0, scope.keyPanSpeed );
				scope.update();
				break;

			case scope.keys.BOTTOM:
				pan( 0, - scope.keyPanSpeed );
				scope.update();
				break;

			case scope.keys.LEFT:
				pan( scope.keyPanSpeed, 0 );
				scope.update();
				break;

			case scope.keys.RIGHT:
				pan( - scope.keyPanSpeed, 0 );
				scope.update();
				break;

		}

	}

	function handleTouchStartRotate( event ) {

		//console.log( 'handleTouchStartRotate' );

		rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );

	}

	function handleTouchStartDolly( event ) {

		//console.log( 'handleTouchStartDolly' );

		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;

		var distance = Math.sqrt( dx * dx + dy * dy );

		dollyStart.set( 0, distance );

	}

	function handleTouchStartPan( event ) {

		//console.log( 'handleTouchStartPan' );

		panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );

	}

	function handleTouchMoveRotate( event ) {

		//console.log( 'handleTouchMoveRotate' );

		rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
		rotateDelta.subVectors( rotateEnd, rotateStart );

		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

		// rotating across whole screen goes 360 degrees around
		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );

		// rotating up and down along whole screen attempts to go 360, but limited to 180
		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );

		rotateStart.copy( rotateEnd );

		scope.update();

	}

	function handleTouchMoveDolly( event ) {

		//console.log( 'handleTouchMoveDolly' );

		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;

		var distance = Math.sqrt( dx * dx + dy * dy );

		dollyEnd.set( 0, distance );

		dollyDelta.subVectors( dollyEnd, dollyStart );

		if ( dollyDelta.y > 0 ) {

			dollyOut( getZoomScale() );

		} else if ( dollyDelta.y < 0 ) {

			dollyIn( getZoomScale() );

		}

		dollyStart.copy( dollyEnd );

		scope.update();

	}

	function handleTouchMovePan( event ) {

		//console.log( 'handleTouchMovePan' );

		panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );

		panDelta.subVectors( panEnd, panStart );

		pan( panDelta.x, panDelta.y );

		panStart.copy( panEnd );

		scope.update();

	}

	function handleTouchEnd( event ) {

		//console.log( 'handleTouchEnd' );

	}

	//
	// event handlers - FSM: listen for events and reset state
	//

	function onMouseDown( event ) {

		if ( scope.enabled === false ) return;

		event.preventDefault();

		switch ( event.button ) {

			case scope.mouseButtons.ORBIT:

				if ( scope.enableRotate === false ) return;

				handleMouseDownRotate( event );

				state = STATE.ROTATE;

				break;

			case scope.mouseButtons.ZOOM:

				if ( scope.enableZoom === false ) return;

				handleMouseDownDolly( event );

				state = STATE.DOLLY;

				break;

			case scope.mouseButtons.PAN:

				if ( scope.enablePan === false ) return;

				handleMouseDownPan( event );

				state = STATE.PAN;

				break;

		}

		if ( state !== STATE.NONE ) {

			document.addEventListener( 'mousemove', onMouseMove, false );
			document.addEventListener( 'mouseup', onMouseUp, false );

			scope.dispatchEvent( startEvent );

		}

	}

	function onMouseMove( event ) {

		if ( scope.enabled === false ) return;

		event.preventDefault();

		switch ( state ) {

			case STATE.ROTATE:

				if ( scope.enableRotate === false ) return;

				handleMouseMoveRotate( event );

				break;

			case STATE.DOLLY:

				if ( scope.enableZoom === false ) return;

				handleMouseMoveDolly( event );

				break;

			case STATE.PAN:

				if ( scope.enablePan === false ) return;

				handleMouseMovePan( event );

				break;

		}

	}

	function onMouseUp( event ) {

		if ( scope.enabled === false ) return;

		handleMouseUp( event );

		document.removeEventListener( 'mousemove', onMouseMove, false );
		document.removeEventListener( 'mouseup', onMouseUp, false );

		scope.dispatchEvent( endEvent );

		state = STATE.NONE;

	}

	function onMouseWheel( event ) {

		if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;

		event.preventDefault();
		event.stopPropagation();

		handleMouseWheel( event );

		scope.dispatchEvent( startEvent ); // not sure why these are here...
		scope.dispatchEvent( endEvent );

	}

	function onKeyDown( event ) {

		if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;

		handleKeyDown( event );

	}

	function onTouchStart( event ) {

		if ( scope.enabled === false ) return;

		switch ( event.touches.length ) {

			case 1:	// one-fingered touch: rotate

				if ( scope.enableRotate === false ) return;

				handleTouchStartRotate( event );

				state = STATE.TOUCH_ROTATE;

				break;

			case 2:	// two-fingered touch: dolly

				if ( scope.enableZoom === false ) return;

				handleTouchStartDolly( event );

				state = STATE.TOUCH_DOLLY;

				break;

			case 3: // three-fingered touch: pan

				if ( scope.enablePan === false ) return;

				handleTouchStartPan( event );

				state = STATE.TOUCH_PAN;

				break;

			default:

				state = STATE.NONE;

		}

		if ( state !== STATE.NONE ) {

			scope.dispatchEvent( startEvent );

		}

	}

	function onTouchMove( event ) {

		if ( scope.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		switch ( event.touches.length ) {

			case 1: // one-fingered touch: rotate

				if ( scope.enableRotate === false ) return;
				if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...

				handleTouchMoveRotate( event );

				break;

			case 2: // two-fingered touch: dolly

				if ( scope.enableZoom === false ) return;
				if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...

				handleTouchMoveDolly( event );

				break;

			case 3: // three-fingered touch: pan

				if ( scope.enablePan === false ) return;
				if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...

				handleTouchMovePan( event );

				break;

			default:

				state = STATE.NONE;

		}

	}

	function onTouchEnd( event ) {

		if ( scope.enabled === false ) return;

		handleTouchEnd( event );

		scope.dispatchEvent( endEvent );

		state = STATE.NONE;

	}

	function onContextMenu( event ) {

		if ( scope.enabled === false ) return;

		event.preventDefault();

	}

	//

	scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );

	scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
	scope.domElement.addEventListener( 'wheel', onMouseWheel, false );

	scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
	scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
	scope.domElement.addEventListener( 'touchmove', onTouchMove, false );

	window.addEventListener( 'keydown', onKeyDown, false );

	// force an update at start

	this.update();

};

THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;

Object.defineProperties( THREE.OrbitControls.prototype, {

	center: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
			return this.target;

		}

	},

	// backward compatibility

	noZoom: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
			return ! this.enableZoom;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
			this.enableZoom = ! value;

		}

	},

	noRotate: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
			return ! this.enableRotate;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
			this.enableRotate = ! value;

		}

	},

	noPan: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
			return ! this.enablePan;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
			this.enablePan = ! value;

		}

	},

	noKeys: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
			return ! this.enableKeys;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
			this.enableKeys = ! value;

		}

	},

	staticMoving: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
			return ! this.enableDamping;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
			this.enableDamping = ! value;

		}

	},

	dynamicDampingFactor: {

		get: function () {

			console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
			return this.dampingFactor;

		},

		set: function ( value ) {

			console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
			this.dampingFactor = value;

		}

	}

} );


================================================
FILE: README.md
================================================


================================================
FILE: obj/girl/obj.mtl
================================================
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# �������ļ�:23.03.2015 22:13:02

newmtl wire_154215229
	Ns 32
	d 1
	Tr 0
	Tf 1 1 1
	illum 2
	Ka 0.6039 0.8431 0.8980
	Kd 0.6039 0.8431 0.8980
	Ks 0.3500 0.3500 0.3500

newmtl SkinHip_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinTorso_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl PubicHair_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000

newmtl SkinNeck_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinHead_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka Cindy_MakeUpblue.jpg
	map_Kd Cindy_MakeUpblue.jpg

newmtl SkinScalp_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyHeadMap_natural.jpg
	map_Kd CindyHeadMap_natural.jpg

newmtl Lips_1
	Ns 200.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka Cindy_Lipsvioletred.jpg
	map_Kd Cindy_Lipsvioletred.jpg

newmtl EyeSocket_1
	Ns 800.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka Cindy_MakeUpblue.jpg
	map_Kd Cindy_MakeUpblue.jpg

newmtl InnerMouth_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyHeadMap_natural.jpg
	map_Kd CindyHeadMap_natural.jpg

newmtl Nostrils_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 0.0000 0.0000 0.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyHeadMap_natural.jpg
	map_Kd CindyHeadMap_natural.jpg

newmtl Lacrimal_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyHeadMap_natural.jpg
	map_Kd CindyHeadMap_natural.jpg

newmtl Eyelashes_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 0.0588 0.0588 0.0588
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000

newmtl Eyebrows_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000

newmtl Teeth_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000

newmtl Gums_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 0.6078 0.0000 0.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000

newmtl Tongue_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyHeadMap_natural.jpg
	map_Kd CindyHeadMap_natural.jpg

newmtl LCornea_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeLeft_darkblue.jpg
	map_Kd EyeLeft_darkblue.jpg

newmtl LEyewhite_1
	Ns 650.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeLeft_darkblue.jpg
	map_Kd EyeLeft_darkblue.jpg

newmtl LPupil_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeLeft_darkblue.jpg
	map_Kd EyeLeft_darkblue.jpg

newmtl LIris_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeLeft_darkblue.jpg
	map_Kd EyeLeft_darkblue.jpg

newmtl RCornea_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeRight_darkblue.jpg
	map_Kd EyeRight_darkblue.jpg

newmtl REyewhite_1
	Ns 650.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeRight_darkblue.jpg
	map_Kd EyeRight_darkblue.jpg

newmtl RPupil_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeRight_darkblue.jpg
	map_Kd EyeRight_darkblue.jpg

newmtl RIris_1
	Ns 1000.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka EyeRight_darkblue.jpg
	map_Kd EyeRight_darkblue.jpg

newmtl Nipples_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinForearm_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinArm_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinHand_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl Fingernails_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0118 0.0118 0.0118
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyNailsMap_violetred.jpg
	map_Kd CindyNailsMap_violetred.jpg

newmtl SkinLeg_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl SkinFeet_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyBodyMap_natural.jpg
	map_Kd CindyBodyMap_natural.jpg

newmtl Toenails_1
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0118 0.0118 0.0118
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka CindyNailsMap_violetred.jpg
	map_Kd CindyNailsMap_violetred.jpg

newmtl Outer_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg

newmtl OuterBangs_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg

newmtl InnerBangs_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg

newmtl Middle_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg

newmtl Inner_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg

newmtl Scull_2
	Ns 300.0000
	Ni 1.5000
	d 1.0000
	Tr 0.0000
	Tf 1.0000 1.0000 1.0000 
	illum 2
	Ka 0.0000 0.0000 0.0000
	Kd 1.0000 1.0000 1.0000
	Ks 0.0000 0.0000 0.0000
	Ke 0.0000 0.0000 0.0000
	map_Ka ErikaHR_03.jpg
	map_Kd ErikaHR_03.jpg


================================================
FILE: obj/girl/obj.obj
================================================
[File too large to display: 16.0 MB]
Download .txt
gitextract_1p4v6ikq/

├── 1.html
├── 2.html
├── 3.html
├── 4.html
├── MTLLoader.js
├── OBJLoader.js
├── OBJLoader2.js
├── OrbitControls.js
├── README.md
└── obj/
    └── girl/
        ├── obj.mtl
        └── obj.obj
Download .txt
SYMBOL INDEX (39 symbols across 4 files)

FILE: MTLLoader.js
  function resolveURL (line 355) | function resolveURL( baseUrl, url ) {
  function setMapForType (line 367) | function setMapForType( mapType, value ) {

FILE: OBJLoader.js
  function ParserState (line 14) | function ParserState() {
  function OBJLoader (line 317) | function OBJLoader( manager ) {

FILE: OBJLoader2.js
  function OBJLoader2 (line 26) | function OBJLoader2( logger, manager ) {
  function Parser (line 322) | function Parser( logger ) {
  function RawMesh (line 846) | function RawMesh( materialPerSmoothingGroup, useIndices, disregardNormal...
  function RawMeshSubGroup (line 1208) | function RawMeshSubGroup( objectName, groupName, materialName, smoothing...

FILE: OrbitControls.js
  function getAutoRotationAngle (line 274) | function getAutoRotationAngle() {
  function getZoomScale (line 280) | function getZoomScale() {
  function rotateLeft (line 286) | function rotateLeft( angle ) {
  function rotateUp (line 292) | function rotateUp( angle ) {
  function dollyIn (line 369) | function dollyIn( dollyScale ) {
  function dollyOut (line 390) | function dollyOut( dollyScale ) {
  function handleMouseDownRotate (line 415) | function handleMouseDownRotate( event ) {
  function handleMouseDownDolly (line 423) | function handleMouseDownDolly( event ) {
  function handleMouseDownPan (line 431) | function handleMouseDownPan( event ) {
  function handleMouseMoveRotate (line 439) | function handleMouseMoveRotate( event ) {
  function handleMouseMoveDolly (line 460) | function handleMouseMoveDolly( event ) {
  function handleMouseMovePan (line 484) | function handleMouseMovePan( event ) {
  function handleMouseUp (line 500) | function handleMouseUp( event ) {
  function handleMouseWheel (line 506) | function handleMouseWheel( event ) {
  function handleKeyDown (line 524) | function handleKeyDown( event ) {
  function handleTouchStartRotate (line 554) | function handleTouchStartRotate( event ) {
  function handleTouchStartDolly (line 562) | function handleTouchStartDolly( event ) {
  function handleTouchStartPan (line 575) | function handleTouchStartPan( event ) {
  function handleTouchMoveRotate (line 583) | function handleTouchMoveRotate( event ) {
  function handleTouchMoveDolly (line 604) | function handleTouchMoveDolly( event ) {
  function handleTouchMovePan (line 633) | function handleTouchMovePan( event ) {
  function handleTouchEnd (line 649) | function handleTouchEnd( event ) {
  function onMouseDown (line 659) | function onMouseDown( event ) {
  function onMouseMove (line 710) | function onMouseMove( event ) {
  function onMouseUp (line 746) | function onMouseUp( event ) {
  function onMouseWheel (line 761) | function onMouseWheel( event ) {
  function onKeyDown (line 775) | function onKeyDown( event ) {
  function onTouchStart (line 783) | function onTouchStart( event ) {
  function onTouchMove (line 833) | function onTouchMove( event ) {
  function onTouchEnd (line 877) | function onTouchEnd( event ) {
  function onContextMenu (line 889) | function onContextMenu( event ) {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (128K chars).
[
  {
    "path": "1.html",
    "chars": 1105,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden}\n "
  },
  {
    "path": "2.html",
    "chars": 1176,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden}\n "
  },
  {
    "path": "3.html",
    "chars": 3294,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow: hidden;}\n"
  },
  {
    "path": "4.html",
    "chars": 5479,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>我会让你眼晕,哈哈哈</title>\n    <style>\n      html,body,*{padding: 0;margin: 0;overflow:"
  },
  {
    "path": "MTLLoader.js",
    "chars": 10999,
    "preview": "/**\n * Loads a Wavefront .mtl file specifying materials\n *\n * @author angelxuanchang\n */\n\nTHREE.MTLLoader = function ( m"
  },
  {
    "path": "OBJLoader.js",
    "chars": 16554,
    "preview": "/**\n * @author mrdoob / http://mrdoob.com/\n */\n\nTHREE.OBJLoader = ( function () {\n\n\t// o object_name | g group_name\n\tvar"
  },
  {
    "path": "OBJLoader2.js",
    "chars": 42560,
    "preview": "/**\n  * @author Kai Salmen / https://kaisalmen.de\n  * Development repository: https://github.com/kaisalmen/WWOBJLoader\n "
  },
  {
    "path": "OrbitControls.js",
    "chars": 22659,
    "preview": "/**\n * @author qiao / https://github.com/qiao\n * @author mrdoob / http://mrdoob.com\n * @author alteredq / http://altered"
  },
  {
    "path": "README.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "obj/girl/obj.mtl",
    "chars": 10303,
    "preview": "# 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\tN"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the WrenchDE/dizzyRetroSnake-three.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (16.1 MB), approximately 34.1k tokens, and a symbol index with 39 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!