module yamalib.draw.morph;

private import y4d_draw.drawbase;
private import y4d_draw.texture;
private import y4d_draw.surface;
private import y4d_draw.screen;
private import ytl.vector;

private import yamalib.log.log;

/// Dでもモーフィング
//	転送元と転送先の対応点集合を与えると、
//	転送先の任意点に対応する転送元の点が取得できる
class MorpherCalc {
public:

	///	設定した対応点集合のクリア
	void	Clear() {
		//	設定した対応点集合のクリア
		m_nDstX.clear();
		m_nDstY.clear();
		m_nSrcX.clear();
		m_nSrcY.clear();
	}	

	///	転送元と、転送先の対応点集合
	void	Set(int nSrcX,int nSrcY,int nDstX,int nDstY) {
		m_nSrcX.push_back(nSrcX);
		m_nSrcY.push_back(nSrcY);
		m_nDstX.push_back(nDstX);
		m_nDstY.push_back(nDstY);
	}

	/// なんぢゃろ
	void	SetRev(int nDstX,int nDstY,int nSrcX,int nSrcY) {
		m_nSrcX.push_back(nSrcX);
		m_nSrcY.push_back(nSrcY);
		m_nDstX.push_back(nDstX);
		m_nDstY.push_back(nDstY);
	}

	///	転送先の点を与えて、転送元の点を取得する
	void	Get(int nDstX, int nDstY, inout int nSrcX, inout int nSrcY) {
		//	与えられた点の近傍3点を取得
		//		m_nSrcX.size()>=3と仮定
		
		static const int PMAX = 5;	//	近傍検索数
	
		int	nNear[PMAX];	//	インデックスだけ記憶＾＾
		int	nDist[PMAX] = int.max;	//	距離の２乗
	
		for(int n = 0; n < m_nSrcX.size(); n++) {
			int nDistance = (nDstX - m_nDstX[n])*(nDstX - m_nDstX[n])
							+ (nDstY - m_nDstY[n])*(nDstY - m_nDstY[n]);
			if (nDistance < nDist[PMAX-1]) {	//	一番遠いエントリより近いか？
				for(int i = 0; i < PMAX; i++){
					if (nDistance < nDist[i]){	//	insertする
						for(int j = PMAX - 1; j > i; j--){	//	ひとつ下にずらす
							nNear[j] = nNear[j-1];
							nDist[j] = nDist[j-1];
						}
						nNear[i] = n;
						nDist[i] = nDistance;
						break;
					}
				}
			}
		}

		if (nDist[2] == int.max) {
			Log.printWarn("CMorpherCalc::Getで近傍３点が求まっていない");
		}
		
		//	UVベクトルに分解
		//		a U + b V = Z	(a,b定数, U,V,Zはベクトル)
		//
		//		[U V](a) = Z  よって (a) = [U V]^-1・Z
		//			 (b)			 (b)
		//	ただし、
		//	３点が同一直線上にあるとU,Vベクタは一次独立でないので
		//	係数a,bは求まらない。
		//	そのときは、PMAXまで次候補を探すのだ～＾＾
		int nLast = 2;
		int a,b,c,d;	//	[U V]の行列要素
		int det;
	retry:;
		a = m_nDstX[nNear[1]] - m_nDstX[nNear[0]];
		c = m_nDstY[nNear[1]] - m_nDstY[nNear[0]];
		b = m_nDstX[nNear[nLast]] - m_nDstX[nNear[0]];
		d = m_nDstY[nNear[nLast]] - m_nDstY[nNear[0]];
		det = a*d - b*c;
		if (det==0) {	//	nNear[nLast]を破棄して、再検索＾＾
			if (++nLast == PMAX) {
				if (nDist[2] == int.max) {
					Log.printWarn("CMorpherCalc::Getで近傍PMAX点はすべて同一直線上");
				}
				return ;
			}
			goto retry;
		}
		int e,f;
		e = nDstX - m_nDstX[nNear[0]];
		f = nDstY - m_nDstY[nNear[0]];
	
		double u,v;
		u = ( cast(double)(d*e - b*f) ) / det;
		v = ( cast(double)(a*f - c*e) ) / det;
		//	↑逆行列を左辺から掛けただけね

		//	さきほどの転送先近傍３点に対応する転送元近傍３点を抽出
		nSrcX = cast(int) ((m_nSrcX[nNear[1]]	   - m_nSrcX[nNear[0]])*u
			+	(m_nSrcX[nNear[nLast]] - m_nSrcX[nNear[0]])*v
			+	 m_nSrcX[nNear[0]]);
		nSrcY = cast(int) ((m_nSrcY[nNear[1]]	   - m_nSrcY[nNear[0]])*u
			+	(m_nSrcY[nNear[nLast]] - m_nSrcY[nNear[0]])*v
			+	 m_nSrcY[nNear[0]]);
		
	}
	
	/// コンストラクタ
	this() {
		m_nDstX = new vector!(int);
		m_nDstY = new vector!(int);
		m_nSrcX = new vector!(int);
		m_nSrcY = new vector!(int);
	}

protected:
	vector!(int)	m_nDstX;
	vector!(int)	m_nDstY;
	vector!(int)	m_nSrcX;
	vector!(int)	m_nSrcY;
};


class Morpher {
public:

	void	SetDiv(int nSizeX,int nSizeY,int nXDiv,int nYDiv) {
		Release();
		m_lpPointSrc = new PointInt[(nXDiv+1)*(nYDiv+1)];
		m_lpPointDst = new PointInt[(nXDiv+1)*(nYDiv+1)];
		
		m_nXDiv = nXDiv;
		m_nYDiv = nYDiv;
		m_nSizeX = nSizeX;
		m_nSizeY = nSizeY;		
	}
	
	void	GetDiv(out int nSizeX, out int nSizeY, out int nXDiv, out int nYDiv) {
		nXDiv = m_nXDiv;
		nYDiv = m_nYDiv;
		nSizeX = m_nSizeX;
		nSizeY = m_nSizeY;
	}
	
	/// 解放するなり
	void	Release() {
		m_lpPointSrc.length = 0;
		m_lpPointDst.length = 0;
	}

	///	転送元と、転送先の対応点集合
	void	Set(int nSrcX,int nSrcY,int nDstX,int nDstY) {
		m_MorphCalc.Set(nSrcX,nSrcY,nDstX,nDstY);
	}
	
	void	SetRev(int nDstX,int nDstY,int nSrcX,int nSrcY) {
		m_MorphCalc.Set(nSrcX,nSrcY,nDstX,nDstY);
	}
	
	///	計算するのだ＾＾
	void	Calc() {
		//	計算するのだ＾＾
//		WARNING(m_lpPointSrc==NULL,"CMorpher::SetDivせずにCalcが呼び出された");
		PointInt* lpPoint = cast(PointInt*) m_lpPointSrc;
		PointInt* lpPoint2 = cast(PointInt*) m_lpPointDst;
		
		for (int y = 0; y <= m_nYDiv; y++) {
			for (int x = 0; x <= m_nXDiv; x++) {
				int ix,iy;
				int px = x * (m_nSizeX-1) / m_nXDiv;
				int py = y * (m_nSizeY-1) / m_nYDiv;
				m_MorphCalc.Get(px,py,ix,iy);
				lpPoint.x = ix;
				lpPoint.y = iy;
				lpPoint2.x = px;
				lpPoint2.y = py;
				lpPoint++;
				lpPoint2++;
			}
		}
	}

	///	困ったときのため＾＾
	MorpherCalc		GetMorph() { return m_MorphCalc; }
	PointInt[]		GetPointSrc() { return m_lpPointSrc; }
	PointInt[]		GetPointDst() { return m_lpPointDst; }

	/// コンストラクタ
	this() {
		m_MorphCalc = new MorpherCalc();
	};
	
	/// デストラクタ
	~this() {
		Release();
	}
	
protected:
	int		m_nXDiv;					//	x方向の分割数
	int		m_nYDiv;					//	y方向の分割数	
	PointInt[]	m_lpPointSrc;				//	ソースの各ポイント
	PointInt[]	m_lpPointDst;				//	転送先の各ポイント
	int		m_nSizeX;					//	転送先サイズ
	int		m_nSizeY;					//	転送先サイズ
	MorpherCalc m_MorphCalc;			//	Morph計算用
};

//	Surfaceによるモーフィングクラス
class SurfaceMorph {
public:
	void	SetDiv(int nSizeX,int nSizeY,int nXDiv,int nYDiv) {
		m_Morph.SetDiv(nSizeX,nSizeY,nXDiv,nYDiv);	//	正計算
		m_MorphRev.SetDiv(nSizeX,nSizeY,nXDiv,nYDiv);	//	逆計算
	}
	
	/// 解放するなり
	void	Release() {
		m_Morph.Release();
		m_MorphRev.Release();		
	}
	
	///	転送元と、転送先の対応点集合
	void	Set(int nSrcX1,int nSrcY1,int nSrcX2,int nSrcY2) {
		m_Morph.Set(nSrcX1,nSrcY1,nSrcX2,nSrcY2);		//	正計算
		m_MorphRev.SetRev(nSrcX1,nSrcY1,nSrcX2,nSrcY2);	//	逆計算
	}
	
	///	計算するのだ＾＾
	void	Calc() {
		m_Morph.Calc();
		m_MorphRev.Calc();
	}	

	///	lpSrc1からlpSrc2へモーフィング。nPhaseは0-256,lpTemporaryはSecondaryと同サイズ確保してね。
	/** lpSecondary 描画先サーフェース
	    lpSrc1      遷移元サーフェース
	    lpSrc2      遷移先サーフェース
	*/ 
	void	OnDraw(Screen screen, inout Surface lpSrc1, inout Surface lpSrc2, inout Surface lpTemporary, inout Surface lpTmp2, int nPhase) {
		if (nPhase == 0) {
//			screen.blt(lpSrc1,0,0);
		} else if (nPhase == 256){
//			screen.blt(lpSrc2,0,0);
		} else {
			lpTemporary.clear();
			lpTmp2.clear();
			
			InnerOnDraw(lpTemporary, lpSrc1, m_Morph, 256-nPhase);
			InnerOnDraw(lpTmp2, lpSrc2, m_MorphRev, nPhase);
			
			if ( null is texSrc ) {
				texSrc = new Texture();
			}
			if ( null is texDst ) {
				texDst = new Texture();
			}
			
			if ( texSrc.getWidth() < lpTemporary.getWidth() || texSrc.getHeight() < lpTemporary.getHeight() ) {
				Surface s = new Surface();
				s.createDIB( lpTemporary.getWidth(), lpTemporary.getHeight(), true );
				texSrc.setSurface(s);
			}
			if ( texDst.getWidth() < lpTmp2.getWidth() || texDst.getHeight() < lpTmp2.getHeight() ) {
				Surface s = new Surface();
				s.createDIB( lpTmp2.getWidth(), lpTmp2.getHeight(), true );
				texDst.setSurface( s );
			}
			
			texSrc.subSurfaceFast( lpTemporary );
			texDst.subSurfaceFast( lpTmp2 );

			screen.blendSrcAlpha();
			// srcの描画
			screen.setColor(256-nPhase&255, 256-nPhase&255, 256-nPhase&255, 256-nPhase&255);
			screen.setColor(255,255,255, 256-nPhase&255);
			screen.blt( texSrc, 0, 0 );
			
			screen.blendAddColorAlpha();
			// dstの描画
			screen.setColor(nPhase, nPhase, nPhase, nPhase);
			screen.blt( texDst, 0, 0 );
			
		}
	}

	/// コンストラクタ
	this() {
		m_Morph = new Morpher();
		m_MorphRev = new Morpher();
	}

protected:
	Morpher	m_Morph;	//	正計算
	Morpher	m_MorphRev;	//	逆計算
	
	Texture texSrc;
	Texture texDst;

	void	InnerOnDraw(inout Surface lpTarget,inout Surface lpSrc, inout Morpher lpMorph, int nPhase) {
		PointInt* lpPoint;
		PointInt* lpPoint2;
		PointInt point_d[4];	//	転送先ポイント
		PointInt point_s[4];	//	転送元ポイント
	
		lpPoint = lpMorph.GetPointSrc().ptr;
		lpPoint2= lpMorph.GetPointDst().ptr;
	
		int nXDiv,nYDiv,nSizeX,nSizeY;
		lpMorph.GetDiv(nSizeX,nSizeY,nXDiv,nYDiv);
		
		int x,y;
		for (y = 0; y < nYDiv; y++) {
			for (x = 0; x < nXDiv; x++) {
				int p = x + y*11;
				point_s[0] = lpPoint[p];
				point_s[1] = lpPoint[p+1];
				point_s[2] = lpPoint[p+11];
				point_s[3] = lpPoint[p+12];
				point_d[0].x = x * (nSizeX-1) / nXDiv;
				point_d[0].y = y * (nSizeY-1) / nYDiv;				
				point_d[1].x = (x+1) * (nSizeX-1) / nXDiv;
				point_d[1].y = point_d[0].y;
				point_d[2].x = point_d[0].x;
				point_d[2].y = (y+1) * (nSizeY-1) / nYDiv;
				point_d[3].x = point_d[1].x;
				point_d[3].y = point_d[2].y;
				// 内分点を取る
				for(int ii = 0; ii < 4; ii++) {
					point_s[ii].x = ( cast(int) point_d[ii].x * nPhase + cast(int) point_s[ii].x * (256-nPhase) ) >> 8;
					point_s[ii].y = ( cast(int) point_d[ii].y * nPhase + cast(int) point_s[ii].y * (256-nPhase) ) >> 8;
				}
				
				RectInt rc;
				rc.setRect(point_d[0].x, point_d[0].y, point_d[3].x, point_d[3].y);
				
				//Log.print("call morphBltFast, x %s, y %s", x, y);
				
				lpTarget.morphBltFast(lpSrc, point_s.ptr, &rc);
			}
		}
	}
	
}
