/**
 * @author alteredq / http://alteredqualia.com/
 */

THREE.BinaryLoader = function ( showStatus ) {

	THREE.Loader.call( this, showStatus );

};

THREE.BinaryLoader.prototype = new THREE.Loader();
THREE.BinaryLoader.prototype.constructor = THREE.BinaryLoader;
THREE.BinaryLoader.prototype.supr = THREE.Loader.prototype;


// Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary)
//  - binary models consist of two files: JS and BIN
//  - parameters
//		- model (required)
//		- callback (required)
//		- bin_path (optional: if not specified, binary file will be assumed to be in the same folder as JS model file)
//		- texture_path (optional: if not specified, textures will be assumed to be in the same folder as JS model file)

THREE.BinaryLoader.prototype.load = function( parameters ) {

	// #1 load JS part via web worker

	//  This isn't really necessary, JS part is tiny,
	//  could be done by more ordinary means.

	var url = parameters.model,
		callback = parameters.callback,
		texture_path = parameters.texture_path ? parameters.texture_path : THREE.Loader.prototype.extractUrlbase( url ),
		bin_path = parameters.bin_path ? parameters.bin_path : THREE.Loader.prototype.extractUrlbase( url ),

		s = (new Date).getTime(),
		worker = new Worker( url ),
		callback_progress = this.showProgress ? THREE.Loader.prototype.updateProgress : null;

	worker.onmessage = function( event ) {

		var materials = event.data.materials,
			buffers = event.data.buffers;

		// #2 load BIN part via Ajax

		//  For some reason it is faster doing loading from here than from within the worker.
		//  Maybe passing of ginormous string as message between threads is costly?
		//  Also, worker loading huge data by Ajax still freezes browser. Go figure,
		//  worker with baked ascii JSON data keeps browser more responsive.

		THREE.BinaryLoader.prototype.loadAjaxBuffers( buffers, materials, callback, bin_path, texture_path, callback_progress );

	};

	worker.onerror = function (event) {

		alert( "worker.onerror: " + event.message + "\n" + event.data );
		event.preventDefault();

	};

	worker.postMessage( s );

};

// Binary AJAX parser based on Magi binary loader
// https://github.com/kig/magi

// Should look more into HTML5 File API
// See also other suggestions by Gregg Tavares
// https://groups.google.com/group/o3d-discuss/browse_thread/thread/a8967bc9ce1e0978

THREE.BinaryLoader.prototype.loadAjaxBuffers = function( buffers, materials, callback, bin_path, texture_path, callback_progress ) {

	var xhr = new XMLHttpRequest(),
		url = bin_path + "/" + buffers;

	var length = 0;

	xhr.onreadystatechange = function() {

		if ( xhr.readyState == 4 ) {

			if ( xhr.status == 200 || xhr.status == 0 ) {

				THREE.BinaryLoader.prototype.createBinModel( xhr.responseText, callback, texture_path, materials );

			} else {

				alert( "Couldn't load [" + url + "] [" + xhr.status + "]" );

			}

		} else if ( xhr.readyState == 3 ) {

			if ( callback_progress ) {

				if ( length == 0 ) {

					length = xhr.getResponseHeader( "Content-Length" );

				}

				callback_progress( { total: length, loaded: xhr.responseText.length } );

			}

		} else if ( xhr.readyState == 2 ) {

			length = xhr.getResponseHeader( "Content-Length" );

		}

	};

	xhr.open("GET", url, true);
	xhr.overrideMimeType("text/plain; charset=x-user-defined");
	xhr.setRequestHeader("Content-Type", "text/plain");
	xhr.send(null);

};

THREE.BinaryLoader.prototype.createBinModel = function ( data, callback, texture_path, materials ) {

	var Model = function ( texture_path ) {

		//var s = (new Date).getTime();

		var scope = this,
			currentOffset = 0,
			md,
			normals = [],
			uvs = [],
			tri_b, tri_c, tri_m, tri_na, tri_nb, tri_nc,
			quad_b, quad_c, quad_d, quad_m, quad_na, quad_nb, quad_nc, quad_nd,
			tri_uvb, tri_uvc, quad_uvb, quad_uvc, quad_uvd,
			start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv,
			start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv,
			tri_size, quad_size,
			len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv,
			len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv;


		THREE.Geometry.call( this );

		THREE.Loader.prototype.init_materials( scope, materials, texture_path );

		md = parseMetaData( data, currentOffset );
		currentOffset += md.header_bytes;

		// cache offsets

		tri_b   = md.vertex_index_bytes,
		tri_c   = md.vertex_index_bytes*2,
		tri_m   = md.vertex_index_bytes*3,
		tri_na  = md.vertex_index_bytes*3 + md.material_index_bytes,
		tri_nb  = md.vertex_index_bytes*3 + md.material_index_bytes + md.normal_index_bytes,
		tri_nc  = md.vertex_index_bytes*3 + md.material_index_bytes + md.normal_index_bytes*2,

		quad_b  = md.vertex_index_bytes,
		quad_c  = md.vertex_index_bytes*2,
		quad_d  = md.vertex_index_bytes*3,
		quad_m  = md.vertex_index_bytes*4,
		quad_na = md.vertex_index_bytes*4 + md.material_index_bytes,
		quad_nb = md.vertex_index_bytes*4 + md.material_index_bytes + md.normal_index_bytes,
		quad_nc = md.vertex_index_bytes*4 + md.material_index_bytes + md.normal_index_bytes*2,
		quad_nd = md.vertex_index_bytes*4 + md.material_index_bytes + md.normal_index_bytes*3,

		tri_uvb = md.uv_index_bytes,
		tri_uvc = md.uv_index_bytes * 2,

		quad_uvb = md.uv_index_bytes,
		quad_uvc = md.uv_index_bytes * 2,
		quad_uvd = md.uv_index_bytes * 3;

		// buffers sizes

		tri_size =  md.vertex_index_bytes * 3 + md.material_index_bytes;
		quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes;

		len_tri_flat      = md.ntri_flat      * ( tri_size );
		len_tri_smooth    = md.ntri_smooth    * ( tri_size + md.normal_index_bytes * 3 );
		len_tri_flat_uv   = md.ntri_flat_uv   * ( tri_size + md.uv_index_bytes * 3 );
		len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 );

		len_quad_flat      = md.nquad_flat      * ( quad_size );
		len_quad_smooth    = md.nquad_smooth    * ( quad_size + md.normal_index_bytes * 4 );
		len_quad_flat_uv   = md.nquad_flat_uv   * ( quad_size + md.uv_index_bytes * 4 );
		len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 );

		// read buffers

		currentOffset += init_vertices( currentOffset );
		currentOffset += init_normals( currentOffset );
		currentOffset += init_uvs( currentOffset );

		start_tri_flat 		= currentOffset;
		start_tri_smooth    = start_tri_flat    + len_tri_flat;
		start_tri_flat_uv   = start_tri_smooth  + len_tri_smooth;
		start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv;

		start_quad_flat     = start_tri_smooth_uv + len_tri_smooth_uv;
		start_quad_smooth   = start_quad_flat     + len_quad_flat;
		start_quad_flat_uv  = start_quad_smooth   + len_quad_smooth;
		start_quad_smooth_uv= start_quad_flat_uv  +len_quad_flat_uv;

		// have to first process faces with uvs
		// so that face and uv indices match

		init_triangles_flat_uv( start_tri_flat_uv );
		init_triangles_smooth_uv( start_tri_smooth_uv );

		init_quads_flat_uv( start_quad_flat_uv );
		init_quads_smooth_uv( start_quad_smooth_uv );

		// now we can process untextured faces

		init_triangles_flat( start_tri_flat );
		init_triangles_smooth( start_tri_smooth );

		init_quads_flat( start_quad_flat );
		init_quads_smooth( start_quad_smooth );

		this.computeCentroids();
		this.computeFaceNormals();

		if ( THREE.Loader.prototype.hasNormals( this ) ) this.computeTangents();

		//var e = (new Date).getTime();

		//log( "binary data parse time: " + (e-s) + " ms" );

		function parseMetaData( data, offset ) {

			var metaData = {

				'signature'               :parseString( data, offset, 8 ),
				'header_bytes'            :parseUChar8( data, offset + 8 ),

				'vertex_coordinate_bytes' :parseUChar8( data, offset + 9 ),
				'normal_coordinate_bytes' :parseUChar8( data, offset + 10 ),
				'uv_coordinate_bytes'     :parseUChar8( data, offset + 11 ),

				'vertex_index_bytes'      :parseUChar8( data, offset + 12 ),
				'normal_index_bytes'      :parseUChar8( data, offset + 13 ),
				'uv_index_bytes'          :parseUChar8( data, offset + 14 ),
				'material_index_bytes'    :parseUChar8( data, offset + 15 ),

				'nvertices'    :parseUInt32( data, offset + 16 ),
				'nnormals'     :parseUInt32( data, offset + 16 + 4*1 ),
				'nuvs'         :parseUInt32( data, offset + 16 + 4*2 ),

				'ntri_flat'      :parseUInt32( data, offset + 16 + 4*3 ),
				'ntri_smooth'    :parseUInt32( data, offset + 16 + 4*4 ),
				'ntri_flat_uv'   :parseUInt32( data, offset + 16 + 4*5 ),
				'ntri_smooth_uv' :parseUInt32( data, offset + 16 + 4*6 ),

				'nquad_flat'      :parseUInt32( data, offset + 16 + 4*7 ),
				'nquad_smooth'    :parseUInt32( data, offset + 16 + 4*8 ),
				'nquad_flat_uv'   :parseUInt32( data, offset + 16 + 4*9 ),
				'nquad_smooth_uv' :parseUInt32( data, offset + 16 + 4*10 )

			};

			/*
			log( "signature: " + metaData.signature );

			log( "header_bytes: " + metaData.header_bytes );
			log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes );
			log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes );
			log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes );

			log( "vertex_index_bytes: " + metaData.vertex_index_bytes );
			log( "normal_index_bytes: " + metaData.normal_index_bytes );
			log( "uv_index_bytes: " + metaData.uv_index_bytes );
			log( "material_index_bytes: " + metaData.material_index_bytes );

			log( "nvertices: " + metaData.nvertices );
			log( "nnormals: " + metaData.nnormals );
			log( "nuvs: " + metaData.nuvs );

			log( "ntri_flat: " + metaData.ntri_flat );
			log( "ntri_smooth: " + metaData.ntri_smooth );
			log( "ntri_flat_uv: " + metaData.ntri_flat_uv );
			log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv );

			log( "nquad_flat: " + metaData.nquad_flat );
			log( "nquad_smooth: " + metaData.nquad_smooth );
			log( "nquad_flat_uv: " + metaData.nquad_flat_uv );
			log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv );

			var total = metaData.header_bytes
					  + metaData.nvertices * metaData.vertex_coordinate_bytes * 3
					  + metaData.nnormals * metaData.normal_coordinate_bytes * 3
					  + metaData.nuvs * metaData.uv_coordinate_bytes * 2
					  + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes )
					  + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 )
					  + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 )
					  + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 )
					  + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes )
					  + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 )
					  + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 )
					  + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 );
			log( "total bytes: " + total );
			*/

			return metaData;

		};

		function parseString( data, offset, length ) {

			return data.substr( offset, length );

		};

		function parseFloat32( data, offset ) {

			var b3 = parseUChar8( data, offset ),
				b2 = parseUChar8( data, offset + 1 ),
				b1 = parseUChar8( data, offset + 2 ),
				b0 = parseUChar8( data, offset + 3 ),

				sign = 1 - ( 2 * ( b0 >> 7 ) ),
				exponent = ((( b0 << 1 ) & 0xff) | ( b1 >> 7 )) - 127,
				mantissa = (( b1 & 0x7f ) << 16) | (b2 << 8) | b3;

				if (mantissa == 0 && exponent == -127)
					return 0.0;

				return sign * ( 1 + mantissa * Math.pow( 2, -23 ) ) * Math.pow( 2, exponent );

		};

		function parseUInt32( data, offset ) {

			var b0 = parseUChar8( data, offset ),
				b1 = parseUChar8( data, offset + 1 ),
				b2 = parseUChar8( data, offset + 2 ),
				b3 = parseUChar8( data, offset + 3 );

			return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;

		};

		function parseUInt16( data, offset ) {

			var b0 = parseUChar8( data, offset ),
				b1 = parseUChar8( data, offset + 1 );

			return (b1 << 8) + b0;

		};

		function parseSChar8( data, offset ) {

			var b = parseUChar8( data, offset );
			return b > 127 ? b - 256 : b;

		};

		function parseUChar8( data, offset ) {

			return data.charCodeAt( offset ) & 0xff;

		};

		function init_vertices( start ) {

			var i, x, y, z,
				stride = md.vertex_coordinate_bytes * 3,
				end = start + md.nvertices * stride;

			for( i = start; i < end; i += stride ) {

				x = parseFloat32( data, i );
				y = parseFloat32( data, i + md.vertex_coordinate_bytes );
				z = parseFloat32( data, i + md.vertex_coordinate_bytes*2 );

				vertex( scope, x, y, z );

			}

			return md.nvertices * stride;

		};

		function init_normals( start ) {

			var i, x, y, z,
				stride = md.normal_coordinate_bytes * 3,
				end = start + md.nnormals * stride;

			for( i = start; i < end; i += stride ) {

				x = parseSChar8( data, i );
				y = parseSChar8( data, i + md.normal_coordinate_bytes );
				z = parseSChar8( data, i + md.normal_coordinate_bytes*2 );

				normals.push( x/127, y/127, z/127 );

			}

			return md.nnormals * stride;

		};

		function init_uvs( start ) {

			var i, u, v,
				stride = md.uv_coordinate_bytes * 2,
				end = start + md.nuvs * stride;

			for( i = start; i < end; i += stride ) {

				u = parseFloat32( data, i );
				v = parseFloat32( data, i + md.uv_coordinate_bytes );

				uvs.push( u, v );

			}

			return md.nuvs * stride;

		};

		function add_tri( i ) {

			var a, b, c, m;

			a = parseUInt32( data, i );
			b = parseUInt32( data, i + tri_b );
			c = parseUInt32( data, i + tri_c );

			m = parseUInt16( data, i + tri_m );

			f3( scope, a, b, c, m );

		};

		function add_tri_n( i ) {

			var a, b, c, m, na, nb, nc;

			a  = parseUInt32( data, i );
			b  = parseUInt32( data, i + tri_b );
			c  = parseUInt32( data, i + tri_c );

			m  = parseUInt16( data, i + tri_m );

			na = parseUInt32( data, i + tri_na );
			nb = parseUInt32( data, i + tri_nb );
			nc = parseUInt32( data, i + tri_nc );

			f3n( scope, normals, a, b, c, m, na, nb, nc );

		};

		function add_quad( i ) {

			var a, b, c, d, m;

			a = parseUInt32( data, i );
			b = parseUInt32( data, i + quad_b );
			c = parseUInt32( data, i + quad_c );
			d = parseUInt32( data, i + quad_d );

			m = parseUInt16( data, i + quad_m );

			f4( scope, a, b, c, d, m );

		};

		function add_quad_n( i ) {

			var a, b, c, d, m, na, nb, nc, nd;

			a  = parseUInt32( data, i );
			b  = parseUInt32( data, i + quad_b );
			c  = parseUInt32( data, i + quad_c );
			d  = parseUInt32( data, i + quad_d );

			m  = parseUInt16( data, i + quad_m );

			na = parseUInt32( data, i + quad_na );
			nb = parseUInt32( data, i + quad_nb );
			nc = parseUInt32( data, i + quad_nc );
			nd = parseUInt32( data, i + quad_nd );

			f4n( scope, normals, a, b, c, d, m, na, nb, nc, nd );

		};

		function add_uv3( i ) {

			var uva, uvb, uvc, u1, u2, u3, v1, v2, v3;

			uva = parseUInt32( data, i );
			uvb = parseUInt32( data, i + tri_uvb );
			uvc = parseUInt32( data, i + tri_uvc );

			u1 = uvs[ uva*2 ];
			v1 = uvs[ uva*2 + 1 ];

			u2 = uvs[ uvb*2 ];
			v2 = uvs[ uvb*2 + 1 ];

			u3 = uvs[ uvc*2 ];
			v3 = uvs[ uvc*2 + 1 ];

			uv3( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3 );

		};

		function add_uv4( i ) {

			var uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4;

			uva = parseUInt32( data, i );
			uvb = parseUInt32( data, i + quad_uvb );
			uvc = parseUInt32( data, i + quad_uvc );
			uvd = parseUInt32( data, i + quad_uvd );

			u1 = uvs[ uva*2 ];
			v1 = uvs[ uva*2 + 1 ];

			u2 = uvs[ uvb*2 ];
			v2 = uvs[ uvb*2 + 1 ];

			u3 = uvs[ uvc*2 ];
			v3 = uvs[ uvc*2 + 1 ];

			u4 = uvs[ uvd*2 ];
			v4 = uvs[ uvd*2 + 1 ];

			uv4( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3, u4, v4 );

		};

		function init_triangles_flat( start ) {

			var i, stride = md.vertex_index_bytes * 3 + md.material_index_bytes,
				end = start + md.ntri_flat * stride;

			for( i = start; i < end; i += stride ) {

				add_tri( i );

			}

			return end - start;

		}

		function init_triangles_flat_uv( start ) {

			var i, offset = md.vertex_index_bytes * 3 + md.material_index_bytes,
				stride = offset + md.uv_index_bytes * 3,
				end = start + md.ntri_flat_uv * stride;

			for( i = start; i < end; i += stride ) {

				add_tri( i );
				add_uv3( i + offset );

			}

			return end - start;

		};

		function init_triangles_smooth( start ) {

			var i, stride = md.vertex_index_bytes * 3 + md.material_index_bytes + md.normal_index_bytes * 3,
				end = start + md.ntri_smooth * stride;

			for( i = start; i < end; i += stride ) {

				add_tri_n( i );

			}

			return end - start;

		};

		function init_triangles_smooth_uv( start ) {

			var i, offset = md.vertex_index_bytes * 3 + md.material_index_bytes + md.normal_index_bytes * 3,
				stride = offset + md.uv_index_bytes * 3,
				end = start + md.ntri_smooth_uv * stride;

			for( i = start; i < end; i += stride ) {

				add_tri_n( i );
				add_uv3( i + offset );

			}

			return end - start;

		};

		function init_quads_flat( start ) {

			var i, stride = md.vertex_index_bytes * 4 + md.material_index_bytes,
				end = start + md.nquad_flat * stride;

			for( i = start; i < end; i += stride ) {

				add_quad( i );

			}

			return end - start;

		};

		function init_quads_flat_uv( start ) {

			var i, offset = md.vertex_index_bytes * 4 + md.material_index_bytes,
				stride = offset + md.uv_index_bytes * 4,
				end = start + md.nquad_flat_uv * stride;

			for( i = start; i < end; i += stride ) {

				add_quad( i );
				add_uv4( i + offset );

			}

			return end - start;

		};

		function init_quads_smooth( start ) {

			var i, stride = md.vertex_index_bytes * 4 + md.material_index_bytes + md.normal_index_bytes * 4,
				end = start + md.nquad_smooth * stride;

			for( i = start; i < end; i += stride ) {

				add_quad_n( i );

			}

			return end - start;

		};

		function init_quads_smooth_uv( start ) {

			var i, offset = md.vertex_index_bytes * 4 + md.material_index_bytes + md.normal_index_bytes * 4,
				stride = offset + md.uv_index_bytes * 4,
				end = start + md.nquad_smooth_uv * stride;

			for( i = start; i < end; i += stride ) {

				add_quad_n( i );
				add_uv4( i + offset );

			}

			return end - start;

		};

	};

	function vertex ( scope, x, y, z ) {

		scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );

	};

	function f3 ( scope, a, b, c, mi ) {

		var material = scope.materials[ mi ];
		scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) );

	};

	function f4 ( scope, a, b, c, d, mi ) {

		var material = scope.materials[ mi ];
		scope.faces.push( new THREE.Face4( a, b, c, d, null, null, material ) );

	};

	function f3n ( scope, normals, a, b, c, mi, na, nb, nc ) {

		var material = scope.materials[ mi ],
			nax = normals[ na*3     ],
			nay = normals[ na*3 + 1 ],
			naz = normals[ na*3 + 2 ],

			nbx = normals[ nb*3     ],
			nby = normals[ nb*3 + 1 ],
			nbz = normals[ nb*3 + 2 ],

			ncx = normals[ nc*3     ],
			ncy = normals[ nc*3 + 1 ],
			ncz = normals[ nc*3 + 2 ];

		scope.faces.push( new THREE.Face3( a, b, c,
						  [new THREE.Vector3( nax, nay, naz ),
						   new THREE.Vector3( nbx, nby, nbz ),
						   new THREE.Vector3( ncx, ncy, ncz )],
						  null,
						  material ) );

	};

	function f4n ( scope, normals, a, b, c, d, mi, na, nb, nc, nd ) {

		var material = scope.materials[ mi ],
			nax = normals[ na*3     ],
			nay = normals[ na*3 + 1 ],
			naz = normals[ na*3 + 2 ],

			nbx = normals[ nb*3     ],
			nby = normals[ nb*3 + 1 ],
			nbz = normals[ nb*3 + 2 ],

			ncx = normals[ nc*3     ],
			ncy = normals[ nc*3 + 1 ],
			ncz = normals[ nc*3 + 2 ],

			ndx = normals[ nd*3     ],
			ndy = normals[ nd*3 + 1 ],
			ndz = normals[ nd*3 + 2 ];

		scope.faces.push( new THREE.Face4( a, b, c, d,
						  [new THREE.Vector3( nax, nay, naz ),
						   new THREE.Vector3( nbx, nby, nbz ),
						   new THREE.Vector3( ncx, ncy, ncz ),
						   new THREE.Vector3( ndx, ndy, ndz )],
						  null,
						  material ) );

	};

	function uv3 ( where, u1, v1, u2, v2, u3, v3 ) {

		var uv = [];
		uv.push( new THREE.UV( u1, v1 ) );
		uv.push( new THREE.UV( u2, v2 ) );
		uv.push( new THREE.UV( u3, v3 ) );
		where.push( uv );

	};

	function uv4 ( where, u1, v1, u2, v2, u3, v3, u4, v4 ) {

		var uv = [];
		uv.push( new THREE.UV( u1, v1 ) );
		uv.push( new THREE.UV( u2, v2 ) );
		uv.push( new THREE.UV( u3, v3 ) );
		uv.push( new THREE.UV( u4, v4 ) );
		where.push( uv );

	};

	Model.prototype = new THREE.Geometry();
	Model.prototype.constructor = Model;

	callback( new Model( texture_path ) );

};
