/**
 * MikuMikuDance VMD形式再生用ボーンパラメータ格納型
 *
 * @author b2ox
 */
package org.b2ox.pv3d.MikuMikuDance
{
	import flash.utils.ByteArray;
	import org.b2ox.pv3d.Utils;
	import org.papervision3d.core.math.*;

	/**
	 * ボーンパラメータの格納
	 */
	public class VMDBoneParam
	{
		public var move:Number3D;
		public var rotation:Quaternion;
		private var interpolationXYZ:Function;
		private var interpolationRot:Function;

		public function VMDBoneParam( move:Number3D, rotation:Quaternion, interpolationXYZ:Function=null, interpolationRot:Function=null ):void
		{
			this.move = move;
			this.rotation = rotation;
			this.interpolationXYZ = interpolationXYZ;
			this.interpolationRot = interpolationRot;
		}

		public function interpolateParam( target:VMDBoneParam, t:Number ):VMDBoneParam
		{
			return new VMDBoneParam( interpolationXYZ(move, target.move, t), interpolationRot(rotation, target.rotation, t) );
		}

		//------------------------------------
		private static const AproxMax:int = 24; // 近似計算の繰り返し回数の最大
		private static const AproxEpsilon:Number = 0.0001; // 近似計算の許容誤差
		private static const r127:Number = 1.0 / 127;
		private static var ans:Vector.<Number> = new Vector.<Number>(3);

		// x に対して x = calcBezier(x1/127, x2/127, t) を満たす t を求め、
		// その t における calcBezier(y1/127, y2/127, t) の値を返す関数 f(x) を作る
		// 0 < x1,x2,y1,y2 < 127, 0.0 < x < 1.0
		private static function makeInterpolation(x1:int, y1:int, x2:int, y2:int):Function
		{
			// 係数が同じ時
			if (x1 == y1 && x2 == y2) return function (x:Number):Number { return x; };
			// そうでないとき
			return function (x:Number):Number {
				var t:Number = x; // 近似開始
				var a:Number = x1 * r127;
				var b:Number = x2 * r127;
				var n:int = Utils.solveEquation3D(3 * (a - b) + 1, 3 * b - 6 * a, 3 * a, -x, ans);
				for (var i:int = 0; i < n; i++)
				{
					if ( 0 <= ans[i] && ans[i] <= 1 ) { t = ans[i]; break; }
				}
				/*
				for (var i:int = 0; i < AproxMax; i++)
				{
				var tmp:Number = calcBezier(a, b, t) - x;
				if (Math.abs(tmp) < AproxEpsilon) break; // 許容誤差未満になったら近似値として採用
				t -= tmp / calcBezierDiff(a, b, t); // ニュートン法
				}
				*/
				return calcBezier(y1 * r127, y2 * r127, t);
			};
		}
		private static function calcBezier(a:Number, b:Number, t:Number):Number
		{
			// 3次ベジェ: f(t) = (1-t)^3 p0 + 3t(1-t)^2 p1 + 3t^2(1-t) p2 + t^3 p3 (0 <= t <= 1)
			// ここでは p0 = 0, p1 = a, p2 = b, p3 = 1 なので
			// f(t) = 3t(1-t)^2 a + 3t^2(1-t) b + t^3
			//      = 3t(1-t)( (1-t)a + tb ) + t^3
			//      = t( 3(1-t)( (1-t)a + tb ) + t^2 ) ←掛け算の数を少なくした変形
			var _t:Number = 1.0 - t;
			return t * ( 3 * _t * (_t * a + t * b) + t * t );
		}
		private static function calcBezierDiff(a:Number, b:Number, t:Number):Number
		{
			// 3次ベジェ f(t) = 3t(1-t)^2 a + 3t^2(1-t) b + t^3 の微分
			// f'(t) = 3(1-t)^2 a - 6t(1-t) a + 6t(1-t) b - 3t^2 b + 3t^2
			//       = 3(1-t)^2 a + 6t(1-t)(b-a) + 3t^2(1-b)
			//       = 3*( (1-t)((1-t)a + 2t(b-a)) + t^2(1-b) )
			var _t:Number = 1.0 - t;
			return 3 * ( _t * (_t * a + 2 * t * (b-a)) + t * t  * (1.0-b));
		}

		//------------------------------------
		/**
		 * ByteArrayで補間関数を作成
		 * @param	bary: VMD形式のXYZR 64バイト配列
		 */
		public function setInterpolationsByByteArray(bary:ByteArray):void
		{
			// interpolation bary[4*j]以外の値は不要
			var j:int = 0;
			var ipX:Function = makeInterpolation(bary[j], bary[j + 4], bary[j + 8], bary[j + 12]);
			j += 16;
			var ipY:Function = makeInterpolation(bary[j], bary[j + 4], bary[j + 8], bary[j + 12]);
			j += 16;
			var ipZ:Function = makeInterpolation(bary[j], bary[j + 4], bary[j + 8], bary[j + 12]);
			j += 16;
			var ipR:Function = makeInterpolation(bary[j], bary[j + 4], bary[j + 8], bary[j + 12]);

			interpolationXYZ = function (v0:Number3D, v1:Number3D, t:Number):Number3D
			{
				return Utils.lerpNumber3D(v0, v1, ipX(t), ipY(t), ipZ(t));
			}
			interpolationRot = function (q0:Quaternion, q1:Quaternion, t:Number):Quaternion
			{
				return Quaternion.slerp(q0, q1, ipR(t));
			}
		}
		/**
		 * X,Y,Z,R 4つの補間係数で補間関数を作成
		 * @param	ipCX
		 * @param	ipCY
		 * @param	ipCZ
		 * @param	ipCR
		 */
		public function setInterpolationsByCoeffs(ipCX:Array, ipCY:Array, ipCZ:Array, ipCR:Array):void
		{
			// translationに関する補間
			var ipX:Function = makeInterpolation(ipCX[0], ipCX[1], ipCX[2], ipCX[3]);
			var ipY:Function = makeInterpolation(ipCY[0], ipCY[1], ipCY[2], ipCY[3]);
			var ipZ:Function = makeInterpolation(ipCZ[0], ipCZ[1], ipCZ[2], ipCZ[3]);

			// rotationに関する補間
			var ipR:Function = makeInterpolation(ipCR[0], ipCR[1], ipCR[2], ipCR[3]);

			interpolationXYZ = function (v0:Number3D, v1:Number3D, t:Number):Number3D
			{
				return Utils.lerpNumber3D(v0, v1, ipX(t), ipY(t), ipZ(t));
			}
			interpolationRot = function (q0:Quaternion, q1:Quaternion, t:Number):Quaternion
			{
				return Quaternion.slerp(q0, q1, ipR(t));
			}
		}
	}
}

