소스 검색

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 );
+
+	}
+
+
+};