Takahiro пре 7 година
родитељ
комит
f3f33b5c5a
1 измењених фајлова са 1167 додато и 1942 уклоњено
  1. 1167 1942
      examples/js/loaders/MMDLoader.js

+ 1167 - 1942
examples/js/loaders/MMDLoader.js

@@ -3,31 +3,23 @@
  *
  * Dependencies
  *  - mmd-parser https://github.com/takahirox/mmd-parser
- *  - ammo.js https://github.com/kripken/ammo.js
  *  - THREE.TGALoader
- *  - THREE.MMDPhysics
- *  - THREE.CCDIKSolver
  *  - THREE.OutlineEffect
  *
+ * MMDLoader creates Three.js Objects from MMD resources as
+ * PMD, PMX, VMD, and VPD files.
  *
- * This loader loads and parses PMD/PMX and VMD binary files
- * then creates mesh for Three.js.
- *
- * PMD/PMX is a model data format and VMD is a motion data format
- * used in MMD(Miku Miku Dance).
- *
- * MMD is a 3D CG animation tool which is popular in Japan.
- *
+ * PMD/PMX is a model data format, VMD is a motion data format
+ * VPD is a posing data format used in MMD(Miku Miku Dance).
  *
  * MMD official site
- *  http://www.geocities.jp/higuchuu4/index_e.htm
+ *  - http://www.geocities.jp/higuchuu4/index_e.htm
  *
- * PMD, VMD format
- *  http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4
+ * PMD, VMD format (in Japanese)
+ *  - http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4
  *
  * PMX format
- *  http://gulshan-i-raz.geo.jp/labs/2012/10/17/pmx-format1/
- *
+ *  - https://gist.github.com/felixjones/f8a06bd48f9da9a4539f
  *
  * TODO
  *  - light motion in vmd support.
@@ -37,2627 +29,1860 @@
  *  - shadow support.
  */
 
-THREE.MMDLoader = function ( manager ) {
+THREE.MMDLoader = ( function () {
 
-	THREE.Loader.call( this );
-	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
-	this.parser = new MMDParser.Parser();
-	this.textureCrossOrigin = null;
-
-};
+	/**
+	 * @param {THREE.LoadingManager} manager
+	 */
+	function MMDLoader( manager ) {
 
-THREE.MMDLoader.prototype = Object.create( THREE.Loader.prototype );
-THREE.MMDLoader.prototype.constructor = THREE.MMDLoader;
+		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
-/*
- * base64 encoded defalut toon textures toon00.bmp - toon10.bmp
- * Users don't need to prepare default texture files.
- *
- * This idea is from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
- */
-THREE.MMDLoader.prototype.defaultToonTextures = [
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/bWiiMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh8aBHZBl14e8wAAAABJRU5ErkJggg==',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOUlEQVRYR+3WMREAMAwDsYY/yoDI7MLwIiP40+RJklfcCCBAgAABAgTqArfb/QMCCBAgQIAAgbbAB3z/e0F3js2cAAAAAElFTkSuQmCC',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/B5ilMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh81dWyx0gFwKAAAAABJRU5ErkJggg==',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOklEQVRYR+3WoREAMAwDsWb/UQtCy9wxTOQJ/oQ8SXKKGwEECBAgQIBAXeDt7f4BAQQIECBAgEBb4AOz8Hzx7WLY4wAAAABJRU5ErkJggg==',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABPUlEQVRYR+1XwW7CMAy1+f9fZOMysSEOEweEOPRNdm3HbdOyIhAcklPrOs/PLy9RygBALxzcCDQFmgJNgaZAU6Ap0BR4PwX8gsRMVLssMRH5HcpzJEaWL7EVg9F1IHRlyqQohgVr4FGUlUcMJSjcUlDw0zvjeun70cLWmneoyf7NgBTQSniBTQQSuJAZsOnnaczjIMb5hCiuHKxokCrJfVnrctyZL0PkJAJe1HMil4nxeyi3Ypfn1kX51jpPvo/JeCNC4PhVdHdJw2XjBR8brF8PEIhNVn12AgP7uHsTBguBn53MUZCqv7Lp07Pn5k1Ro+uWmUNn7D+M57rtk7aG0Vo73xyF/fbFf0bPJjDXngnGocDTdFhygZjwUQrMNrDcmZlQT50VJ/g/UwNyHpu778+yW+/ksOz/BFo54P4AsUXMfRq7XWsAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMElEQVRYR+2Xv4pTQRTGf2dubhLdICiii2KnYKHVolhauKWPoGAnNr6BD6CvIVaihYuI2i1ia0BY0MZGRHQXjZj/mSPnnskfNWiWZUlzJ5k7M2cm833nO5Mziej2DWWJRUoCpQKlAntSQCqgw39/iUWAGmh37jrRnVsKlgpiqmkoGVABA7E57fvY+pJDdgKqF6HzFCSADkDq+F6AHABtQ+UMVE5D7zXod7fFNhTEckTbj5XQgHzNN+5tQvc5NG7C6BNkp6D3EmpXHDR+dQAjFLchW3VS9rlw3JBh+B7ys5Cf9z0GW1C/7P32AyBAOAz1q4jGliIH3YPuBnSfQX4OGreTIgEYQb/pBDtPnEQ4CivXYPAWBk13oHrB54yA9QuSn2H4AcKRpEILDt0BUzj+RLR1V5EqjD66NPRBVpLcQwjHoHYJOhsQv6U4mnzmrIXJCFr4LDwm/xBUoboG9XX4cc9VKdYoSA2yk5NQLJaKDUjTBoveG3Z2TElTxwjNK4M3LEZgUdDdruvcXzKBpStgp2NPiWi3ks9ZXxIoFVi+AvHLdc9TqtjL3/aYjpPlrzOcEnK62Szhimdd7xX232zFDTgtxezOu3WNMRLjiKgjtOhHVMd1loynVHvOgjuIIJMaELEqhJAV/RCSLbWTcfPFakFgFlALTRRvx+ok6Hlp/Q+v3fmx90bMyUzaEAhmM3KvHlXTL5DxnbGf/1M8RNNACLL5MNtPxP/mypJAqcDSFfgFhpYqWUzhTEAAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
-	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII='
-];
-
-/*
- * Set 'anonymous' for the the texture image file in other domain
- * even if server responds with "Access-Control-Allow-Origin: *"
- * because some image operation fails in MMDLoader.
- */
-THREE.MMDLoader.prototype.setTextureCrossOrigin = function ( value ) {
+		this.loader = new THREE.FileLoader( this.manager );
 
-	this.textureCrossOrigin = value;
+		this.parser = null; // lazy generation
+		this.meshBuilder = new MeshBuilder( this.manager );
+		this.animationBuilder = new AnimationBuilder();
 
-};
+	}
 
-THREE.MMDLoader.prototype.load = function ( modelUrl, vmdUrls, callback, onProgress, onError ) {
+	MMDLoader.prototype = {
 
-	var scope = this;
+		constructor: MMDLoader,
 
-	this.loadModel( modelUrl, function ( mesh ) {
+		crossOrigin: undefined,
 
-		scope.loadVmds( vmdUrls, function ( vmd ) {
+		/**
+		 * @param {string} value
+		 * @return {THREE.MMDLoader}
+		 */
+		setCrossOrigin: function ( crossOrigin ) {
 
-			scope.pourVmdIntoModel( mesh, vmd );
-			callback( mesh );
+			this.crossOrigin = crossOrigin;
+			return this;
 
-		}, onProgress, onError );
+		},
 
-	}, onProgress, onError );
+		// Load MMD assets as Three.js Object
 
-};
+		/**
+		 * Loads Model file (.pmd or .pmx) as a THREE.SkinnedMesh.
+		 *
+		 * @param {string} url - url to Model(.pmd or .pmx) file
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		load: function ( url, onLoad, onProgress, onError ) {
 
-THREE.MMDLoader.prototype.loadModel = function ( url, callback, onProgress, onError ) {
+			var parser = this._getParser();
+			var builder = this.meshBuilder.setCrossOrigin( this.crossOrigin );
 
-	var scope = this;
+			var texturePath = THREE.LoaderUtils.extractUrlBase( url );
+			var modelExtension = this._extractExtension( url ).toLowerCase();
 
-	var texturePath = THREE.LoaderUtils.extractUrlBase( url );
-	var modelExtension = this.extractExtension( url );
+			// Should I detect by seeing header?
+			if ( modelExtension !== 'pmd' && modelExtension !== 'pmx' ) {
 
-	this.loadFileAsBuffer( url, function ( buffer ) {
+				if ( onError ) onError( new Error( 'THREE.MMDLoader: Unknown model file extension .' + modelExtension + '.' ) );
 
-		callback( scope.createModel( buffer, modelExtension, texturePath, onProgress, onError ) );
+				return;
 
-	}, onProgress, onError );
+			}
 
-};
+			this[ modelExtension === 'pmd' ? 'loadPMD' : 'loadPMX' ]( url, function ( data ) {
 
-THREE.MMDLoader.prototype.createModel = function ( buffer, modelExtension, texturePath, onProgress, onError ) {
+				onLoad(	builder.build( data, texturePath, onProgress, onError )	);
 
-	return this.createMesh( this.parseModel( buffer, modelExtension ), texturePath, onProgress, onError );
+			}, onProgress, onError );
 
-};
+		},
 
-THREE.MMDLoader.prototype.loadVmd = function ( url, callback, onProgress, onError ) {
+		/**
+		 * Loads Motion file(s) (.vmd) as a THREE.AnimationClip.
+		 * If two or more files are specified, they'll be merged.
+		 *
+		 * @param {string|Array<string>} url - url(s) to animation(.vmd) file(s)
+		 * @param {THREE.SkinnedMesh|THREE.Camera} object - tracks will be fitting to this object
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadAnimation: function ( url, object, onLoad, onProgress, onError ) {
 
-	var scope = this;
+			var builder = this.animationBuilder;
 
-	this.loadFileAsBuffer( url, function ( buffer ) {
+			this.loadVMD( url, function ( vmd ) {
 
-		callback( scope.parseVmd( buffer ) );
+				onLoad( object.isCamera
+					? builder.buildCameraAnimation( vmd )
+					: builder.build( vmd, object ) );
 
-	}, onProgress, onError );
+			}, onProgress, onError );
 
-};
+		},
 
-THREE.MMDLoader.prototype.loadVmds = function ( urls, callback, onProgress, onError ) {
+		/**
+		 * Loads mode file and motion file(s) as an object containing
+		 * a THREE.SkinnedMesh and a THREE.AnimationClip.
+		 * Tracks of THREE.AnimationClip are fitting to the model.
+		 *
+		 * @param {string} modelUrl - url to Model(.pmd or .pmx) file
+		 * @param {string|Array{string}} vmdUrl - url(s) to animation(.vmd) file
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadWithAnimation: function ( modelUrl, vmdUrl, onLoad, onProgress, onError ) {
 
-	var scope = this;
+			var scope = this;
 
-	var vmds = [];
-	urls = urls.slice();
+			this.load( modelUrl, function ( mesh ) {
 
-	function run() {
+				scope.loadAnimation( vmdUrl, mesh, function ( animation ) {
 
-		var url = urls.shift();
+					onLoad( {
+						mesh: mesh,
+						animation: animation
+					} );
 
-		scope.loadVmd( url, function ( vmd ) {
+				}, onProgress, onError );
 
-			vmds.push( vmd );
+			}, onProgress, onError );
 
-			if ( urls.length > 0 ) {
+		},
 
-				run();
+		// Load MMD assets as Object data parsed by MMDParser
 
-			} else {
+		/**
+		 * Loads .pmd file as an Object.
+		 *
+		 * @param {string} url - url to .pmd file
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadPMD: function ( url, onLoad, onProgress, onError ) {
 
-				callback( scope.mergeVmds( vmds ) );
+			var parser = this._getParser();
 
-			}
+			this.loader
+				.setMimeType( undefined )
+				.setResponseType( 'arraybuffer' )
+				.load( url, function ( buffer ) {
 
-		}, onProgress, onError );
+					onLoad( parser.parsePmd( buffer, true ) );
 
-	}
+				}, onProgress, onError );
 
-	run();
+		},
 
-};
+		/**
+		 * Loads .pmx file as an Object.
+		 *
+		 * @param {string} url - url to .pmx file
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadPMX: function ( url, onLoad, onProgress, onError ) {
 
-THREE.MMDLoader.prototype.loadAudio = function ( url, callback, onProgress, onError ) {
+			var parser = this._getParser();
 
-	var listener = new THREE.AudioListener();
-	var audio = new THREE.Audio( listener );
-	var loader = new THREE.AudioLoader( this.manager );
+			this.loader
+				.setMimeType( undefined )
+				.setResponseType( 'arraybuffer' )
+				.load( url, function ( buffer ) {
 
-	loader.load( url, function ( buffer ) {
+					onLoad( parser.parsePmx( buffer, true ) );
 
-		audio.setBuffer( buffer );
-		callback( audio, listener );
+				}, onProgress, onError );
 
-	}, onProgress, onError );
+		},
 
-};
+		/**
+		 * Loads .vmd file as an Object. If two or more files are specified
+		 * they'll be merged.
+		 *
+		 * @param {string|Array<string>} url - url(s) to .vmd file(s)
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadVMD: function ( url, onLoad, onProgress, onError ) {
 
-THREE.MMDLoader.prototype.loadVpd = function ( url, callback, onProgress, onError, params ) {
+			var urls = Array.isArray( url ) ? url : [ url ];
 
-	var scope = this;
+			var vmds = [];
+			var vmdNum = urls.length;
 
-	var func = ( ( params && params.charcode === 'unicode' ) ? this.loadFileAsText : this.loadFileAsShiftJISText ).bind( this );
+			var scope = this;
+			var parser = this._getParser();
 
-	func( url, function ( text ) {
+			this.loader
+				.setMimeType( undefined )
+				.setResponseType( 'arraybuffer' );
 
-		callback( scope.parseVpd( text ) );
+			for ( var i = 0, il = urls.length; i < il; i ++ ) {
 
-	}, onProgress, onError );
+				this.loader.load( urls[ i ], function ( buffer ) {
 
-};
+					vmds.push( parser.parseVmd( buffer, true ) );
 
-THREE.MMDLoader.prototype.parseModel = function ( buffer, modelExtension ) {
+					if ( vmds.length === vmdNum ) onLoad( parser.mergeVmds( vmds ) );
 
-	// Should I judge from model data header?
-	switch ( modelExtension.toLowerCase() ) {
+				}, onProgress, onError );
 
-		case 'pmd':
-			return this.parsePmd( buffer );
+			}
 
-		case 'pmx':
-			return this.parsePmx( buffer );
+		},
 
-		default:
-			throw 'extension ' + modelExtension + ' is not supported.';
+		/**
+		 * Loads .vpd file as an Object.
+		 *
+		 * @param {string} url - url to .vpd file
+		 * @param {boolean} isUnicode
+		 * @param {function} onLoad
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 */
+		loadVPD: function ( url, isUnicode, onLoad, onProgress, onError, params ) {
 
-	}
+			params = params || {};
 
-};
+			var parser = this._getParser();
 
-THREE.MMDLoader.prototype.parsePmd = function ( buffer ) {
+			this.loader
+				.setMimeType( isUnicode ? undefined : 'text/plain; charset=shift_jis' )
+				.setResponseType( 'text' )
+				.load( url, function ( text ) {
 
-	return this.parser.parsePmd( buffer, true );
+					onLoad( parser.parseVpd( text, true ) );
 
-};
+				}, onProgress, onError );
 
-THREE.MMDLoader.prototype.parsePmx = function ( buffer ) {
+		},
 
-	return this.parser.parsePmx( buffer, true );
+		// private methods
 
-};
+		_extractExtension: function ( url ) {
 
-THREE.MMDLoader.prototype.parseVmd = function ( buffer ) {
+			var index = url.lastIndexOf( '.' );
+			return index < 0 ? '' : url.slice( index + 1 );
 
-	return this.parser.parseVmd( buffer, true );
+		},
 
-};
+		_getParser: function () {
 
-THREE.MMDLoader.prototype.parseVpd = function ( text ) {
+			if ( this.parser === null ) {
 
-	return this.parser.parseVpd( text, true );
+				if ( typeof MMDParser === 'undefined' ) {
 
-};
+					throw new Error( 'THREE.MMDLoader: Import MMDParser https://github.com/takahirox/mmd-parser' );
 
-THREE.MMDLoader.prototype.mergeVmds = function ( vmds ) {
+				}
 
-	return this.parser.mergeVmds( vmds );
+				this.parser = new MMDParser.Parser();
 
-};
+			}
 
-THREE.MMDLoader.prototype.pourVmdIntoModel = function ( mesh, vmd, name ) {
+			return this.parser;
 
-	this.createAnimation( mesh, vmd, name );
+		}
 
-};
+	};
 
-THREE.MMDLoader.prototype.pourVmdIntoCamera = function ( camera, vmd, name ) {
+	// Utilities
 
-	var helper = new THREE.MMDLoader.DataCreationHelper();
+	/*
+	 * base64 encoded defalut toon textures toon00.bmp - toon10.bmp.
+	 * We don't need to request external toon image files.
+	 * This idea is from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
+	 */
+	var DEFAULT_TOON_TEXTURES = [
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/bWiiMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh8aBHZBl14e8wAAAABJRU5ErkJggg==',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOUlEQVRYR+3WMREAMAwDsYY/yoDI7MLwIiP40+RJklfcCCBAgAABAgTqArfb/QMCCBAgQIAAgbbAB3z/e0F3js2cAAAAAElFTkSuQmCC',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/B5ilMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh81dWyx0gFwKAAAAABJRU5ErkJggg==',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOklEQVRYR+3WoREAMAwDsWb/UQtCy9wxTOQJ/oQ8SXKKGwEECBAgQIBAXeDt7f4BAQQIECBAgEBb4AOz8Hzx7WLY4wAAAABJRU5ErkJggg==',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABPUlEQVRYR+1XwW7CMAy1+f9fZOMysSEOEweEOPRNdm3HbdOyIhAcklPrOs/PLy9RygBALxzcCDQFmgJNgaZAU6Ap0BR4PwX8gsRMVLssMRH5HcpzJEaWL7EVg9F1IHRlyqQohgVr4FGUlUcMJSjcUlDw0zvjeun70cLWmneoyf7NgBTQSniBTQQSuJAZsOnnaczjIMb5hCiuHKxokCrJfVnrctyZL0PkJAJe1HMil4nxeyi3Ypfn1kX51jpPvo/JeCNC4PhVdHdJw2XjBR8brF8PEIhNVn12AgP7uHsTBguBn53MUZCqv7Lp07Pn5k1Ro+uWmUNn7D+M57rtk7aG0Vo73xyF/fbFf0bPJjDXngnGocDTdFhygZjwUQrMNrDcmZlQT50VJ/g/UwNyHpu778+yW+/ksOz/BFo54P4AsUXMfRq7XWsAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMElEQVRYR+2Xv4pTQRTGf2dubhLdICiii2KnYKHVolhauKWPoGAnNr6BD6CvIVaihYuI2i1ia0BY0MZGRHQXjZj/mSPnnskfNWiWZUlzJ5k7M2cm833nO5Mziej2DWWJRUoCpQKlAntSQCqgw39/iUWAGmh37jrRnVsKlgpiqmkoGVABA7E57fvY+pJDdgKqF6HzFCSADkDq+F6AHABtQ+UMVE5D7zXod7fFNhTEckTbj5XQgHzNN+5tQvc5NG7C6BNkp6D3EmpXHDR+dQAjFLchW3VS9rlw3JBh+B7ys5Cf9z0GW1C/7P32AyBAOAz1q4jGliIH3YPuBnSfQX4OGreTIgEYQb/pBDtPnEQ4CivXYPAWBk13oHrB54yA9QuSn2H4AcKRpEILDt0BUzj+RLR1V5EqjD66NPRBVpLcQwjHoHYJOhsQv6U4mnzmrIXJCFr4LDwm/xBUoboG9XX4cc9VKdYoSA2yk5NQLJaKDUjTBoveG3Z2TElTxwjNK4M3LEZgUdDdruvcXzKBpStgp2NPiWi3ks9ZXxIoFVi+AvHLdc9TqtjL3/aYjpPlrzOcEnK62Szhimdd7xX232zFDTgtxezOu3WNMRLjiKgjtOhHVMd1loynVHvOgjuIIJMaELEqhJAV/RCSLbWTcfPFakFgFlALTRRvx+ok6Hlp/Q+v3fmx90bMyUzaEAhmM3KvHlXTL5DxnbGf/1M8RNNACLL5MNtPxP/mypJAqcDSFfgFhpYqWUzhTEAAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=',
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII='
+	];
+
+	// Builders. They build Three.js object from Object data parsed by MMDParser.
+
+	/**
+	 * @param {THREE.LoadingManager} manager
+	 */
+	function MeshBuilder( manager ) {
 
-	var initAnimation = function () {
+		this.geometryBuilder = new GeometryBuilder();
+		this.materialBuilder = new MaterialBuilder( manager );
 
-		var orderedMotions = helper.createOrderedMotionArray( vmd.cameras );
+	}
 
-		var times = [];
-		var centers = [];
-		var quaternions = [];
-		var positions = [];
-		var fovs = [];
+	MeshBuilder.prototype = {
 
-		var cInterpolations = [];
-		var qInterpolations = [];
-		var pInterpolations = [];
-		var fInterpolations = [];
+		constructor: MeshBuilder,
 
-		var quaternion = new THREE.Quaternion();
-		var euler = new THREE.Euler();
-		var position = new THREE.Vector3();
-		var center = new THREE.Vector3();
+		crossOrigin: undefined,
 
-		var pushVector3 = function ( array, vec ) {
+		/**
+		 * @param {string} crossOrigin
+		 * @return {MeshBuilder}
+		 */
+		setCrossOrigin: function ( crossOrigin ) {
 
-			array.push( vec.x );
-			array.push( vec.y );
-			array.push( vec.z );
+			this.crossOrigin = crossOrigin;
+			return this;
 
-		};
+		},
 
-		var pushQuaternion = function ( array, q ) {
+		/**
+		 * @param {Object} data - parsed PMD/PMX data
+		 * @param {string} texturePath
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 * @return {THREE.SkinnedMesh}
+		 */
+		build: function ( data, texturePath, onProgress, onError ) {
 
-			array.push( q.x );
-			array.push( q.y );
-			array.push( q.z );
-			array.push( q.w );
+			var geometry = this.geometryBuilder.build( data );
+			var material = this.materialBuilder
+					.setCrossOrigin( this.crossOrigin )
+					.setTexturePath( texturePath )
+					.build( data, geometry, onProgress, onError );
 
-		};
+			var mesh = new THREE.SkinnedMesh( geometry, material );
 
-		var pushInterpolation = function ( array, interpolation, index ) {
+			// console.log( mesh ); // for console debug
 
-			array.push( interpolation[ index * 4 + 0 ] / 127 ); // x1
-			array.push( interpolation[ index * 4 + 1 ] / 127 ); // x2
-			array.push( interpolation[ index * 4 + 2 ] / 127 ); // y1
-			array.push( interpolation[ index * 4 + 3 ] / 127 ); // y2
+			return mesh;
 
-		};
+		}
 
-		var createTrack = function ( node, type, times, values, interpolations ) {
+	};
 
-			/*
-			 * optimizes here not to let KeyframeTrackPrototype optimize
-			 * because KeyframeTrackPrototype optimizes times and values but
-			 * doesn't optimize interpolations.
-			 */
-			if ( times.length > 2 ) {
+	//
 
-				times = times.slice();
-				values = values.slice();
-				interpolations = interpolations.slice();
+	function GeometryBuilder() {
 
-				var stride = values.length / times.length;
-				var interpolateStride = ( stride === 3 ) ? 12 : 4; // 3: Vector3, others: Quaternion or Number
+	}
 
-				var index = 1;
+	GeometryBuilder.prototype = {
 
-				for ( var aheadIndex = 2, endIndex = times.length; aheadIndex < endIndex; aheadIndex ++ ) {
+		constructor: GeometryBuilder,
 
-					for ( var i = 0; i < stride; i ++ ) {
+		/**
+		 * @param {Object} data - parsed PMD/PMX data
+		 * @return {THREE.BufferGeometry}
+		 */
+		build: function ( data ) {
 
-						if ( values[ index * stride + i ] !== values[ ( index - 1 ) * stride + i ] ||
-							values[ index * stride + i ] !== values[ aheadIndex * stride + i ] ) {
+			// for geometry
+			var positions = [];
+			var uvs = [];
+			var normals = [];
 
-							index ++;
-							break;
+			var indices = [];
 
-						}
+			var groups = [];
 
-					}
+			var bones = [];
+			var skinIndices = [];
+			var skinWeights = [];
 
-					if ( aheadIndex > index ) {
+			var morphTargets = [];
+			var morphPositions = [];
 
-						times[ index ] = times[ aheadIndex ];
+			var iks = [];
+			var grants = [];
 
-						for ( var i = 0; i < stride; i ++ ) {
+			var rigidBodies = [];
+			var constraints = [];
 
-							values[ index * stride + i ] = values[ aheadIndex * stride + i ];
+			// for work
+			var offset = 0;
+			var boneTypeTable = {};
 
-						}
+			// positions, normals, uvs, skinIndices, skinWeights
 
-						for ( var i = 0; i < interpolateStride; i ++ ) {
+			for ( var i = 0; i < data.metadata.vertexCount; i ++ ) {
 
-							interpolations[ index * interpolateStride + i ] = interpolations[ aheadIndex * interpolateStride + i ];
+				var v = data.vertices[ i ];
 
-						}
+				for ( var j = 0, jl = v.position.length; j < jl; j ++ ) {
 
-					}
+					positions.push( v.position[ j ] );
 
 				}
 
-				times.length = index + 1;
-				values.length = ( index + 1 ) * stride;
-				interpolations.length = ( index + 1 ) * interpolateStride;
+				for ( var j = 0, jl = v.normal.length; j < jl; j ++ ) {
 
-			}
+					normals.push( v.normal[ j ] );
 
-			var track = new THREE[ type ]( node, times, values );
+				}
 
-			track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) {
+				for ( var j = 0, jl = v.uv.length; j < jl; j ++ ) {
 
-				return new THREE.MMDLoader.CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) );
+					uvs.push( v.uv[ j ] );
 
-			};
+				}
 
-			return track;
+				for ( var j = 0; j < 4; j ++ ) {
 
-		};
+					skinIndices.push( v.skinIndices.length - 1 >= j ? v.skinIndices[ j ] : 0.0 );
 
-		for ( var i = 0; i < orderedMotions.length; i ++ ) {
+				}
 
-			var m = orderedMotions[ i ];
+				for ( var j = 0; j < 4; j ++ ) {
 
-			var time = m.frameNum / 30;
-			var pos = m.position;
-			var rot = m.rotation;
-			var distance = m.distance;
-			var fov = m.fov;
-			var interpolation = m.interpolation;
+					skinWeights.push( v.skinWeights.length - 1 >= j ? v.skinWeights[ j ] : 0.0 );
 
-			position.set( 0, 0, - distance );
-			center.set( pos[ 0 ], pos[ 1 ], pos[ 2 ] );
+				}
 
-			euler.set( - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] );
-			quaternion.setFromEuler( euler );
+			}
 
-			position.add( center );
-			position.applyQuaternion( quaternion );
+			// indices
 
-			times.push( time );
+			for ( var i = 0; i < data.metadata.faceCount; i ++ ) {
 
-			pushVector3( centers, center );
-			pushQuaternion( quaternions, quaternion );
-			pushVector3( positions, position );
+				var face = data.faces[ i ];
 
-			fovs.push( fov );
+				for ( var j = 0, jl = face.indices.length; j < jl; j ++ ) {
 
-			for ( var j = 0; j < 3; j ++ ) {
+					indices.push( face.indices[ j ] );
 
-				pushInterpolation( cInterpolations, interpolation, j );
+				}
 
 			}
 
-			pushInterpolation( qInterpolations, interpolation, 3 );
+			// groups
 
-			// use same one parameter for x, y, z axis.
-			for ( var j = 0; j < 3; j ++ ) {
+			for ( var i = 0; i < data.metadata.materialCount; i ++ ) {
 
-				pushInterpolation( pInterpolations, interpolation, 4 );
+				var material = data.materials[ i ];
 
-			}
+				groups.push( {
+					offset: offset * 3,
+					count: material.faceCount * 3
+				} );
 
-			pushInterpolation( fInterpolations, interpolation, 5 );
+				offset += material.faceCount;
 
-		}
+			}
 
-		if ( times.length === 0 ) return;
+			// bones
 
-		var tracks = [];
+			for ( var i = 0; i < data.metadata.rigidBodyCount; i ++ ) {
 
-		tracks.push( createTrack( '.center', 'VectorKeyframeTrack', times, centers, cInterpolations ) );
-		tracks.push( createTrack( '.quaternion', 'QuaternionKeyframeTrack', times, quaternions, qInterpolations ) );
-		tracks.push( createTrack( '.position', 'VectorKeyframeTrack', times, positions, pInterpolations ) );
-		tracks.push( createTrack( '.fov', 'NumberKeyframeTrack', times, fovs, fInterpolations ) );
+				var body = data.rigidBodies[ i ];
+				var value = boneTypeTable[ body.boneIndex ];
 
-		var clip = new THREE.AnimationClip( name === undefined ? THREE.Math.generateUUID() : name, - 1, tracks );
+				// keeps greater number if already value is set without any special reasons
+				value = value === undefined ? body.type : Math.max( body.type, value );
 
-		if ( camera.center === undefined ) camera.center = new THREE.Vector3( 0, 0, 0 );
-		if ( camera.animations === undefined ) camera.animations = [];
-		camera.animations.push( clip );
+				boneTypeTable[ body.boneIndex ] = value;
 
-	};
+			}
 
-	initAnimation();
+			for ( var i = 0; i < data.metadata.boneCount; i ++ ) {
 
-};
+				var boneData = data.bones[ i ];
 
-THREE.MMDLoader.prototype.extractExtension = function ( url ) {
+				var bone = {
+					parent: boneData.parentIndex,
+					name: boneData.name,
+					pos: boneData.position.slice( 0, 3 ),
+					rotq: [ 0, 0, 0, 1 ],
+					scl: [ 1, 1, 1 ],
+					rigidBodyType: boneTypeTable[ i ] !== undefined ? boneTypeTable[ i ] : - 1
+				};
 
-	var index = url.lastIndexOf( '.' );
+				if ( bone.parent !== - 1 ) {
 
-	if ( index < 0 ) {
+					bone.pos[ 0 ] -= data.bones[ bone.parent ].position[ 0 ];
+					bone.pos[ 1 ] -= data.bones[ bone.parent ].position[ 1 ];
+					bone.pos[ 2 ] -= data.bones[ bone.parent ].position[ 2 ];
 
-		return null;
+				}
 
-	}
+				bones.push( bone );
 
-	return url.slice( index + 1 );
+			}
 
-};
+			// iks
 
-THREE.MMDLoader.prototype.loadFile = function ( url, onLoad, onProgress, onError, responseType, mimeType ) {
+			// TODO: remove duplicated codes between PMD and PMX
+			if ( data.metadata.format === 'pmd' ) {
 
-	var loader = new THREE.FileLoader( this.manager );
+				for ( var i = 0; i < data.metadata.ikCount; i ++ ) {
 
-	if ( mimeType !== undefined ) loader.setMimeType( mimeType );
+					var ik = data.iks[ i ];
 
-	loader.setResponseType( responseType );
+					var param = {
+						target: ik.target,
+						effector: ik.effector,
+						iteration: ik.iteration,
+						maxAngle: ik.maxAngle * 4,
+						links: []
+					};
 
-	var request = loader.load( url, function ( result ) {
+					for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-		onLoad( result );
+						var link = {};
+						link.index = ik.links[ j ].index;
+						link.enabled = true;
 
-	}, onProgress, onError );
+						if ( data.bones[ link.index ].name.indexOf( 'ひざ' ) >= 0 ) {
 
-	return request;
+							link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
 
-};
+						}
 
-THREE.MMDLoader.prototype.loadFileAsBuffer = function ( url, onLoad, onProgress, onError ) {
+						param.links.push( link );
 
-	this.loadFile( url, onLoad, onProgress, onError, 'arraybuffer' );
+					}
 
-};
+					iks.push( param );
 
-THREE.MMDLoader.prototype.loadFileAsText = function ( url, onLoad, onProgress, onError ) {
+				}
 
-	this.loadFile( url, onLoad, onProgress, onError, 'text' );
+			} else {
 
-};
+				for ( var i = 0; i < data.metadata.boneCount; i ++ ) {
 
-THREE.MMDLoader.prototype.loadFileAsShiftJISText = function ( url, onLoad, onProgress, onError ) {
+					var ik = data.bones[ i ].ik;
 
-	this.loadFile( url, onLoad, onProgress, onError, 'text', 'text/plain; charset=shift_jis' );
+					if ( ik === undefined ) continue;
 
-};
+					var param = {
+						target: i,
+						effector: ik.effector,
+						iteration: ik.iteration,
+						maxAngle: ik.maxAngle,
+						links: []
+					};
 
-THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress, onError ) {
+					for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-	var scope = this;
-	var geometry = new THREE.BufferGeometry();
-	var materials = [];
+						var link = {};
+						link.index = ik.links[ j ].index;
+						link.enabled = true;
 
-	var buffer = {};
+						if ( ik.links[ j ].angleLimitation === 1 ) {
 
-	buffer.vertices = [];
-	buffer.uvs = [];
-	buffer.normals = [];
-	buffer.skinIndices = [];
-	buffer.skinWeights = [];
-	buffer.indices = [];
+							link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
+							// TODO: use limitation angles
+							// link.lowerLimitationAngle;
+							// link.upperLimitationAngle;
 
-	var initVartices = function () {
+						}
 
-		for ( var i = 0; i < model.metadata.vertexCount; i ++ ) {
+						param.links.push( link );
 
-			var v = model.vertices[ i ];
+					}
 
-			for ( var j = 0, jl = v.position.length; j < jl; j ++ ) {
+					iks.push( param );
 
-				buffer.vertices.push( v.position[ j ] );
+				}
 
 			}
 
-			for ( var j = 0, jl = v.normal.length; j < jl; j ++ ) {
+			// grants
 
-				buffer.normals.push( v.normal[ j ] );
+			if ( data.metadata.format === 'pmx' ) {
 
-			}
+				for ( var i = 0; i < data.metadata.boneCount; i ++ ) {
 
-			for ( var j = 0, jl = v.uv.length; j < jl; j ++ ) {
+					var boneData = data.bones[ i ];
+					var grant = boneData.grant;
 
-				buffer.uvs.push( v.uv[ j ] );
+					if ( grant === undefined ) continue;
 
-			}
+					var param = {
+						index: i,
+						parentIndex: grant.parentIndex,
+						ratio: grant.ratio,
+						isLocal: grant.isLocal,
+						affectRotation: grant.affectRotation,
+						affectPosition: grant.affectPosition,
+						transformationClass: boneData.transformationClass
+					};
 
-			for ( var j = 0; j < 4; j ++ ) {
+					grants.push( param );
 
-				buffer.skinIndices.push( v.skinIndices.length - 1 >= j ? v.skinIndices[ j ] : 0.0 );
+				}
 
-			}
+				grants.sort( function ( a, b ) {
 
-			for ( var j = 0; j < 4; j ++ ) {
+					return a.transformationClass - b.transformationClass;
 
-				buffer.skinWeights.push( v.skinWeights.length - 1 >= j ? v.skinWeights[ j ] : 0.0 );
+				} );
 
 			}
 
-		}
+			// morph
 
-	};
+			function updateAttributes( attribute, morph, ratio ) {
 
-	var initFaces = function () {
+				for ( var i = 0; i < morph.elementCount; i ++ ) {
 
-		for ( var i = 0; i < model.metadata.faceCount; i ++ ) {
+					var element = morph.elements[ i ];
 
-			var f = model.faces[ i ];
+					var index;
 
-			for ( var j = 0, jl = f.indices.length; j < jl; j ++ ) {
+					if ( data.metadata.format === 'pmd' ) {
 
-				buffer.indices.push( f.indices[ j ] );
+						index = data.morphs[ 0 ].elements[ element.index ].index;
 
-			}
+					} else {
 
-		}
+						index = element.index;
 
-	};
+					}
 
-	var initBones = function () {
+					attribute.array[ index * 3 + 0 ] += element.position[ 0 ] * ratio;
+					attribute.array[ index * 3 + 1 ] += element.position[ 1 ] * ratio;
+					attribute.array[ index * 3 + 2 ] += element.position[ 2 ] * ratio;
 
-		var bones = [];
+				}
+
+			}
 
-		var rigidBodies = model.rigidBodies;
-		var dictionary = {};
+			for ( var i = 0; i < data.metadata.morphCount; i ++ ) {
 
-		for ( var i = 0, il = rigidBodies.length; i < il; i ++ ) {
+				var morph = data.morphs[ i ];
+				var params = { name: morph.name };
 
-			var body = rigidBodies[ i ];
-			var value = dictionary[ body.boneIndex ];
+				var attribute = new THREE.Float32BufferAttribute( data.metadata.vertexCount * 3, 3 );
+				attribute.name = morph.name;
 
-			// keeps greater number if already value is set without any special reasons
-			value = value === undefined ? body.type : Math.max( body.type, value );
+				for ( var j = 0; j < data.metadata.vertexCount * 3; j ++ ) {
 
-			dictionary[ body.boneIndex ] = value;
+					attribute.array[ j ] = positions[ j ];
 
-		}
+				}
 
-		for ( var i = 0; i < model.metadata.boneCount; i ++ ) {
+				if ( data.metadata.format === 'pmd' ) {
 
-			var bone = {};
-			var b = model.bones[ i ];
+					if ( i !== 0 ) {
 
-			bone.parent = b.parentIndex;
-			bone.name = b.name;
-			bone.pos = [ b.position[ 0 ], b.position[ 1 ], b.position[ 2 ] ];
-			bone.rotq = [ 0, 0, 0, 1 ];
-			bone.scl = [ 1, 1, 1 ];
+						updateAttributes( attribute, morph, 1.0 );
 
-			if ( bone.parent !== - 1 ) {
+					}
 
-				bone.pos[ 0 ] -= model.bones[ bone.parent ].position[ 0 ];
-				bone.pos[ 1 ] -= model.bones[ bone.parent ].position[ 1 ];
-				bone.pos[ 2 ] -= model.bones[ bone.parent ].position[ 2 ];
+				} else {
 
-			}
+					if ( morph.type === 0 ) { // group
 
-			bone.rigidBodyType = dictionary[ i ] !== undefined ? dictionary[ i ] : - 1;
+						for ( var j = 0; j < morph.elementCount; j ++ ) {
 
-			bones.push( bone );
+							var morph2 = data.morphs[ morph.elements[ j ].index ];
+							var ratio = morph.elements[ j ].ratio;
 
-		}
+							if ( morph2.type === 1 ) {
 
-		geometry.bones = bones;
+								updateAttributes( attribute, morph2, ratio );
 
-	};
+							} else {
 
-	var initIKs = function () {
+								// TODO: implement
 
-		var iks = [];
+							}
 
-		// TODO: remove duplicated codes between PMD and PMX
-		if ( model.metadata.format === 'pmd' ) {
+						}
 
-			for ( var i = 0; i < model.metadata.ikCount; i ++ ) {
+					} else if ( morph.type === 1 ) { // vertex
 
-				var ik = model.iks[ i ];
-				var param = {};
+						updateAttributes( attribute, morph, 1.0 );
 
-				param.target = ik.target;
-				param.effector = ik.effector;
-				param.iteration = ik.iteration;
-				param.maxAngle = ik.maxAngle * 4;
-				param.links = [];
+					} else if ( morph.type === 2 ) { // bone
 
-				for ( var j = 0; j < ik.links.length; j ++ ) {
+						// TODO: implement
 
-					var link = {};
-					link.index = ik.links[ j ].index;
+					} else if ( morph.type === 3 ) { // uv
 
-					if ( model.bones[ link.index ].name.indexOf( 'ひざ' ) >= 0 ) {
+						// TODO: implement
 
-						link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
+					} else if ( morph.type === 4 ) { // additional uv1
 
-					}
+						// TODO: implement
 
-					param.links.push( link );
+					} else if ( morph.type === 5 ) { // additional uv2
 
-				}
+						// TODO: implement
 
-				iks.push( param );
+					} else if ( morph.type === 6 ) { // additional uv3
 
-			}
+						// TODO: implement
 
-		} else {
+					} else if ( morph.type === 7 ) { // additional uv4
 
-			for ( var i = 0; i < model.metadata.boneCount; i ++ ) {
+						// TODO: implement
 
-				var b = model.bones[ i ];
-				var ik = b.ik;
+					} else if ( morph.type === 8 ) { // material
 
-				if ( ik === undefined ) {
+						// TODO: implement
 
-					continue;
+					}
 
 				}
 
-				var param = {};
+				morphTargets.push( params );
+				morphPositions.push( attribute );
+
+			}
 
-				param.target = i;
-				param.effector = ik.effector;
-				param.iteration = ik.iteration;
-				param.maxAngle = ik.maxAngle;
-				param.links = [];
+			// rigid bodies from rigidBodies field.
 
-				for ( var j = 0; j < ik.links.length; j ++ ) {
+			for ( var i = 0; i < data.metadata.rigidBodyCount; i ++ ) {
 
-					var link = {};
-					link.index = ik.links[ j ].index;
-					link.enabled = true;
+				var rigidBody = data.rigidBodies[ i ];
+				var params = {};
 
-					if ( ik.links[ j ].angleLimitation === 1 ) {
+				for ( var key in rigidBody ) {
 
-						link.limitation = new THREE.Vector3( 1.0, 0.0, 0.0 );
-						// TODO: use limitation angles
-						// link.lowerLimitationAngle;
-						// link.upperLimitationAngle;
+					params[ key ] = rigidBody[ key ];
 
-					}
+				}
+
+				/*
+				 * RigidBody position parameter in PMX seems global position
+				 * while the one in PMD seems offset from corresponding bone.
+				 * So unify being offset.
+				 */
+				if ( data.metadata.format === 'pmx' ) {
+
+					if ( params.boneIndex !== - 1 ) {
+
+						var bone = data.bones[ params.boneIndex ];
+						params.position[ 0 ] -= bone.position[ 0 ];
+						params.position[ 1 ] -= bone.position[ 1 ];
+						params.position[ 2 ] -= bone.position[ 2 ];
 
-					param.links.push( link );
+					}
 
 				}
 
-				iks.push( param );
+				rigidBodies.push( params );
 
 			}
 
-		}
+			// constraints from constraints field.
 
-		geometry.iks = iks;
+			for ( var i = 0; i < data.metadata.constraintCount; i ++ ) {
 
-	};
+				var constraint = data.constraints[ i ];
+				var params = {};
+
+				for ( var key in constraint ) {
 
-	var initGrants = function () {
+					params[ key ] = constraint[ key ];
 
-		if ( model.metadata.format === 'pmd' ) {
+				}
 
-			return;
+				var bodyA = rigidBodies[ params.rigidBodyIndex1 ];
+				var bodyB = rigidBodies[ params.rigidBodyIndex2 ];
 
-		}
+				// Refer to http://www20.atpages.jp/katwat/wp/?p=4135
+				if ( bodyA.type !== 0 && bodyB.type === 2 ) {
 
-		var grants = [];
+					if ( bodyA.boneIndex !== - 1 && bodyB.boneIndex !== - 1 &&
+					     data.bones[ bodyB.boneIndex ].parentIndex === bodyA.boneIndex ) {
 
-		for ( var i = 0; i < model.metadata.boneCount; i ++ ) {
+						bodyB.type = 1;
 
-			var b = model.bones[ i ];
-			var grant = b.grant;
+					}
 
-			if ( grant === undefined ) {
+				}
 
-				continue;
+				constraints.push( params );
 
 			}
 
-			var param = {};
+			// build BufferGeometry.
 
-			param.index = i;
-			param.parentIndex = grant.parentIndex;
-			param.ratio = grant.ratio;
-			param.isLocal = grant.isLocal;
-			param.affectRotation = grant.affectRotation;
-			param.affectPosition = grant.affectPosition;
-			param.transformationClass = b.transformationClass;
+			var geometry = new THREE.BufferGeometry();
 
-			grants.push( param );
+			geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+			geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+			geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
+			geometry.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( skinIndices, 4 ) );
+			geometry.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) );
+			geometry.setIndex( indices );
 
-		}
+			for ( var i = 0, il = groups.length; i < il; i ++ ) {
+
+				geometry.addGroup( groups[ i ].offset, groups[ i ].count, i );
 
-		grants.sort( function ( a, b ) {
+			}
 
-			return a.transformationClass - b.transformationClass;
+			geometry.bones = bones;
 
-		} );
+			geometry.morphTargets = morphTargets;
+			geometry.morphAttributes.position = morphPositions;
 
-		geometry.grants = grants;
+			geometry.iks = iks;
+			geometry.grants = grants;
 
-	};
+			geometry.rigidBodies = rigidBodies;
+			geometry.constraints = constraints;
 
-	var initMorphs = function () {
+			geometry.mmdFormat = data.metadata.format;
 
-		function updateVertex( attribute, index, v, ratio ) {
+			geometry.computeBoundingSphere();
 
-			attribute.array[ index * 3 + 0 ] += v.position[ 0 ] * ratio;
-			attribute.array[ index * 3 + 1 ] += v.position[ 1 ] * ratio;
-			attribute.array[ index * 3 + 2 ] += v.position[ 2 ] * ratio;
+			return geometry;
 
 		}
 
-		function updateVertices( attribute, m, ratio ) {
+	};
 
-			for ( var i = 0; i < m.elementCount; i ++ ) {
+	//
 
-				var v = m.elements[ i ];
+	/**
+	 * @param {THREE.LoadingManager} manager
+	 */
+	function MaterialBuilder( manager ) {
 
-				var index;
+		this.manager = manager;
 
-				if ( model.metadata.format === 'pmd' ) {
+		this.textureLoader = new THREE.TextureLoader( this.manager );
+		this.tgaLoader = null; // lazy generation
 
-					index = model.morphs[ 0 ].elements[ v.index ].index;
+	}
 
-				} else {
+	MaterialBuilder.prototype = {
 
-					index = v.index;
+		constructor: MaterialBuilder,
 
-				}
+		crossOrigin: undefined,
 
-				updateVertex( attribute, index, v, ratio );
+		texturePath: undefined,
 
-			}
+		/**
+		 * @param {string} crossOrigin
+		 * @return {MaterialBuilder}
+		 */
+		setCrossOrigin: function ( crossOrigin ) {
 
-		}
+			this.crossOrigin = crossOrigin;
+			return this;
 
-		var morphTargets = [];
-		var attributes = [];
+		},
 
-		for ( var i = 0; i < model.metadata.morphCount; i ++ ) {
+		/**
+		 * @param {string} texturePath
+		 * @return {MaterialBuilder}
+		 */
+		setTexturePath: function ( texturePath ) {
 
-			var m = model.morphs[ i ];
-			var params = { name: m.name };
+			this.texturePath = texturePath;
+			return this;
 
-			var attribute = new THREE.Float32BufferAttribute( model.metadata.vertexCount * 3, 3 );
-			attribute.name = m.name;
+		},
 
-			for ( var j = 0; j < model.metadata.vertexCount * 3; j ++ ) {
+		/**
+		 * @param {Object} data - parsed PMD/PMX data
+		 * @param {THREE.BufferGeometry} geometry - some properties are dependend on geometry
+		 * @param {function} onProgress
+		 * @param {function} onError
+		 * @return {Array<THREE.MeshToonMaterial>}
+		 */
+		build: function ( data, geometry, onProgress, onError ) {
 
-				attribute.array[ j ] = buffer.vertices[ j ];
+			var materials = [];
 
-			}
+			var textures = {};
 
-			if ( model.metadata.format === 'pmd' ) {
+			this.textureLoader.setCrossOrigin( this.crossOrigin );
 
-				if ( i !== 0 ) {
+			// materials
 
-					updateVertices( attribute, m, 1.0 );
+			for ( var i = 0; i < data.metadata.materialCount; i ++ ) {
 
-				}
+				var material = data.materials[ i ];
 
-			} else {
+				var params = { userData: {} };
 
-				if ( m.type === 0 ) { // group
+				if ( material.name !== undefined ) params.name = material.name;
 
-					for ( var j = 0; j < m.elementCount; j ++ ) {
+				/*
+				 * Color
+				 *
+				 * MMD         MeshToonMaterial
+				 * diffuse  -  color
+				 * specular -  specular
+				 * ambient  -  emissive * a
+				 *               (a = 1.0 without map texture or 0.2 with map texture)
+				 *
+				 * MeshToonMaterial doesn't have ambient. Set it to emissive instead.
+				 * It'll be too bright if material has map texture so using coef 0.2.
+				 */
+				params.color = new THREE.Color().fromArray( material.diffuse );
+				params.opacity = material.diffuse[ 3 ];
+				params.specular = new THREE.Color().fromArray( material.specular );
+				params.emissive = new THREE.Color().fromArray( material.ambient );
+				params.shininess = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+				params.transparent = params.opacity !== 1.0;
 
-						var m2 = model.morphs[ m.elements[ j ].index ];
-						var ratio = m.elements[ j ].ratio;
+				// 
 
-						if ( m2.type === 1 ) {
+				params.skinning = geometry.bones.length > 0 ? true : false;
+				params.morphTargets = geometry.morphTargets.length > 0 ? true : false;
+				params.lights = true;
+				params.fog = true;
 
-							updateVertices( attribute, m2, ratio );
+				// blend
 
-						} else {
+				params.blending = THREE.CustomBlending;
+				params.blendSrc = THREE.SrcAlphaFactor;
+				params.blendDst = THREE.OneMinusSrcAlphaFactor;
+				params.blendSrcAlpha = THREE.SrcAlphaFactor;
+				params.blendDstAlpha = THREE.DstAlphaFactor;
 
-							// TODO: implement
+				// side
 
-						}
+				if ( data.metadata.format === 'pmx' && ( material.flag & 0x1 ) === 1 ) {
 
-					}
+					params.side = THREE.DoubleSide;
+
+				} else {
 
-				} else if ( m.type === 1 ) { // vertex
+					params.side = params.opacity === 1.0 ? THREE.FrontSide : THREE.DoubleSide;
 
-					updateVertices( attribute, m, 1.0 );
+				}
 
-				} else if ( m.type === 2 ) { // bone
+				if ( data.metadata.format === 'pmd' ) {
 
-					// TODO: implement
+					// map, envMap
 
-				} else if ( m.type === 3 ) { // uv
+					if ( material.fileName ) {
 
-					// TODO: implement
+						var fileName = material.fileName;
+						var fileNames = fileName.split( '*' );
 
-				} else if ( m.type === 4 ) { // additional uv1
+						// fileNames[ 0 ]: mapFileName
+						// fileNames[ 1 ]: envMapFileName( optional )
 
-					// TODO: implement
+						params.map = this._loadTexture( fileNames[ 0 ], textures );
 
-				} else if ( m.type === 5 ) { // additional uv2
+						if ( fileNames.length > 1 ) {
 
-					// TODO: implement
+							var extension = fileNames[ 1 ].slice( - 4 ).toLowerCase();
 
-				} else if ( m.type === 6 ) { // additional uv3
+							params.envMap = this._loadTexture(
+								fileNames[ 1 ],
+								textures,
+								{ sphericalReflectionMapping: true }
+							);
 
-					// TODO: implement
+							params.combine = extension === '.sph'
+								? THREE.MultiplyOperation
+								: THREE.AddOperation;
 
-				} else if ( m.type === 7 ) { // additional uv4
+						}
 
-					// TODO: implement
+					}
 
-				} else if ( m.type === 8 ) { // material
+					// gradientMap
 
-					// TODO: implement
+					var toonFileName = ( material.toonIndex === - 1 )
+						? 'toon00.bmp'
+						: data.toonTextures[ material.toonIndex ].fileName;
 
-				}
+					params.gradientMap = this._loadTexture(
+						toonFileName,
+						textures,
+						{
+							isToonTexture: true,
+							isDefaultToonTexture: this._isDefaultToonTexture( toonFileName )
+						}
+					);
 
-			}
+					// parameters for OutlineEffect
 
-			morphTargets.push( params );
-			attributes.push( attribute );
+					params.userData.outlineParameters = {
+						thickness: material.edgeFlag === 1 ? 0.003 : 0.0,
+						color: [ 0, 0, 0 ],
+						alpha: 1.0,
+						visible: material.edgeFlag === 1
+					};
 
-		}
+				} else {
 
-		geometry.morphTargets = morphTargets;
-		geometry.morphAttributes.position = attributes;
+					// map
 
-	};
+					if ( material.textureIndex !== - 1 ) {
 
-	var initMaterials = function () {
+						params.map = this._loadTexture( data.textures[ material.textureIndex ], textures );
 
-		var textures = {};
-		var textureLoader = new THREE.TextureLoader( scope.manager );
-		var tgaLoader = new THREE.TGALoader( scope.manager );
-		var canvas = document.createElement( 'canvas' );
-		var context = canvas.getContext( '2d' );
-		var offset = 0;
-		var materialParams = [];
+					}
 
-		if ( scope.textureCrossOrigin !== null ) textureLoader.setCrossOrigin( scope.textureCrossOrigin );
+					// envMap TODO: support m.envFlag === 3
 
-		function loadTexture( filePath, params ) {
+					if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) {
 
-			if ( params === undefined ) {
+						params.envMap = this._loadTexture(
+							data.textures[ material.envTextureIndex ],
+							textures, { sphericalReflectionMapping: true }
+						);
 
-				params = {};
+						params.combine = material.envFlag === 1
+							? THREE.MultiplyOperation
+							: THREE.AddOperation;
 
-			}
+					}
 
-			var fullPath;
+					// gradientMap
 
-			if ( params.defaultTexturePath === true ) {
+					var toonFileName, isDefaultToon;
 
-				try {
+					if ( material.toonIndex === - 1 || material.toonFlag !== 0 ) {
 
-					fullPath = scope.defaultToonTextures[ parseInt( filePath.match( 'toon([0-9]{2})\.bmp$' )[ 1 ] ) ];
+						toonFileName = 'toon' + ( '0' + ( material.toonIndex + 1 ) ).slice( - 2 ) + '.bmp';
+						isDefaultToon = true;
 
-				} catch ( e ) {
+					} else {
+
+						toonFileName = data.textures[ material.toonIndex ];
+						isDefaultToon = false;
 
-					console.warn( 'THREE.MMDLoader: ' + filePath + ' seems like not right default texture path. Using toon00.bmp instead.' );
-					fullPath = scope.defaultToonTextures[ 0 ];
+					}
+
+					params.gradientMap = this._loadTexture(
+						toonFileName,
+						textures,
+						{
+							isToonTexture: true,
+							isDefaultToonTexture: isDefaultToon
+						}
+					);
+
+					// parameters for OutlineEffect
+					params.userData.outlineParameters = {
+						thickness: material.edgeSize / 300,  // TODO: better calculation?
+						color: material.edgeColor.slice( 0, 3 ),
+						alpha: material.edgeColor[ 3 ],
+						visible: ( material.flag & 0x10 ) !== 0 && material.edgeSize > 0.0
+					};
 
 				}
 
-			} else {
+				if ( params.map !== undefined ) {
 
-				fullPath = texturePath + filePath;
+					if ( ! params.transparent ) {
 
-			}
+						this._checkImageTransparency( params.map, geometry, i );
 
-			if ( textures[ fullPath ] !== undefined ) return fullPath;
+					}
 
-			var loader = THREE.Loader.Handlers.get( fullPath );
+					params.emissive.multiplyScalar( 0.2 );
 
-			if ( loader === null ) {
+				}
 
-				loader = ( filePath.indexOf( '.tga' ) >= 0 ) ? tgaLoader : textureLoader;
+				materials.push( new THREE.MeshToonMaterial( params ) );
 
 			}
 
-			var texture = loader.load( fullPath, function ( t ) {
+			if ( data.metadata.format === 'pmx' ) {
 
-				// MMD toon texture is Axis-Y oriented
-				// but Three.js gradient map is Axis-X oriented.
-				// So here replaces the toon texture image with the rotated one.
-				if ( params.isToonTexture === true ) {
+				// set transparent true if alpha morph is defined.
 
-					var image = t.image;
-					var width = image.width;
-					var height = image.height;
+				function checkAlphaMorph( elements, materials ) {
 
-					canvas.width = width;
-					canvas.height = height;
+					for ( var i = 0, il = elements.length; i < il; i ++ ) {
 
-					context.clearRect( 0, 0, width, height );
-					context.translate( width / 2.0, height / 2.0 );
-					context.rotate( 0.5 * Math.PI ); // 90.0 * Math.PI / 180.0
-					context.translate( - width / 2.0, - height / 2.0 );
-					context.drawImage( image, 0, 0 );
+						var element = elements[ i ];
 
-					t.image = context.getImageData( 0, 0, width, height );
+						if ( element.index === - 1 ) continue;
 
-				}
+						var material = materials[ element.index ];
 
-				t.flipY = false;
-				t.wrapS = THREE.RepeatWrapping;
-				t.wrapT = THREE.RepeatWrapping;
+						if ( material.opacity !== element.diffuse[ 3 ] ) {
 
-				for ( var i = 0; i < texture.readyCallbacks.length; i ++ ) {
+							material.transparent = true;
 
-					texture.readyCallbacks[ i ]( texture );
+						}
+
+					}
 
 				}
 
-				delete texture.readyCallbacks;
+				for ( var i = 0, il = data.morphs.length; i < il; i ++ ) {
 
-			}, onProgress, onError );
+					var morph = data.morphs[ i ];
+					var elements = morph.elements;
 
-			if ( params.sphericalReflectionMapping === true ) {
+					if ( morph.type === 0 ) {
 
-				texture.mapping = THREE.SphericalReflectionMapping;
+						for ( var j = 0, jl = elements.length; j < jl; j ++ ) {
 
-			}
+							var morph2 = model.morphs[ elements[ j ].index ];
 
-			texture.readyCallbacks = [];
+							if ( morph2.type !== 8 ) continue;
 
-			textures[ fullPath ] = texture;
+							checkAlphaMorph( morph2.elements, materials );
 
-			return fullPath;
+						}
 
-		}
+					} else if ( morph.type === 8 ) {
 
-		function getTexture( name, textures ) {
+						checkAlphaMorph( elements, materials );
 
-			if ( textures[ name ] === undefined ) {
+					}
 
-				console.warn( 'THREE.MMDLoader: Undefined texture', name );
+				}
 
 			}
 
-			return textures[ name ];
+			return materials;
 
-		}
+		},
 
-		for ( var i = 0; i < model.metadata.materialCount; i ++ ) {
+		// private methods
 
-			var m = model.materials[ i ];
-			var params = {};
+		_getTGALoader: function () {
 
-			params.faceOffset = offset;
-			params.faceNum = m.faceCount;
+			if ( this.tgaLoader === null ) {
 
-			offset += m.faceCount;
+				if ( THREE.TGALoader === undefined ) {
 
-			params.name = m.name;
+					throw new Error( 'THREE.MMDLoader: Import THREE.TGALoader' );
 
-			/*
-			 * Color
-			 *
-			 * MMD         MeshToonMaterial
-			 * diffuse  -  color
-			 * specular -  specular
-			 * ambient  -  emissive * a
-			 *               (a = 1.0 without map texture or 0.2 with map texture)
-			 *
-			 * MeshToonMaterial doesn't have ambient. Set it to emissive instead.
-			 * It'll be too bright if material has map texture so using coef 0.2.
-			 */
-			params.color = new THREE.Color( m.diffuse[ 0 ], m.diffuse[ 1 ], m.diffuse[ 2 ] );
-			params.opacity = m.diffuse[ 3 ];
-			params.specular = new THREE.Color( m.specular[ 0 ], m.specular[ 1 ], m.specular[ 2 ] );
-			params.shininess = m.shininess;
+				}
 
-			if ( params.opacity === 1.0 ) {
+				this.tgaLoader = new THREE.TGALoader( this.manager );
 
-				params.side = THREE.FrontSide;
-				params.transparent = false;
+			}
 
-			} else {
+			return this.tgaLoader;
 
-				params.side = THREE.DoubleSide;
-				params.transparent = true;
+		},
 
-			}
+		_isDefaultToonTexture: function ( name ) {
 
-			if ( model.metadata.format === 'pmd' ) {
+			if ( name.length !== 10 ) return false;
 
-				if ( m.fileName ) {
+			return /toon(10|0[0-9])\.bmp/.test( name );
 
-					var fileName = m.fileName;
-					var fileNames = [];
+		},
 
-					var index = fileName.lastIndexOf( '*' );
+		_loadTexture: function ( filePath, textures, params, onProgress, onError ) {
 
-					if ( index >= 0 ) {
+			params = params || {};
 
-						fileNames.push( fileName.slice( 0, index ) );
-						fileNames.push( fileName.slice( index + 1 ) );
+			var scope = this;
 
-					} else {
+			var fullPath;
 
-						fileNames.push( fileName );
+			if ( params.isDefaultToonTexture === true ) {
 
-					}
+				var index;
+
+				try {
 
-					for ( var j = 0; j < fileNames.length; j ++ ) {
+					index = parseInt( filePath.match( 'toon([0-9]{2})\.bmp$' )[ 1 ] );
 
-						var n = fileNames[ j ];
+				} catch ( e ) {
 
-						if ( n.indexOf( '.sph' ) >= 0 || n.indexOf( '.spa' ) >= 0 ) {
+					console.warn( 'THREE.MMDLoader: ' + filePath + ' seems like a '
+						+ 'not right default texture path. Using toon00.bmp instead.' );
 
-							params.envMap = loadTexture( n, { sphericalReflectionMapping: true } );
+					index = 0;
 
-							if ( n.indexOf( '.sph' ) >= 0 ) {
+				}
 
-								params.envMapType = THREE.MultiplyOperation;
+				fullPath = DEFAULT_TOON_TEXTURES[ index ];
 
-							} else {
+			} else {
 
-								params.envMapType = THREE.AddOperation;
+				fullPath = this.texturePath + filePath;
 
-							}
+			}
 
-						} else {
+			if ( textures[ fullPath ] !== undefined ) return textures[ fullPath ];
 
-							params.map = loadTexture( n );
+			var loader = THREE.Loader.Handlers.get( fullPath );
 
-						}
+			if ( loader === null ) {
 
-					}
+				loader = ( filePath.slice( - 4 ).toLowerCase() === '.tga' )
+					? this._getTGALoader()
+					: this.textureLoader;
 
-				}
+			}
 
-			} else {
+			var texture = loader.load( fullPath, function ( t ) {
 
-				if ( m.textureIndex !== - 1 ) {
+				// MMD toon texture is Axis-Y oriented
+				// but Three.js gradient map is Axis-X oriented.
+				// So here replaces the toon texture image with the rotated one.
+				if ( params.isToonTexture === true ) {
 
-					var n = model.textures[ m.textureIndex ];
-					params.map = loadTexture( n );
+					t.image = scope._getRotatedImage( t.image );
 
 				}
 
-				// TODO: support m.envFlag === 3
-				if ( m.envTextureIndex !== - 1 && ( m.envFlag === 1 || m.envFlag == 2 ) ) {
+				t.flipY = false;
+				t.wrapS = THREE.RepeatWrapping;
+				t.wrapT = THREE.RepeatWrapping;
 
-					var n = model.textures[ m.envTextureIndex ];
-					params.envMap = loadTexture( n, { sphericalReflectionMapping: true } );
+				for ( var i = 0; i < texture.readyCallbacks.length; i ++ ) {
 
-					if ( m.envFlag === 1 ) {
+					texture.readyCallbacks[ i ]( texture );
 
-						params.envMapType = THREE.MultiplyOperation;
+				}
 
-					} else {
+				delete texture.readyCallbacks;
 
-						params.envMapType = THREE.AddOperation;
+			}, onProgress, onError );
 
-					}
+			if ( params.sphericalReflectionMapping === true ) {
 
-				}
+				texture.mapping = THREE.SphericalReflectionMapping;
 
 			}
 
-			var coef = ( params.map === undefined ) ? 1.0 : 0.2;
-			params.emissive = new THREE.Color( m.ambient[ 0 ] * coef, m.ambient[ 1 ] * coef, m.ambient[ 2 ] * coef );
+			texture.readyCallbacks = [];
 
-			materialParams.push( params );
+			textures[ fullPath ] = texture;
 
-		}
+			return texture;
 
-		for ( var i = 0; i < materialParams.length; i ++ ) {
+		},
 
-			var p = materialParams[ i ];
-			var p2 = model.materials[ i ];
-			var m = new THREE.MeshToonMaterial();
+		_getRotatedImage: function ( image ) {
 
-			geometry.addGroup( p.faceOffset * 3, p.faceNum * 3, i );
+			var canvas = document.createElement( 'canvas' );
+			var context = canvas.getContext( '2d' );
 
-			if ( p.name !== undefined ) m.name = p.name;
+			var width = image.width;
+			var height = image.height;
 
-			m.skinning = geometry.bones.length > 0 ? true : false;
-			m.morphTargets = geometry.morphTargets.length > 0 ? true : false;
-			m.lights = true;
-			m.side = ( model.metadata.format === 'pmx' && ( p2.flag & 0x1 ) === 1 ) ? THREE.DoubleSide : p.side;
-			m.transparent = p.transparent;
-			m.fog = true;
+			canvas.width = width;
+			canvas.height = height;
 
-			m.blending = THREE.CustomBlending;
-			m.blendSrc = THREE.SrcAlphaFactor;
-			m.blendDst = THREE.OneMinusSrcAlphaFactor;
-			m.blendSrcAlpha = THREE.SrcAlphaFactor;
-			m.blendDstAlpha = THREE.DstAlphaFactor;
+			context.clearRect( 0, 0, width, height );
+			context.translate( width / 2.0, height / 2.0 );
+			context.rotate( 0.5 * Math.PI ); // 90.0 * Math.PI / 180.0
+			context.translate( - width / 2.0, - height / 2.0 );
+			context.drawImage( image, 0, 0 );
 
-			if ( p.map !== undefined ) {
+			return context.getImageData( 0, 0, width, height );
 
-				m.faceOffset = p.faceOffset;
-				m.faceNum = p.faceNum;
+		},
 
-				// Check if this part of the texture image the material uses requires transparency
-				function checkTextureTransparency( m ) {
+		// Check if the partial image area used by the texture is transparent.
+		_checkImageTransparency: function ( map, geometry, groupIndex ) {
 
-					m.map.readyCallbacks.push( function ( t ) {
+			map.readyCallbacks.push( function ( texture ) {
 
-						// Is there any efficient ways?
-						function createImageData( image ) {
+				// Is there any efficient ways?
+				function createImageData( image ) {
 
-							var c = document.createElement( 'canvas' );
-							c.width = image.width;
-							c.height = image.height;
+					var canvas = document.createElement( 'canvas' );
+					canvas.width = image.width;
+					canvas.height = image.height;
 
-							var ctx = c.getContext( '2d' );
-							ctx.drawImage( image, 0, 0 );
+					var context = canvas.getContext( '2d' );
+					context.drawImage( image, 0, 0 );
 
-							return ctx.getImageData( 0, 0, c.width, c.height );
-
-						}
-
-						function detectTextureTransparency( image, uvs, indices ) {
-
-							var width = image.width;
-							var height = image.height;
-							var data = image.data;
-							var threshold = 253;
-
-							if ( data.length / ( width * height ) !== 4 ) {
-
-								return false;
-
-							}
-
-							for ( var i = 0; i < indices.length; i += 3 ) {
-
-								var centerUV = { x: 0.0, y: 0.0 };
-
-								for ( var j = 0; j < 3; j ++ ) {
-
-									var index = indices[ i * 3 + j ];
-									var uv = { x: uvs[ index * 2 + 0 ], y: uvs[ index * 2 + 1 ] };
-
-									if ( getAlphaByUv( image, uv ) < threshold ) {
-
-										return true;
-
-									}
-
-									centerUV.x += uv.x;
-									centerUV.y += uv.y;
-
-								}
-
-								centerUV.x /= 3;
-								centerUV.y /= 3;
-
-								if ( getAlphaByUv( image, centerUV ) < threshold ) {
-
-									return true;
-
-								}
-
-							}
-
-							return false;
-
-						}
-
-						/*
-						 * This method expects
-						 *   t.flipY = false
-						 *   t.wrapS = THREE.RepeatWrapping
-						 *   t.wrapT = THREE.RepeatWrapping
-						 * TODO: more precise
-						 */
-						function getAlphaByUv( image, uv ) {
-
-							var width = image.width;
-							var height = image.height;
-
-							var x = Math.round( uv.x * width ) % width;
-							var y = Math.round( uv.y * height ) % height;
-
-							if ( x < 0 ) {
-
-								x += width;
-
-							}
-
-							if ( y < 0 ) {
-
-								y += height;
-
-							}
-
-							var index = y * width + x;
-
-							return image.data[ index * 4 + 3 ];
-
-						}
-
-						var imageData = t.image.data !== undefined ? t.image : createImageData( t.image );
-						var indices = geometry.index.array.slice( m.faceOffset * 3, m.faceOffset * 3 + m.faceNum * 3 );
-
-						if ( detectTextureTransparency( imageData, geometry.attributes.uv.array, indices ) ) m.transparent = true;
-
-						delete m.faceOffset;
-						delete m.faceNum;
-
-					} );
+					return context.getImageData( 0, 0, canvas.width, canvas.height );
 
 				}
 
-				m.map = getTexture( p.map, textures );
-				checkTextureTransparency( m );
-
-			}
-
-			if ( p.envMap !== undefined ) {
+				function detectImageTransparency( image, uvs, indices ) {
 
-				m.envMap = getTexture( p.envMap, textures );
-				m.combine = p.envMapType;
+					var width = image.width;
+					var height = image.height;
+					var data = image.data;
+					var threshold = 253;
 
-			}
+					if ( data.length / ( width * height ) !== 4 ) return false;
 
-			m.opacity = p.opacity;
-			m.color = p.color;
+					for ( var i = 0; i < indices.length; i += 3 ) {
 
-			if ( p.emissive !== undefined ) {
+						var centerUV = { x: 0.0, y: 0.0 };
 
-				m.emissive = p.emissive;
+						for ( var j = 0; j < 3; j ++ ) {
 
-			}
+							var index = indices[ i * 3 + j ];
+							var uv = { x: uvs[ index * 2 + 0 ], y: uvs[ index * 2 + 1 ] };
 
-			m.specular = p.specular;
-			m.shininess = Math.max( p.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+							if ( getAlphaByUv( image, uv ) < threshold ) return true;
 
-			if ( model.metadata.format === 'pmd' ) {
+							centerUV.x += uv.x;
+							centerUV.y += uv.y;
 
-				function isDefaultToonTexture( n ) {
+						}
 
-					if ( n.length !== 10 ) {
+						centerUV.x /= 3;
+						centerUV.y /= 3;
 
-						return false;
+						if ( getAlphaByUv( image, centerUV ) < threshold ) return true;
 
 					}
 
-					return n.match( /toon(10|0[0-9]).bmp/ ) === null ? false : true;
-
-				}
-
-				// parameters for OutlineEffect
-				m.outlineParameters = {
-					thickness: p2.edgeFlag === 1 ? 0.003 : 0.0,
-					color: new THREE.Color( 0.0, 0.0, 0.0 ),
-					alpha: 1.0
-				};
-
-				if ( m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
-
-				var toonFileName = ( p2.toonIndex === - 1 ) ? 'toon00.bmp' : model.toonTextures[ p2.toonIndex ].fileName;
-				var uuid = loadTexture( toonFileName, { isToonTexture: true, defaultTexturePath: isDefaultToonTexture( toonFileName ) } );
-				m.gradientMap = getTexture( uuid, textures );
-
-			} else {
-
-				// parameters for OutlineEffect
-				m.outlineParameters = {
-					thickness: p2.edgeSize / 300,
-					color: new THREE.Color( p2.edgeColor[ 0 ], p2.edgeColor[ 1 ], p2.edgeColor[ 2 ] ),
-					alpha: p2.edgeColor[ 3 ]
-				};
-
-				if ( ( p2.flag & 0x10 ) === 0 || m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
-
-				var toonFileName, isDefaultToon;
-
-				if ( p2.toonIndex === - 1 || p2.toonFlag !== 0 ) {
-
-					var num = p2.toonIndex + 1;
-					toonFileName = 'toon' + ( num < 10 ? '0' + num : num ) + '.bmp';
-					isDefaultToon = true;
-
-				} else {
-
-					toonFileName = model.textures[ p2.toonIndex ];
-					isDefaultToon = false;
-
-				}
-
-				var uuid = loadTexture( toonFileName, { isToonTexture: true, defaultTexturePath: isDefaultToon } );
-				m.gradientMap = getTexture( uuid, textures );
-
-			}
-
-			materials.push( m );
-
-		}
-
-		if ( model.metadata.format === 'pmx' ) {
-
-			function checkAlphaMorph( morph, elements ) {
-
-				if ( morph.type !== 8 ) {
-
-					return;
+					return false;
 
 				}
 
-				for ( var i = 0; i < elements.length; i ++ ) {
-
-					var e = elements[ i ];
+				/*
+				 * This method expects
+				 *   texture.flipY = false
+				 *   texture.wrapS = THREE.RepeatWrapping
+				 *   texture.wrapT = THREE.RepeatWrapping
+				 * TODO: more precise
+				 */
+				function getAlphaByUv( image, uv ) {
 
-					if ( e.index === - 1 ) {
-
-						continue;
-
-					}
+					var width = image.width;
+					var height = image.height;
 
-					var m = materials[ e.index ];
+					var x = Math.round( uv.x * width ) % width;
+					var y = Math.round( uv.y * height ) % height;
 
-					if ( m.opacity !== e.diffuse[ 3 ] ) {
+					if ( x < 0 ) x += width;
+					if ( y < 0 ) y += height;
 
-						m.transparent = true;
+					var index = y * width + x;
 
-					}
+					return image.data[ index * 4 + 3 ];
 
 				}
 
-			}
-
-			for ( var i = 0; i < model.morphs.length; i ++ ) {
-
-				var morph = model.morphs[ i ];
-				var elements = morph.elements;
-
-				if ( morph.type === 0 ) {
-
-					for ( var j = 0; j < elements.length; j ++ ) {
-
-						var morph2 = model.morphs[ elements[ j ].index ];
-						var elements2 = morph2.elements;
+				var imageData = texture.image.data !== undefined
+					? texture.image
+					: createImageData( texture.image );
 
-						checkAlphaMorph( morph2, elements2 );
+				var group = geometry.groups[ groupIndex ];
 
-					}
-
-				} else {
+				if ( detectImageTransparency(
+					imageData,
+					geometry.attributes.uv.array,
+					geometry.index.array.slice( group.start, group.start + group.count ) ) ) {
 
-					checkAlphaMorph( morph, elements );
+					map.transparent = true;
 
 				}
 
-			}
+			} );
 
 		}
 
 	};
 
-	var initPhysics = function () {
-
-		var rigidBodies = [];
-		var constraints = [];
+	//
 
-		for ( var i = 0; i < model.metadata.rigidBodyCount; i ++ ) {
+	function AnimationBuilder() {
 
-			var b = model.rigidBodies[ i ];
-			var keys = Object.keys( b );
-
-			var p = {};
+	}
 
-			for ( var j = 0; j < keys.length; j ++ ) {
+	AnimationBuilder.prototype = {
 
-				var key = keys[ j ];
-				p[ key ] = b[ key ];
+		constructor: AnimationBuilder,
 
-			}
+		/**
+		 * @param {Object} vmd - parsed VMD data
+		 * @param {THREE.SkinnedMesh} mesh - tracks will be fitting to mesh
+		 * @return {THREE.AnimationClip}
+		 */
+		build: function ( vmd, mesh ) {
 
-			/*
-			 * RigidBody position parameter in PMX seems global position
-			 * while the one in PMD seems offset from corresponding bone.
-			 * So unify being offset.
-			 */
-			if ( model.metadata.format === 'pmx' ) {
+			// combine skeletal and morph animations
 
-				if ( p.boneIndex !== - 1 ) {
+			var tracks = this.buildSkeletalAnimation( vmd, mesh ).tracks;
+			var tracks2 = this.buildMorphAnimation( vmd, mesh ).tracks;
 
-					var bone = model.bones[ p.boneIndex ];
-					p.position[ 0 ] -= bone.position[ 0 ];
-					p.position[ 1 ] -= bone.position[ 1 ];
-					p.position[ 2 ] -= bone.position[ 2 ];
+			for ( var i = 0, il = tracks2.length; i < il; i ++ ) {
 
-				}
+				tracks.push( tracks2[ i ] );
 
 			}
 
-			rigidBodies.push( p );
-
-		}
-
-		for ( var i = 0; i < model.metadata.constraintCount; i ++ ) {
-
-			var c = model.constraints[ i ];
-			var keys = Object.keys( c );
+			return new THREE.AnimationClip( '', - 1, tracks );
 
-			var p = {};
+		},
 
-			for ( var j = 0; j < keys.length; j ++ ) {
+		/**
+		 * @param {Object} vmd - parsed VMD data
+		 * @param {THREE.SkinnedMesh} mesh - tracks will be fitting to mesh
+		 * @return {THREE.AnimationClip}
+		 */
+		buildSkeletalAnimation: function ( vmd, mesh ) {
 
-				var key = keys[ j ];
-				p[ key ] = c[ key ];
+			function pushInterpolation( array, interpolation, index ) {
 
-			}
+				array.push( interpolation[ index + 0 ] / 127 ); // x1
+				array.push( interpolation[ index + 8 ] / 127 ); // x2
+				array.push( interpolation[ index + 4 ] / 127 ); // y1
+				array.push( interpolation[ index + 12 ] / 127 ); // y2
 
-			var bodyA = rigidBodies[ p.rigidBodyIndex1 ];
-			var bodyB = rigidBodies[ p.rigidBodyIndex2 ];
+			};
 
-			/*
-			 * Refer to http://www20.atpages.jp/katwat/wp/?p=4135
-			 */
-			if ( bodyA.type !== 0 && bodyB.type === 2 ) {
+			var tracks = [];
 
-				if ( bodyA.boneIndex !== - 1 && bodyB.boneIndex !== - 1 &&
-				     model.bones[ bodyB.boneIndex ].parentIndex === bodyA.boneIndex ) {
+			var motions = {};
+			var bones = mesh.skeleton.bones;
+			var boneNameDictionary = {};
 
-					bodyB.type = 1;
+			for ( var i = 0, il = bones.length; i < il; i ++ ) {
 
-				}
+				boneNameDictionary[ bones[ i ].name ] = true;
 
 			}
 
-			constraints.push( p );
+			for ( var i = 0; i < vmd.metadata.motionCount; i ++ ) {
 
-		}
+				var motion = vmd.motions[ i ];
+				var boneName = motion.boneName;
 
-		geometry.rigidBodies = rigidBodies;
-		geometry.constraints = constraints;
+				if ( boneNameDictionary[ boneName ] === undefined ) continue;
 
-	};
+				motions[ boneName ] = motions[ boneName ] || [];
+				motions[ boneName ].push( motion );
 
-	var initGeometry = function () {
+			}
 
-		geometry.setIndex( buffer.indices );
-		geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( buffer.vertices, 3 ) );
-		geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( buffer.normals, 3 ) );
-		geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( buffer.uvs, 2 ) );
-		geometry.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffer.skinIndices, 4 ) );
-		geometry.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffer.skinWeights, 4 ) );
+			for ( var key in motions ) {
 
-		geometry.computeBoundingSphere();
-		geometry.mmdFormat = model.metadata.format;
+				var array = motions[ key ];
 
-	};
+				array.sort( function ( a, b ) {
 
-	initVartices();
-	initFaces();
-	initBones();
-	initIKs();
-	initGrants();
-	initMorphs();
-	initMaterials();
-	initPhysics();
-	initGeometry();
+					return a.frameNum - b.frameNum;
 
-	var mesh = new THREE.SkinnedMesh( geometry, materials );
+				} );
 
-	// console.log( mesh ); // for console debug
+				var times = [];
+				var positions = [];
+				var rotations = [];
+				var pInterpolations = [];
+				var rInterpolations = [];
 
-	return mesh;
+				var basePosition = mesh.skeleton.getBoneByName( key ).position.toArray();
 
-};
+				for ( var i = 0, il = array.length; i < il; i ++ ) {
 
-THREE.MMDLoader.prototype.createAnimation = function ( mesh, vmd, name ) {
+					var time = array[ i ].frameNum / 30;
+					var position = array[ i ].position;
+					var rotation = array[ i ].rotation;
+					var interpolation = array[ i ].interpolation;
 
-	var helper = new THREE.MMDLoader.DataCreationHelper();
+					times.push( time );
 
-	var initMotionAnimations = function () {
+					for ( var j = 0; j < 3; j ++ ) positions.push( basePosition[ j ] + position[ j ] );
+					for ( var j = 0; j < 4; j ++ ) rotations.push( rotation[ j ] );
+					for ( var j = 0; j < 3; j ++ ) pushInterpolation( pInterpolations, interpolation, j );
 
-		if ( vmd.metadata.motionCount === 0 ) {
+					pushInterpolation( rInterpolations, interpolation, 3 );
 
-			return;
+				}
 
-		}
+				var targetName = '.bones[' + key + ']';
 
-		var bones = mesh.geometry.bones;
-		var orderedMotions = helper.createOrderedMotionArrays( bones, vmd.motions, 'boneName' );
+				tracks.push( this._createTrack( targetName + '.position', THREE.VectorKeyframeTrack, times, positions, pInterpolations ) );
+				tracks.push( this._createTrack( targetName + '.quaternion', THREE.QuaternionKeyframeTrack, times, rotations, rInterpolations ) );
 
-		var tracks = [];
+			}
 
-		var pushInterpolation = function ( array, interpolation, index ) {
+			return new THREE.AnimationClip( '', - 1, tracks );
 
-			array.push( interpolation[ index + 0 ] / 127 ); // x1
-			array.push( interpolation[ index + 8 ] / 127 ); // x2
-			array.push( interpolation[ index + 4 ] / 127 ); // y1
-			array.push( interpolation[ index + 12 ] / 127 ); // y2
+		},
 
-		};
+		/**
+		 * @param {Object} vmd - parsed VMD data
+		 * @param {THREE.SkinnedMesh} mesh - tracks will be fitting to mesh
+		 * @return {THREE.AnimationClip}
+		 */
+		buildMorphAnimation: function ( vmd, mesh ) {
 
-		var createTrack = function ( node, type, times, values, interpolations ) {
+			var tracks = [];
 
-			var track = new THREE[ type ]( node, times, values );
+			var morphs = {};
+			var morphTargetDictionary = mesh.morphTargetDictionary;
 
-			track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) {
+			for ( var i = 0; i < vmd.metadata.morphCount; i ++ ) {
 
-				return new THREE.MMDLoader.CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) );
+				var morph = vmd.morphs[ i ];
+				var morphName = morph.morphName;
 
-			};
+				if ( morphTargetDictionary[ morphName ] === undefined ) continue;
 
-			return track;
+				morphs[ morphName ] = morphs[ morphName ] || [];
+				morphs[ morphName ].push( morph );
 
-		};
+			}
 
-		for ( var i = 0; i < orderedMotions.length; i ++ ) {
+			for ( var key in morphs ) {
 
-			var times = [];
-			var positions = [];
-			var rotations = [];
-			var pInterpolations = [];
-			var rInterpolations = [];
+				var array = morphs[ key ];
 
-			var bone = bones[ i ];
-			var array = orderedMotions[ i ];
+				array.sort( function ( a, b ) {
 
-			for ( var j = 0; j < array.length; j ++ ) {
+					return a.frameNum - b.frameNum;
 
-				var time = array[ j ].frameNum / 30;
-				var pos = array[ j ].position;
-				var rot = array[ j ].rotation;
-				var interpolation = array[ j ].interpolation;
+				} );
 
-				times.push( time );
+				var times = [];
+				var values = [];
 
-				for ( var k = 0; k < 3; k ++ ) {
+				for ( var i = 0, il = array.length; i < il; i ++ ) {
 
-					positions.push( bone.pos[ k ] + pos[ k ] );
+					times.push( array[ i ].frameNum / 30 );
+					values.push( array[ i ].weight );
 
 				}
 
-				for ( var k = 0; k < 4; k ++ ) {
+				tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetDictionary[ key ] + ']', times, values ) );
 
-					rotations.push( rot[ k ] );
+			}
 
-				}
+			return new THREE.AnimationClip( '', - 1, tracks );
 
-				for ( var k = 0; k < 3; k ++ ) {
+		},
 
-					pushInterpolation( pInterpolations, interpolation, k );
+		/**
+		 * @param {Object} vmd - parsed VMD data
+		 * @return {THREE.AnimationClip}
+		 */
+		buildCameraAnimation: function ( vmd ) {
 
-				}
+			function pushVector3( array, vec ) {
 
-				pushInterpolation( rInterpolations, interpolation, 3 );
+				array.push( vec.x );
+				array.push( vec.y );
+				array.push( vec.z );
 
 			}
 
-			if ( times.length === 0 ) continue;
-
-			var boneName = '.bones[' + bone.name + ']';
+			function pushQuaternion( array, q ) {
 
-			tracks.push( createTrack( boneName + '.position', 'VectorKeyframeTrack', times, positions, pInterpolations ) );
-			tracks.push( createTrack( boneName + '.quaternion', 'QuaternionKeyframeTrack', times, rotations, rInterpolations ) );
+				array.push( q.x );
+				array.push( q.y );
+				array.push( q.z );
+				array.push( q.w );
 
-		}
-
-		var clip = new THREE.AnimationClip( name === undefined ? THREE.Math.generateUUID() : name, - 1, tracks );
-
-		if ( mesh.geometry.animations === undefined ) mesh.geometry.animations = [];
-		mesh.geometry.animations.push( clip );
+			}
 
-	};
+			function pushInterpolation( array, interpolation, index ) {
 
-	var initMorphAnimations = function () {
+				array.push( interpolation[ index * 4 + 0 ] / 127 ); // x1
+				array.push( interpolation[ index * 4 + 1 ] / 127 ); // x2
+				array.push( interpolation[ index * 4 + 2 ] / 127 ); // y1
+				array.push( interpolation[ index * 4 + 3 ] / 127 ); // y2
 
-		if ( vmd.metadata.morphCount === 0 ) {
+			};
 
-			return;
+			var tracks = [];
 
-		}
+			var cameras = vmd.cameras === undefined ? [] : vmd.cameras.slice();
 
-		var orderedMorphs = helper.createOrderedMotionArrays( mesh.geometry.morphTargets, vmd.morphs, 'morphName' );
+			cameras.sort( function ( a, b ) {
 
-		var tracks = [];
+				return a.frameNum - b.frameNum;
 
-		for ( var i = 0; i < orderedMorphs.length; i ++ ) {
+			} );
 
 			var times = [];
-			var values = [];
-			var array = orderedMorphs[ i ];
-
-			for ( var j = 0; j < array.length; j ++ ) {
-
-				times.push( array[ j ].frameNum / 30 );
-				values.push( array[ j ].weight );
-
-			}
-
-			if ( times.length === 0 ) continue;
-
-			tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluences[' + i + ']', times, values ) );
-
-		}
-
-		var clip = new THREE.AnimationClip( name === undefined ? THREE.Math.generateUUID() : name + 'Morph', - 1, tracks );
-
-		if ( mesh.geometry.animations === undefined ) mesh.geometry.animations = [];
-		mesh.geometry.animations.push( clip );
-
-	};
-
-	initMotionAnimations();
-	initMorphAnimations();
-
-};
+			var centers = [];
+			var quaternions = [];
+			var positions = [];
+			var fovs = [];
 
-THREE.MMDLoader.DataCreationHelper = function () {
+			var cInterpolations = [];
+			var qInterpolations = [];
+			var pInterpolations = [];
+			var fInterpolations = [];
 
-};
+			var quaternion = new THREE.Quaternion();
+			var euler = new THREE.Euler();
+			var position = new THREE.Vector3();
+			var center = new THREE.Vector3();
 
-THREE.MMDLoader.DataCreationHelper.prototype = {
+			for ( var i = 0, il = cameras.length; i < il; i ++ ) {
 
-	constructor: THREE.MMDLoader.DataCreationHelper,
+				var motion = cameras[ i ];
 
-	/*
-	 * Note: Sometimes to use Japanese Unicode characters runs into problems in Three.js.
-	 *       In such a case, use this method to convert it to Unicode hex charcode strings,
-	 *       like 'あいう' -> '0x30420x30440x3046'
-	 */
+				var time = motion.frameNum / 30;
+				var pos = motion.position;
+				var rot = motion.rotation;
+				var distance = motion.distance;
+				var fov = motion.fov;
+				var interpolation = motion.interpolation;
 
-	toCharcodeStrings: function ( s ) {
+				times.push( time );
 
-		var str = '';
+				position.set( 0, 0, - distance );
+				center.set( pos[ 0 ], pos[ 1 ], pos[ 2 ] );
 
-		for ( var i = 0; i < s.length; i ++ ) {
+				euler.set( - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] );
+				quaternion.setFromEuler( euler );
 
-			str += '0x' + ( '0000' + s[ i ].charCodeAt().toString( 16 ) ).substr( - 4 );
+				position.add( center );
+				position.applyQuaternion( quaternion );
 
-		}
+				pushVector3( centers, center );
+				pushQuaternion( quaternions, quaternion );
+				pushVector3( positions, position );
 
-		return str;
+				fovs.push( fov );
 
-	},
+				for ( var j = 0; j < 3; j ++ ) {
 
-	createDictionary: function ( array ) {
+					pushInterpolation( cInterpolations, interpolation, j );
 
-		var dict = {};
+				}
 
-		for ( var i = 0; i < array.length; i ++ ) {
+				pushInterpolation( qInterpolations, interpolation, 3 );
 
-			dict[ array[ i ].name ] = i;
+				// use the same parameter for x, y, z axis.
+				for ( var j = 0; j < 3; j ++ ) {
 
-		}
+					pushInterpolation( pInterpolations, interpolation, 4 );
 
-		return dict;
+				}
 
-	},
+				pushInterpolation( fInterpolations, interpolation, 5 );
 
-	initializeMotionArrays: function ( array ) {
+			}
 
-		var result = [];
+			var tracks = [];
 
-		for ( var i = 0; i < array.length; i ++ ) {
+			// I expect an object whose name 'target' exists under THREE.Camera
+			tracks.push( this._createTrack( 'target.position', THREE.VectorKeyframeTrack, times, centers, cInterpolations ) );
 
-			result[ i ] = [];
+			tracks.push( this._createTrack( '.quaternion', THREE.QuaternionKeyframeTrack, times, quaternions, qInterpolations ) );
+			tracks.push( this._createTrack( '.position', THREE.VectorKeyframeTrack, times, positions, pInterpolations ) );
+			tracks.push( this._createTrack( '.fov', THREE.NumberKeyframeTrack, times, fovs, fInterpolations ) );
 
-		}
+			return new THREE.AnimationClip( '', - 1, tracks );
 
-		return result;
+		},
 
-	},
+		// private method
 
-	sortMotionArray: function ( array ) {
+		_createTrack: function ( node, typedKeyframeTrack, times, values, interpolations ) {
 
-		array.sort( function ( a, b ) {
+			/*
+			 * optimizes here not to let KeyframeTrackPrototype optimize
+			 * because KeyframeTrackPrototype optimizes times and values but
+			 * doesn't optimize interpolations.
+			 */
+			if ( times.length > 2 ) {
 
-			return a.frameNum - b.frameNum;
+				times = times.slice();
+				values = values.slice();
+				interpolations = interpolations.slice();
 
-		} );
+				var stride = values.length / times.length;
+				var interpolateStride = interpolations.length / times.length;
 
-	},
+				var index = 1;
 
-	sortMotionArrays: function ( arrays ) {
+				for ( var aheadIndex = 2, endIndex = times.length; aheadIndex < endIndex; aheadIndex ++ ) {
 
-		for ( var i = 0; i < arrays.length; i ++ ) {
+					for ( var i = 0; i < stride; i ++ ) {
 
-			this.sortMotionArray( arrays[ i ] );
+						if ( values[ index * stride + i ] !== values[ ( index - 1 ) * stride + i ] ||
+							values[ index * stride + i ] !== values[ aheadIndex * stride + i ] ) {
 
-		}
+							index ++;
+							break;
 
-	},
+						}
 
-	createMotionArray: function ( array ) {
+					}
 
-		var result = [];
+					if ( aheadIndex > index ) {
 
-		for ( var i = 0; i < array.length; i ++ ) {
+						times[ index ] = times[ aheadIndex ];
 
-			result.push( array[ i ] );
+						for ( var i = 0; i < stride; i ++ ) {
 
-		}
+							values[ index * stride + i ] = values[ aheadIndex * stride + i ];
 
-		return result;
+						}
 
-	},
+						for ( var i = 0; i < interpolateStride; i ++ ) {
 
-	createMotionArrays: function ( array, result, dict, key ) {
+							interpolations[ index * interpolateStride + i ] = interpolations[ aheadIndex * interpolateStride + i ];
 
-		for ( var i = 0; i < array.length; i ++ ) {
+						}
 
-			var a = array[ i ];
-			var num = dict[ a[ key ] ];
+					}
 
-			if ( num === undefined ) {
+				}
 
-				continue;
+				times.length = index + 1;
+				values.length = ( index + 1 ) * stride;
+				interpolations.length = ( index + 1 ) * interpolateStride;
 
 			}
 
-			result[ num ].push( a );
-
-		}
-
-	},
-
-	createOrderedMotionArray: function ( array ) {
-
-		var result = this.createMotionArray( array );
-		this.sortMotionArray( result );
-		return result;
-
-	},
-
-	createOrderedMotionArrays: function ( targetArray, motionArray, key ) {
-
-		var dict = this.createDictionary( targetArray );
-		var result = this.initializeMotionArrays( targetArray );
-		this.createMotionArrays( motionArray, result, dict, key );
-		this.sortMotionArrays( result );
-
-		return result;
-
-	}
-
-};
-
-THREE.MMDLoader.CubicBezierInterpolation = function ( parameterPositions, sampleValues, sampleSize, resultBuffer, params ) {
-
-	THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
-
-	this.params = params;
-
-};
-
-THREE.MMDLoader.CubicBezierInterpolation.prototype = Object.create( THREE.LinearInterpolant.prototype );
-THREE.MMDLoader.CubicBezierInterpolation.prototype.constructor = THREE.MMDLoader.CubicBezierInterpolation;
-
-THREE.MMDLoader.CubicBezierInterpolation.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
-
-	var result = this.resultBuffer;
-	var values = this.sampleValues;
-	var stride = this.valueSize;
-
-	var offset1 = i1 * stride;
-	var offset0 = offset1 - stride;
-
-	// No interpolation if next key frame is in one frame in 30fps. This is from MMD animation spec.
-	var weight1 = ( ( t1 - t0 ) < 1 / 30 * 1.5 ) ? 0.0 : ( t - t0 ) / ( t1 - t0 );
+			var track = new typedKeyframeTrack( node, times, values );
 
-	if ( stride === 4 ) { // Quaternion
-
-		var x1 = this.params[ i1 * 4 + 0 ];
-		var x2 = this.params[ i1 * 4 + 1 ];
-		var y1 = this.params[ i1 * 4 + 2 ];
-		var y2 = this.params[ i1 * 4 + 3 ];
-
-		var ratio = this._calculate( x1, x2, y1, y2, weight1 );
-
-		THREE.Quaternion.slerpFlat( result, 0, values, offset0, values, offset1, ratio );
-
-	} else if ( stride === 3 ) { // Vector3
-
-		for ( var i = 0; i !== stride; ++ i ) {
+			track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) {
 
-			var x1 = this.params[ i1 * 12 + i * 4 + 0 ];
-			var x2 = this.params[ i1 * 12 + i * 4 + 1 ];
-			var y1 = this.params[ i1 * 12 + i * 4 + 2 ];
-			var y2 = this.params[ i1 * 12 + i * 4 + 3 ];
+				return new CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) );
 
-			var ratio = this._calculate( x1, x2, y1, y2, weight1 );
+			};
 
-			result[ i ] = values[ offset0 + i ] * ( 1 - ratio ) + values[ offset1 + i ] * ratio;
+			return track;
 
 		}
 
-	} else { // Number
-
-		var x1 = this.params[ i1 * 4 + 0 ];
-		var x2 = this.params[ i1 * 4 + 1 ];
-		var y1 = this.params[ i1 * 4 + 2 ];
-		var y2 = this.params[ i1 * 4 + 3 ];
-
-		var ratio = this._calculate( x1, x2, y1, y2, weight1 );
+	};
 
-		result[ 0 ] = values[ offset0 ] * ( 1 - ratio ) + values[ offset1 ] * ratio;
+	// interpolation
 
-	}
+	function CubicBezierInterpolation( parameterPositions, sampleValues, sampleSize, resultBuffer, params ) {
 
-	return result;
+		THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-};
+		this.interpolationParams = params;
 
-THREE.MMDLoader.CubicBezierInterpolation.prototype._calculate = function ( x1, x2, y1, y2, x ) {
+	}
 
-	/*
-	 * Cubic Bezier curves
-	 *   https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
-	 *
-	 * B(t) = ( 1 - t ) ^ 3 * P0
-	 *      + 3 * ( 1 - t ) ^ 2 * t * P1
-	 *      + 3 * ( 1 - t ) * t^2 * P2
-	 *      + t ^ 3 * P3
-	 *      ( 0 <= t <= 1 )
-	 *
-	 * MMD uses Cubic Bezier curves for bone and camera animation interpolation.
-	 *   http://d.hatena.ne.jp/edvakf/20111016/1318716097
-	 *
-	 *    x = ( 1 - t ) ^ 3 * x0
-	 *      + 3 * ( 1 - t ) ^ 2 * t * x1
-	 *      + 3 * ( 1 - t ) * t^2 * x2
-	 *      + t ^ 3 * x3
-	 *    y = ( 1 - t ) ^ 3 * y0
-	 *      + 3 * ( 1 - t ) ^ 2 * t * y1
-	 *      + 3 * ( 1 - t ) * t^2 * y2
-	 *      + t ^ 3 * y3
-	 *      ( x0 = 0, y0 = 0 )
-	 *      ( x3 = 1, y3 = 1 )
-	 *      ( 0 <= t, x1, x2, y1, y2 <= 1 )
-	 *
-	 * Here solves this equation with Bisection method,
-	 *   https://en.wikipedia.org/wiki/Bisection_method
-	 * gets t, and then calculate y.
-	 *
-	 * f(t) = 3 * ( 1 - t ) ^ 2 * t * x1
-	 *      + 3 * ( 1 - t ) * t^2 * x2
-	 *      + t ^ 3 - x = 0
-	 *
-	 * (Another option: Newton's method
-	 *    https://en.wikipedia.org/wiki/Newton%27s_method)
-	 */
+	CubicBezierInterpolation.prototype = Object.assign( Object.create( THREE.Interpolant.prototype ), {
 
-	var c = 0.5;
-	var t = c;
-	var s = 1.0 - t;
-	var loop = 15;
-	var eps = 1e-5;
-	var math = Math;
+		constructor: CubicBezierInterpolation,
 
-	var sst3, stt3, ttt;
+		interpolate_: function ( i1, t0, t, t1 ) {
 
-	for ( var i = 0; i < loop; i ++ ) {
+			var result = this.resultBuffer;
+			var values = this.sampleValues;
+			var stride = this.valueSize;
+			var params = this.interpolationParams;
 
-		sst3 = 3.0 * s * s * t;
-		stt3 = 3.0 * s * t * t;
-		ttt = t * t * t;
+			var offset1 = i1 * stride;
+			var offset0 = offset1 - stride;
 
-		var ft = ( sst3 * x1 ) + ( stt3 * x2 ) + ( ttt ) - x;
+			// No interpolation if next key frame is in one frame in 30fps.
+			// This is from MMD animation spec.
+			// '1.5' is for precision loss. times are Float32 in Three.js Animation system.
+			var weight1 = ( ( t1 - t0 ) < 1 / 30 * 1.5 ) ? 0.0 : ( t - t0 ) / ( t1 - t0 );
 
-		if ( math.abs( ft ) < eps ) break;
+			if ( stride === 4 ) { // Quaternion
 
-		c /= 2.0;
+				var x1 = params[ i1 * 4 + 0 ];
+				var x2 = params[ i1 * 4 + 1 ];
+				var y1 = params[ i1 * 4 + 2 ];
+				var y2 = params[ i1 * 4 + 3 ];
 
-		t += ( ft < 0 ) ? c : - c;
-		s = 1.0 - t;
+				var ratio = this._calculate( x1, x2, y1, y2, weight1 );
 
-	}
+				THREE.Quaternion.slerpFlat( result, 0, values, offset0, values, offset1, ratio );
 
-	return ( sst3 * y1 ) + ( stt3 * y2 ) + ttt;
+			} else if ( stride === 3 ) { // Vector3
 
-};
+				for ( var i = 0; i !== stride; ++ i ) {
 
-THREE.MMDAudioManager = function ( audio, listener, p ) {
+					var x1 = params[ i1 * 12 + i * 4 + 0 ];
+					var x2 = params[ i1 * 12 + i * 4 + 1 ];
+					var y1 = params[ i1 * 12 + i * 4 + 2 ];
+					var y2 = params[ i1 * 12 + i * 4 + 3 ];
 
-	var params = ( p === null || p === undefined ) ? {} : p;
+					var ratio = this._calculate( x1, x2, y1, y2, weight1 );
 
-	this.audio = audio;
-	this.listener = listener;
+					result[ i ] = values[ offset0 + i ] * ( 1 - ratio ) + values[ offset1 + i ] * ratio;
 
-	this.elapsedTime = 0.0;
-	this.currentTime = 0.0;
-	this.delayTime = params.delayTime !== undefined ? params.delayTime : 0.0;
+				}
 
-	this.audioDuration = this.audio.buffer.duration;
-	this.duration = this.audioDuration + this.delayTime;
+			} else { // Number
 
-};
+				var x1 = params[ i1 * 4 + 0 ];
+				var x2 = params[ i1 * 4 + 1 ];
+				var y1 = params[ i1 * 4 + 2 ];
+				var y2 = params[ i1 * 4 + 3 ];
 
-THREE.MMDAudioManager.prototype = {
+				var ratio = this._calculate( x1, x2, y1, y2, weight1 );
 
-	constructor: THREE.MMDAudioManager,
+				result[ 0 ] = values[ offset0 ] * ( 1 - ratio ) + values[ offset1 ] * ratio;
 
-	control: function ( delta ) {
+			}
 
-		this.elapsed += delta;
-		this.currentTime += delta;
+			return result;
 
-		if ( this.checkIfStopAudio() ) {
+		},
 
-			this.audio.stop();
+		_calculate: function ( x1, x2, y1, y2, x ) {
 
-		}
+			/*
+			 * Cubic Bezier curves
+			 *   https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
+			 *
+			 * B(t) = ( 1 - t ) ^ 3 * P0
+			 *      + 3 * ( 1 - t ) ^ 2 * t * P1
+			 *      + 3 * ( 1 - t ) * t^2 * P2
+			 *      + t ^ 3 * P3
+			 *      ( 0 <= t <= 1 )
+			 *
+			 * MMD uses Cubic Bezier curves for bone and camera animation interpolation.
+			 *   http://d.hatena.ne.jp/edvakf/20111016/1318716097
+			 *
+			 *    x = ( 1 - t ) ^ 3 * x0
+			 *      + 3 * ( 1 - t ) ^ 2 * t * x1
+			 *      + 3 * ( 1 - t ) * t^2 * x2
+			 *      + t ^ 3 * x3
+			 *    y = ( 1 - t ) ^ 3 * y0
+			 *      + 3 * ( 1 - t ) ^ 2 * t * y1
+			 *      + 3 * ( 1 - t ) * t^2 * y2
+			 *      + t ^ 3 * y3
+			 *      ( x0 = 0, y0 = 0 )
+			 *      ( x3 = 1, y3 = 1 )
+			 *      ( 0 <= t, x1, x2, y1, y2 <= 1 )
+			 *
+			 * Here solves this equation with Bisection method,
+			 *   https://en.wikipedia.org/wiki/Bisection_method
+			 * gets t, and then calculate y.
+			 *
+			 * f(t) = 3 * ( 1 - t ) ^ 2 * t * x1
+			 *      + 3 * ( 1 - t ) * t^2 * x2
+			 *      + t ^ 3 - x = 0
+			 *
+			 * (Another option: Newton's method
+			 *    https://en.wikipedia.org/wiki/Newton%27s_method)
+			 */
 
-		if ( this.checkIfStartAudio() ) {
+			var c = 0.5;
+			var t = c;
+			var s = 1.0 - t;
+			var loop = 15;
+			var eps = 1e-5;
+			var math = Math;
 
-			this.audio.play();
+			var sst3, stt3, ttt;
 
-		}
+			for ( var i = 0; i < loop; i ++ ) {
 
-	},
+				sst3 = 3.0 * s * s * t;
+				stt3 = 3.0 * s * t * t;
+				ttt = t * t * t;
 
-	checkIfStartAudio: function () {
+				var ft = ( sst3 * x1 ) + ( stt3 * x2 ) + ( ttt ) - x;
 
-		if ( this.audio.isPlaying ) {
+				if ( math.abs( ft ) < eps ) break;
 
-			return false;
+				c /= 2.0;
 
-		}
+				t += ( ft < 0 ) ? c : - c;
+				s = 1.0 - t;
 
-		while ( this.currentTime >= this.duration ) {
+			}
 
-			this.currentTime -= this.duration;
+			return ( sst3 * y1 ) + ( stt3 * y2 ) + ttt;
 
 		}
 
-		if ( this.currentTime < this.delayTime ) {
+	} );
 
-			return false;
-
-		}
-
-		this.audio.startTime = this.currentTime - this.delayTime;
-
-		return true;
-
-	},
-
-	checkIfStopAudio: function () {
-
-		if ( ! this.audio.isPlaying ) {
-
-			return false;
-
-		}
-
-		if ( this.currentTime >= this.duration ) {
-
-			return true;
-
-		}
-
-		return false;
-
-	}
-
-};
-
-THREE.MMDGrantSolver = function ( mesh ) {
-
-	this.mesh = mesh;
-
-};
-
-THREE.MMDGrantSolver.prototype = {
-
-	constructor: THREE.MMDGrantSolver,
-
-	update: function () {
-
-		var q = new THREE.Quaternion();
-
-		return function () {
-
-			for ( var i = 0; i < this.mesh.geometry.grants.length; i ++ ) {
-
-				var g = this.mesh.geometry.grants[ i ];
-				var b = this.mesh.skeleton.bones[ g.index ];
-				var pb = this.mesh.skeleton.bones[ g.parentIndex ];
-
-				if ( g.isLocal ) {
-
-					// TODO: implement
-					if ( g.affectPosition ) {
-
-					}
-
-					// TODO: implement
-					if ( g.affectRotation ) {
-
-					}
-
-				} else {
-
-					// TODO: implement
-					if ( g.affectPosition ) {
-
-					}
-
-					if ( g.affectRotation ) {
-
-						q.set( 0, 0, 0, 1 );
-						q.slerp( pb.quaternion, g.ratio );
-						b.quaternion.multiply( q );
-
-					}
-
-				}
-
-			}
-
-		};
-
-	}()
-
-};
-
-THREE.MMDHelper = function () {
-
-	this.meshes = [];
-
-	this.doAnimation = true;
-	this.doIk = true;
-	this.doGrant = true;
-	this.doPhysics = true;
-	this.doCameraAnimation = true;
-
-	this.sharedPhysics = false;
-	this.masterPhysics = null;
-
-	this.audioManager = null;
-	this.camera = null;
-
-};
-
-THREE.MMDHelper.prototype = {
-
-	constructor: THREE.MMDHelper,
-
-	add: function ( mesh ) {
-
-		if ( ! ( mesh instanceof THREE.SkinnedMesh ) ) {
-
-			throw new Error( 'THREE.MMDHelper.add() accepts only THREE.SkinnedMesh instance.' );
-
-		}
-
-		if ( mesh.mixer === undefined ) mesh.mixer = null;
-		if ( mesh.ikSolver === undefined ) mesh.ikSolver = null;
-		if ( mesh.grantSolver === undefined ) mesh.grantSolver = null;
-		if ( mesh.physics === undefined ) mesh.physics = null;
-		if ( mesh.looped === undefined ) mesh.looped = false;
-
-		this.meshes.push( mesh );
-
-		// workaround until I make IK and Physics Animation plugin
-		this.initBackupBones( mesh );
-
-	},
-
-	setAudio: function ( audio, listener, params ) {
-
-		this.audioManager = new THREE.MMDAudioManager( audio, listener, params );
-
-	},
-
-	setCamera: function ( camera ) {
-
-		camera.mixer = null;
-		this.camera = camera;
-
-	},
-
-	setPhysicses: function ( params ) {
-
-		for ( var i = 0; i < this.meshes.length; i ++ ) {
-
-			this.setPhysics( this.meshes[ i ], params );
-
-		}
-
-	},
-
-	setPhysics: function ( mesh, params ) {
-
-		params = ( params === undefined ) ? {} : Object.assign( {}, params );
-
-		if ( params.world === undefined && this.sharedPhysics ) {
-
-			var masterPhysics = this.getMasterPhysics();
-
-			if ( masterPhysics !== null ) params.world = masterPhysics.world;
-
-		}
-
-		var warmup = params.warmup !== undefined ? params.warmup : 60;
-
-		var physics = new THREE.MMDPhysics( mesh, params );
-
-		if ( mesh.mixer !== null && mesh.mixer !== undefined && params.preventAnimationWarmup !== true ) {
-
-			this.animateOneMesh( 0, mesh );
-			physics.reset();
-
-		}
-
-		physics.warmup( warmup );
-
-		this.updateIKParametersDependingOnPhysicsEnabled( mesh, true );
-
-		mesh.physics = physics;
-
-	},
-
-	getMasterPhysics: function () {
-
-		if ( this.masterPhysics !== null ) return this.masterPhysics;
-
-		for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
-
-			var physics = this.meshes[ i ].physics;
-
-			if ( physics !== undefined && physics !== null ) {
-
-				this.masterPhysics = physics;
-				return this.masterPhysics;
-
-			}
-
-		}
-
-		return null;
-
-	},
-
-	enablePhysics: function ( enabled ) {
-
-		if ( enabled === true ) {
-
-			this.doPhysics = true;
-
-		} else {
-
-			this.doPhysics = false;
-
-		}
-
-		for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
-
-			this.updateIKParametersDependingOnPhysicsEnabled( this.meshes[ i ], enabled );
-
-		}
-
-	},
-
-	updateIKParametersDependingOnPhysicsEnabled: function ( mesh, physicsEnabled ) {
-
-		var iks = mesh.geometry.iks;
-		var bones = mesh.geometry.bones;
-
-		for ( var j = 0, jl = iks.length; j < jl; j ++ ) {
-
-			var ik = iks[ j ];
-			var links = ik.links;
-
-			for ( var k = 0, kl = links.length; k < kl; k ++ ) {
-
-				var link = links[ k ];
-
-				if ( physicsEnabled === true ) {
-
-					// disable IK of the bone the corresponding rigidBody type of which is 1 or 2
-					// because its rotation will be overriden by physics
-					link.enabled = bones[ link.index ].rigidBodyType > 0 ? false : true;
-
-				} else {
-
-					link.enabled = true;
-
-				}
-
-			}
-
-		}
-
-	},
-
-	setAnimations: function () {
-
-		for ( var i = 0; i < this.meshes.length; i ++ ) {
-
-			this.setAnimation( this.meshes[ i ] );
-
-		}
-
-	},
-
-	setAnimation: function ( mesh ) {
-
-		if ( mesh.geometry.animations !== undefined ) {
-
-			mesh.mixer = new THREE.AnimationMixer( mesh );
-
-			// TODO: find a workaround not to access (seems like) private properties
-			//       the name of them begins with "_".
-			mesh.mixer.addEventListener( 'loop', function ( e ) {
-
-				if ( e.action._clip.tracks.length > 0 &&
-				     e.action._clip.tracks[ 0 ].name.indexOf( '.bones' ) !== 0 ) return;
-
-				var mesh = e.target._root;
-				mesh.looped = true;
-
-			} );
-
-			var foundAnimation = false;
-			var foundMorphAnimation = false;
-
-			for ( var i = 0; i < mesh.geometry.animations.length; i ++ ) {
-
-				var clip = mesh.geometry.animations[ i ];
-
-				var action = mesh.mixer.clipAction( clip );
-
-				if ( clip.tracks.length > 0 && clip.tracks[ 0 ].name.indexOf( '.morphTargetInfluences' ) === 0 ) {
-
-					if ( ! foundMorphAnimation ) {
-
-						action.play();
-						foundMorphAnimation = true;
-
-					}
-
-				} else {
-
-					if ( ! foundAnimation ) {
-
-						action.play();
-						foundAnimation = true;
-
-					}
-
-				}
-
-			}
-
-			if ( foundAnimation ) {
-
-				mesh.ikSolver = new THREE.CCDIKSolver( mesh );
-
-				if ( mesh.geometry.grants !== undefined ) {
-
-					mesh.grantSolver = new THREE.MMDGrantSolver( mesh );
-
-				}
-
-			}
-
-		}
-
-	},
-
-	setCameraAnimation: function ( camera ) {
-
-		if ( camera.animations !== undefined ) {
-
-			camera.mixer = new THREE.AnimationMixer( camera );
-			camera.mixer.clipAction( camera.animations[ 0 ] ).play();
-
-		}
-
-	},
-
-	/*
-	 * detect the longest duration among model, camera, and audio animations and then
-	 * set it to them to sync.
-	 * TODO: touching private properties ( ._actions and ._clip ) so consider better way
-	 *       to access them for safe and modularity.
-	 */
-	unifyAnimationDuration: function ( params ) {
-
-		params = params === undefined ? {} : params;
-
-		var max = 0.0;
-
-		var camera = this.camera;
-		var audioManager = this.audioManager;
-
-		// check the longest duration
-		for ( var i = 0; i < this.meshes.length; i ++ ) {
-
-			var mesh = this.meshes[ i ];
-			var mixer = mesh.mixer;
-
-			if ( mixer === null ) {
-
-				continue;
-
-			}
-
-			for ( var j = 0; j < mixer._actions.length; j ++ ) {
-
-				var action = mixer._actions[ j ];
-				max = Math.max( max, action._clip.duration );
-
-			}
-
-		}
-
-		if ( camera !== null && camera.mixer !== null ) {
-
-			var mixer = camera.mixer;
-
-			for ( var i = 0; i < mixer._actions.length; i ++ ) {
-
-				var action = mixer._actions[ i ];
-				max = Math.max( max, action._clip.duration );
-
-			}
-
-		}
-
-		if ( audioManager !== null ) {
-
-			max = Math.max( max, audioManager.duration );
-
-		}
-
-		if ( params.afterglow !== undefined ) {
-
-			max += params.afterglow;
-
-		}
-
-		// set the duration
-		for ( var i = 0; i < this.meshes.length; i ++ ) {
-
-			var mesh = this.meshes[ i ];
-			var mixer = mesh.mixer;
-
-			if ( mixer === null ) {
-
-				continue;
-
-			}
-
-			for ( var j = 0; j < mixer._actions.length; j ++ ) {
-
-				var action = mixer._actions[ j ];
-				action._clip.duration = max;
-
-			}
-
-		}
-
-		if ( camera !== null && camera.mixer !== null ) {
-
-			var mixer = camera.mixer;
-
-			for ( var i = 0; i < mixer._actions.length; i ++ ) {
-
-				var action = mixer._actions[ i ];
-				action._clip.duration = max;
-
-			}
-
-		}
-
-		if ( audioManager !== null ) {
-
-			audioManager.duration = max;
-
-		}
-
-	},
-
-	controlAudio: function ( delta ) {
-
-		if ( this.audioManager === null ) {
-
-			return;
-
-		}
-
-		this.audioManager.control( delta );
-
-	},
-
-	animate: function ( delta ) {
-
-		this.controlAudio( delta );
-
-		for ( var i = 0; i < this.meshes.length; i ++ ) {
-
-			this.animateOneMesh( delta, this.meshes[ i ] );
-
-		}
-
-		if ( this.sharedPhysics ) this.updateSharedPhysics( delta );
-
-		this.animateCamera( delta );
-
-	},
-
-	animateOneMesh: function ( delta, mesh ) {
-
-		var mixer = mesh.mixer;
-		var ikSolver = mesh.ikSolver;
-		var grantSolver = mesh.grantSolver;
-		var physics = mesh.physics;
-
-		if ( mixer !== null && this.doAnimation === true ) {
-
-			// restore/backupBones are workaround
-			// until I make IK, Grant, and Physics Animation plugin
-			this.restoreBones( mesh );
-
-			mixer.update( delta );
-
-			this.backupBones( mesh );
-
-		}
-
-		if ( ikSolver !== null && this.doIk === true ) {
-
-			ikSolver.update();
-
-		}
-
-		if ( grantSolver !== null && this.doGrant === true ) {
-
-			grantSolver.update();
-
-		}
-
-		if ( mesh.looped === true ) {
-
-			if ( physics !== null ) physics.reset();
-
-			mesh.looped = false;
-
-		}
-
-		if ( physics !== null && this.doPhysics && ! this.sharedPhysics ) {
-
-			physics.update( delta );
-
-		}
-
-	},
-
-	updateSharedPhysics: function ( delta ) {
-
-		if ( this.meshes.length === 0 || ! this.doPhysics || ! this.sharedPhysics ) return;
-
-		var physics = this.getMasterPhysics();
-
-		if ( physics === null ) return;
-
-		for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
-
-			var p = this.meshes[ i ].physics;
-
-			if ( p !== null && p !== undefined ) {
-
-				p.updateRigidBodies();
-
-			}
-
-		}
-
-		physics.stepSimulation( delta );
-
-		for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
-
-			var p = this.meshes[ i ].physics;
-
-			if ( p !== null && p !== undefined ) {
-
-				p.updateBones();
-
-			}
-
-		}
-
-	},
-
-	animateCamera: function ( delta ) {
-
-		if ( this.camera === null ) {
-
-			return;
-
-		}
-
-		var mixer = this.camera.mixer;
-
-		if ( mixer !== null && this.camera.center !== undefined && this.doCameraAnimation === true ) {
-
-			mixer.update( delta );
-
-			// TODO: Let PerspectiveCamera automatically update?
-			this.camera.updateProjectionMatrix();
-
-			this.camera.up.set( 0, 1, 0 );
-			this.camera.up.applyQuaternion( this.camera.quaternion );
-			this.camera.lookAt( this.camera.center );
-
-		}
-
-	},
-
-	poseAsVpd: function ( mesh, vpd, params ) {
-
-		if ( params === undefined ) params = {};
-
-		if ( params.preventResetPose !== true ) mesh.pose();
-
-		var bones = mesh.skeleton.bones;
-		var bones2 = vpd.bones;
-
-		var table = {};
-
-		for ( var i = 0; i < bones.length; i ++ ) {
-
-			table[ bones[ i ].name ] = i;
-
-		}
-
-		var thV = new THREE.Vector3();
-		var thQ = new THREE.Quaternion();
-
-		for ( var i = 0; i < bones2.length; i ++ ) {
-
-			var b = bones2[ i ];
-			var index = table[ b.name ];
-
-			if ( index === undefined ) continue;
-
-			var b2 = bones[ index ];
-			var t = b.translation;
-			var q = b.quaternion;
-
-			thV.set( t[ 0 ], t[ 1 ], t[ 2 ] );
-			thQ.set( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
-
-			b2.position.add( thV );
-			b2.quaternion.multiply( thQ );
-
-		}
-
-		mesh.updateMatrixWorld( true );
-
-		if ( params.preventIk !== true ) {
-
-			var solver = new THREE.CCDIKSolver( mesh );
-			solver.update( params.saveOriginalBonesBeforeIK );
-
-		}
-
-		if ( params.preventGrant !== true && mesh.geometry.grants !== undefined ) {
-
-			var solver = new THREE.MMDGrantSolver( mesh );
-			solver.update();
-
-		}
-
-	},
-
-	/*
-	 * Note: These following three functions are workaround for r74dev.
-	 *       THREE.PropertyMixer.apply() seems to save values into buffer cache
-	 *       when mixer.update() is called.
-	 *       ikSolver.update() and physics.update() change bone position/quaternion
-	 *       without mixer.update() then buffer cache will be inconsistent.
-	 *       So trying to avoid buffer cache inconsistency by doing
-	 *       backup bones position/quaternion right after mixer.update() call
-	 *       and then restore them after rendering.
-	 */
-	initBackupBones: function ( mesh ) {
-
-		mesh.skeleton.backupBones = [];
-
-		for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) {
-
-			mesh.skeleton.backupBones.push( mesh.skeleton.bones[ i ].clone() );
-
-		}
-
-	},
-
-	backupBones: function ( mesh ) {
-
-		mesh.skeleton.backupBoneIsSaved = true;
-
-		for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) {
-
-			var b = mesh.skeleton.backupBones[ i ];
-			var b2 = mesh.skeleton.bones[ i ];
-			b.position.copy( b2.position );
-			b.quaternion.copy( b2.quaternion );
-
-		}
-
-	},
-
-	restoreBones: function ( mesh ) {
-
-		if ( mesh.skeleton.backupBoneIsSaved !== true ) {
-
-			return;
-
-		}
-
-		mesh.skeleton.backupBoneIsSaved = false;
-
-		for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) {
-
-			var b = mesh.skeleton.bones[ i ];
-			var b2 = mesh.skeleton.backupBones[ i ];
-			b.position.copy( b2.position );
-			b.quaternion.copy( b2.quaternion );
-
-		}
-
-	}
+	return MMDLoader;
 
-};
+} )();