/**
 * MikuMikuDance Model+Motion
 * MikuMikuDanceのモデルデータ(*.pmd)とモーションデータ(*.vmd)を読み込み、再生するためのユーザー用APIクラス
 *
 * var mmd:MikuMikuDance = new MikuMikuDance();
 * mmd.loadPMD("miku.pmd", 1.0, function ():void { mmd.loadVMD("umauma.vmd", "umauma", function ():void { mmd.play(); } ); });
 *
 * @author b2ox
 */
package org.b2ox.pv3d
{
	import flash.display.*;
	import flash.events.Event;
	import org.b2ox.pv3d.MikuMikuDance.*;
	import org.libspark.betweenas3.tweens.*;
	import org.libspark.thread.*;
	import org.papervision3d.core.geom.*;
	import org.papervision3d.core.geom.renderables.*;
	import org.papervision3d.core.math.*;
	import org.papervision3d.materials.*;
	import org.papervision3d.materials.utils.*;
	import org.papervision3d.objects.*;
	import org.tarotaro.flash.pv3d.*;

	public class MikuMikuDance extends TriangleMesh3D
	{
		//---------------------------------------------------------------------
		// 内部的なパラメータ類
		private var _pmdController:PMDController = null;
		public function get pmdController():PMDController { return _pmdController; }
		private var _vmdControllers:Vector.<IVMDController> = new Vector.<IVMDController>();
		private var _vmdNames:Vector.<String> = new Vector.<String>();
		private var _vmdID:int = -1;
		private var _animation:IVMDController = null;

		//---------------------------------------------------------------------
		private var _scaling:Number = 1.0;
		public function set scaling(v:Number):void { _scaling = v; }
		public function get scaling():Number { return _scaling; }

		//---------------------------------------------------------------------
		/**
		 * コンストラクタ
		 */
		public function MikuMikuDance():void
		{
			if (!Thread.isReady) Thread.initialize(new EnterFrameThreadExecutor());
			super(null, new Array(), new Array(), null);
			// ボーン変形などを行うコントローラ
			_pmdController = new PMDController(this);
		}

		//---------------------------------------------------------------------
		// モーション再生用API
		public function play():void { if(_animation) _animation.play(); }
		public function stop():void { if(_animation) _animation.stop(); }
		public function togglePause():void { if(_animation) _animation.togglePause(); }
		public function gotoAndPlay(pos:Number):void { if (_animation) _animation.gotoAndPlay(pos); }
		public function gotoAndStop(pos:Number):void { if (_animation) _animation.gotoAndStop(pos); }

		public function get playing():Boolean { return _animation ? _animation.playing : false; }
		public function get looping():Boolean { return _animation ? _animation.looping : false; }
		public function set looping(loop:Boolean):void { if (_animation) _animation.looping = loop; }

		private var _motionName:String = "";
		public function motionID_of(motionName:String):int
		{
			return _vmdNames.indexOf(motionName);
		}
		public function motionName_of(motionID:int):String
		{
			return _vmdNames[motionID];
		}

		public static const MOTION_CHANGED:String = "MMD_MOTION_CHANGED";
		public static const MOTION_ADDED:String = "MMD_MOTION_ADDED";
		public function changeMotion(motion:String):void {
			var mID:int = motionID_of(motion);
			if (mID >= 0) {
				_animation = _vmdControllers[mID];
				_motionName = motion;
				_vmdID = mID;
				dispatchEvent(new Event(MOTION_CHANGED));
				trace("[DEBUG] MikuMikuDance.changeMotion: ID:" + _vmdID + " Name:" + _motionName );
			}
		}
		public function changeMotionByID(motionID:int):void {
			_animation = _vmdControllers[motionID];
			_motionName = motionName_of(motionID);
			_vmdID = motionID;
			dispatchEvent(new Event(MOTION_CHANGED));
			trace("[DEBUG] MikuMikuDance.changeMotion: ID:" + _vmdID + " Name:" + _motionName );
		}
		public function changeNextMotion():void {
			if (_vmdControllers.length > 1) {
				var mID:int = (_vmdID + 1) % _vmdNames.length;
				changeMotionByID(mID);
			}
		}
		public function addMotion(motion:String, ctrl:IVMDController):void {
			trace("addMotion: "+motion);
			var mID:int = motionID_of(motion);
			if (mID < 0) {
				_vmdControllers.push(ctrl);
				_vmdNames.push(motion);
			} else {
				_vmdControllers[mID] = ctrl;
			}
			_vmdID = mID;
			_motionName = motion;
			_animation = ctrl;
			dispatchEvent(new Event(MOTION_ADDED));
		}
		public function noMotion():void { _animation = null; _motionName = ""; }
		public function get motionName():String { return _motionName; }

		public function getMotion(motion:String):IVMDController {
			var mID:int = motionID_of(motion);
			return (mID >= 0) ? _vmdControllers[mID] : null;
		}

		public function get pos():Number { return _animation ? _animation.tween.position : 0; }

		//---------------------------------------------------------------------
		// ポーズ変更用API
		public function update():void { _pmdController.update(); }

		//---------------------------------------------------------------------
		/**
		 * InteractiveScene3DEventを受け取るかどうかを設定します。(Metasequoiaクラスのまね。よくわかってない)
		 */
		public var interactive:Boolean = false;

		public var version:Number, modelName:String, comment:String;

		//---------------------------------------------------------------------
		/**
		 * PMDファイルの読み込み
		 *
		 * @param	url
		 * @param	scaling
		 * @param	afterLoad
		 */
		public function loadPMD(url:String, scaling:Number=1.0, afterLoad:Function=null):void
		{
			makePMDLoader(url, scaling).$next( function ():void { if (afterLoad != null) afterLoad(); } ).start();
		}

		//---------------------------------------------------------------------
		/**
		 * PMDファイルの読み込みスレッドの作成
		 *
		 * @param	url
		 * @param	scaling
		 */
		public function makePMDLoader(url:String, scaling:Number=1.0):PMDLoaderThread
		{
			this._scaling = scaling;
			return new PMDLoaderThread(this, url, scaling);
		}

		//---------------------------------------------------------------------
		// 以下はPMDの読み込みで使用する頂点や面情報の登録用メソッド
		private var _uvarray:Array;
		/**
		 * UV配列の取得
		 */
		public function get uvarray():Array { return _uvarray; }

		/**
		 * 頂点,UV配列の初期化
		 * @param	vertex_count
		 */
		public function initVertexUVarrays(vertex_count:uint):void
		{
			geometry.vertices = new Array(vertex_count);
			_uvarray = new Array(vertex_count);
		}

		/**
		 * 頂点の登録
		 * @param	i
		 * @param	x
		 * @param	y
		 * @param	z
		 */
		public function regVertex(i:uint, x:Number, y:Number, z:Number):void
		{
			geometry.vertices[i] = new Vertex3D(x, y, z);
		}

		/**
		 * UVの登録
		 * @param	i
		 * @param	u
		 * @param	v
		 */
		public function regUV(i:uint, u:Number, v:Number):void
		{
			_uvarray[i] = new NumberUV(u, v);
		}

		/**
		 * (三角形)面配列の初期化
		 * @param	face_count
		 */
		public function initTriangleArray(face_count:uint):void
		{
			geometry.faces = new Array(face_count);
		}

		/**
		 * 三角形の登録
		 * 頂点は頂点配列のインデックスで指定
		 * @param	i
		 * @param	v0
		 * @param	v1
		 * @param	v2
		 */
		public function regTriangle(i:uint, v0:int, v1:int, v2:int):void
		{
			geometry.faces[i] = new Triangle3D(this, [geometry.vertices[v2], geometry.vertices[v1], geometry.vertices[v0]], null, [_uvarray[v2], _uvarray[v1], _uvarray[v0]]);
		}

		/**
		 * 材質リストの初期化
		 */
		public function initMaterials():void
		{
			materials = new MaterialsList();
		}

		/**
		 * 材質の登録
		 * テクスチャはBitmapDataで与える
		 *
		 * @param	matName
		 * @param	tex
		 * @param	fillColor
		 * @param	faceOffset
		 * @param	count
		 */
		public function regMaterialBase(matName:String, tex:BitmapData, fillColor:uint, faceOffset:uint, count:uint):void
		{
			var material:BitmapMaterial;
			if (tex != null)
			{
				material = new BitmapMaterial(tex, true);
			} else {
				material = new BitmapColorMaterial(fillColor);
			}
			material.interactive = this.interactive;
			material.smooth = true;
			material.name = matName;
			materials.addMaterial(material, material.name);
			for (var i:int = 0; i < count; i++) geometry.faces[faceOffset + i].material = material;
		}

		private var spa_re:RegExp = /\*.+\.sp[ah]$/i; // ～.bmp*～.spa や .sph への対策
		/**
		 * 材質の登録
		 * テクスチャは外部から読み込む
		 *
		 * @param	matName
		 * @param	texPath
		 * @param	fillColor
		 * @param	faceOffset
		 * @param	count
		 */
		public function regMaterial(matName:String, texPath:String, fillColor:uint, faceOffset:uint, count:uint):void
		{
			if (texPath != "")
			{
				if (spa_re.test(texPath))
				{
					trace("警告: " + texPath + " の読み込みは現在未対応です");
					texPath = texPath.replace(spa_re, "");
				}
				var manager:ImageManager = new ImageManager(texPath);
				manager.addEventListener( ImageManagerEvent.LOAD_COMPLETE, function (evt:ImageManagerEvent):void
				{
					regMaterialBase(matName, evt.manager.bitmapData, fillColor, faceOffset, count);
				}
				);
				manager.getImage(texPath);
			} else {
				regMaterialBase(matName, null, fillColor, faceOffset, count);
			}
		}

		//---------------------------------------------------------------------
		/**
		 * VMD読み込み時のスケーリング設定
		 * 未設定もしくは0以下の値の場合はPMD読み込み時のスケーリング値を使う
		 */
		private var _vmdScaling:Number = -1;
		public function set vmdScaling(v:Number):void { _vmdScaling = v; }
		public function get vmdScaling():Number { return _vmdScaling > 0 ? _vmdScaling : _scaling; }
		//---------------------------------------------------------------------
		/**
		 * VMDファイルを読み込んでモーションを登録する
		 *
		 * @param	url
		 * @param	motion_name
		 * @param	afterLoad
		 */
		public function loadVMD(url:String, motion_name:String, afterLoad:Function = null):void
		{
			makeVMDLoader(url, motion_name).$next(
			function (vmd:VMDLoaderThread):void {
				if (afterLoad != null) afterLoad();
			} ).start();
		}

		//---------------------------------------------------------------------
		/**
		 * VMDファイルを読み込んでモーションを登録するスレッドの作成
		 *
		 * @param	url
		 * @param	motion_name
		 */
		public function makeVMDLoader(url:String, motion_name:String):VMDLoaderThread
		{
			return new VMDLoaderThread(url, _pmdController, vmdScaling).$next(
			function (vmd:VMDLoaderThread):void { addMotion(motion_name, vmd.controller); }
			);
		}

		//---------------------------------------------------------------------
		/**
		 * ボーンにアクセサリを取り付ける。ボーンが存在しないときはfalseが返る
		 * @param	boneName
		 * @param	mdl
		 * @param	mdlName
		 * @return
		 */
		public function attachModel(boneName:String, mdl:DisplayObject3D, mdlName:String):Boolean
		{
			return _pmdController.attachModel(boneName, mdl, mdlName);
		}
		/**
		 * ボーンからアクセサリを取り外す
		 * @param	boneName
		 * @param	mdlName
		 */
		public function removeModel(boneName:String, mdlName:String):void
		{
			_pmdController.removeModel(boneName, mdlName);
		}

		//---------------------------------------------------------------------
		public function setSkinWeight(skinName:String, weight:Number=1.0):void { _pmdController.setSkinWeight(skinName, weight); }

		public function setLip(skinName:String, weight:Number=1.0):void { setSkinType(skinName, weight, PMDSkin.TYPE_LIP); }
		public function setEye(skinName:String, weight:Number=1.0):void { setSkinType(skinName, weight, PMDSkin.TYPE_EYE); }
		public function setEyeBrow(skinName:String, weight:Number=1.0):void { setSkinType(skinName, weight, PMDSkin.TYPE_EYEBROW); }
		private function setSkinType(skinName:String, weight:Number=1.0, skinType:int=-1):void
		{
			_pmdController.resetSkinWeightsByType(skinType);
			_pmdController.setSkinWeight(skinName, weight);
		}
		public function resetSkinWeightsByType(skinType:int):void { _pmdController.resetSkinWeightsByType(skinType); }
		//---------------------------------------------------------------------
		//public function setBoneParam(boneName:String, rot:Quaternion, dv:Number3D):void { _pmdController.setVMDBoneParam(boneName, rot, dv); }
		public function showBone():void { _pmdController.showBone(); }
	}
}

