浏览代码

Transfered binary-format related snippets from old Loader into BinaryLoader.

Binary loader was not yet refactored to use new format, which means it supports only subset of possible face types.
alteredq 14 年之前
父节点
当前提交
4a3fcf4a75
共有 1 个文件被更改,包括 769 次插入0 次删除
  1. 769 0
      src/extras/io/BinaryLoader.js

+ 769 - 0
src/extras/io/BinaryLoader.js

@@ -0,0 +1,769 @@
+/**
+ * @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;
+
+
+THREE.BinaryLoader.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)
+
+	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.LoaderOld.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
+
+	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);
+
+	},
+
+	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();
+
+			//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 );
+
+					THREE.BinaryLoader.prototype.v( 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 );
+
+				THREE.BinaryLoader.prototype.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 );
+
+				THREE.BinaryLoader.prototype.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 );
+
+				THREE.BinaryLoader.prototype.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 );
+
+				THREE.BinaryLoader.prototype.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 ];
+
+				THREE.BinaryLoader.prototype.uv3( scope.uvs, 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 ];
+
+				THREE.BinaryLoader.prototype.uv4( scope.uvs, 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;
+
+			}
+
+		}
+
+		Model.prototype = new THREE.Geometry();
+		Model.prototype.constructor = Model;
+
+		callback( new Model( texture_path ) );
+
+	},
+
+
+	v: function( scope, x, y, z ) {
+
+		scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
+
+	},
+	
+	f3: function( scope, a, b, c, mi ) {
+
+		var material = scope.materials[ mi ];
+		scope.faces.push( new THREE.Face3( a, b, c, null, material ) );
+
+	},
+
+	f4: function( scope, a, b, c, d, mi ) {
+
+		var material = scope.materials[ mi ];
+		scope.faces.push( new THREE.Face4( a, b, c, d, null, material ) );
+
+	},
+
+	f3n: function( 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 )], 
+						  material ) );
+
+	},
+
+	f4n: function( 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 )], 
+						  material ) );
+
+	},
+
+	uv3: function( 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 );
+
+	},
+
+	uv4: function( 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 );
+
+	}
+
+
+};