module y4d_draw.screen;

private import SDL;
private import SDL_image;
private import opengl;
private import ytl.y4d_result;
private import std.string;
private import ytl.singleton;
private import y4d_draw.drawbase;
private import y4d_draw.surface;
private import y4d_draw.texture;
private import y4d_math.round;
private import y4d_math.sintable;
private import y4d_draw.drawcontext;
private import y4d_draw.texturebase;

private import yamalib.log.log;

///	画面を表現しているサーフェース
/**
	描画にはopenGLを用いています。
	画面にbmp,png,etc..画像を描画するためには、 Texture クラスを用います。
	2D描画するときも、 Texture クラスを用います。<BR>

	座標系は、ディフォルトで、ウィンドゥの左上が(0,0)、右下が(640,480)となる
	※　右上の(640,480)というのは、ウィンドゥサイズにより異なります。<BR>

	視体積(見える部分)の深さ(z方向)は、0～256が初期値として設定してあります。
	つまりは、
<PRE>
		glOrtho(0,640,480,0,0,256);	//	256 = depth
</PRE>
	としてあります。これは、setVideoModeのときに設定されます。
	もし、これでまずければ再設定するようにしてください。<BR>

	関数の説明で2Dと書いてあるのは、2Dに特化した描画のときに用いるべきもので
	3D描画の時に用いてうまく動くかどうかは保証しません。

	また3D描画を行ないたいときは、 setVideoMode に先駆けて enable3D を呼び出す
	ようにしてください。
*/
class Screen {
	///	スクリーン解像度をテストする(2D/3D)
	/**
		ビデオボードがこちらの希望のスクリーン解像度、
		bpp深度を持つとは限らない。<BR>

		そこで、テストをする必要がある。
	<PRE>
		beginScreenTest();	//	いまからテストする
		testVideoMode(640,480,32);	// フルスクリーン 640×480×32をテスト
		testVideoMode(640,480,16);	// フルスクリーン 640×480×16をテスト
		testVideoMode(640,480,24);	// フルスクリーン 640×480×24をテスト
		testVideoMode(640,480,0);	// ウィンドゥモード 640×480をテスト
		endScreenTest();	//	テスト終了
		//	結果として、最初にスクリーン変更に成功した解像度になる。
	</PRE>
	フルスクリーンモードは、成功するとは限らない。
	ウィンドゥモードは、現在のディスプレイ解像度より小さなサイズならば
	メモリが足りている限りは成功する。
	*/
	void beginScreenTest(){
		bTestScreen = true; bTestScreenSuccess=false;
	}

	///	スクリーン解像度のテスト終了(2D/3D)
	/**
		beginScreenTest も参照すること。<BR>

		スクリーン変更に成功していれば0,失敗していれば非0が返る。
	*/
	y4d_result endScreenTest(){
		bTestScreen = false;
		return bTestScreenSuccess
			? y4d_result.no_error:y4d_result.happen_some_error;
	}

	///	スクリーン解像度のテスト(2D/3D)
	/**
		スクリーン解像度のテストを行なう。<BR>
		x,y はスクリーンサイズ。bpp はピクセル深度(bits per pixel)
		bpp として0を指定すれば、ウィンドゥモード。
		(現在のモードから解像度切り替えを行なわないの意味)

		beginScreenTest～endScreenTestで用いる。
		beginScreenTest も参照すること。

		成功すれば0が返るが、この返し値を使うのではなく、
		beginScreenTest～endScreenTestで用いること。

		テストに成功した場合、その成功した解像度が
		getDrawContext で取得できる描画contextの
		rateX , rateY にも自動的に代入されます。
	*/
	y4d_result testVideoMode(int x,int y,int bpp){
		if (bTestScreenSuccess) return y4d_result.already_done;
			// すでに成功している
		y4d_result res;
		res = initializer.get().init();
		if (y4d_result.no_error != res) {
			Log.printError("%s#testVideoMode initializer.get().init() fault!!",this.toString);
			return res;
		}

		uint option;
		if (bpp == 0) {
		//	option |= SDL_RESIZABLE;
		} else {
			option |= SDL_FULLSCREEN;
		}
		option |= SDL_OPENGL;	//	openGL強制

		SDL_VideoInfo * VideoInfo = SDL_GetVideoInfo();
		// query SDL for information about our video hardware

		if (!VideoInfo)   // if this query fails
		{
		//	これ失敗されてもなぁ．．(´Д`)
			Log.printError("%s#testVideoMode SDL_GetVideoInfo() fault!!",this.toString);
		} else {
/*		//	これ書いたほうがいいかな？
			if(VideoInfo -> hw_available)
				VideoFlags |= SDL_HWSURFACE;
			else
				VideoFlags |= SDL_SWSURFACE;
*/
//			if(VideoInfo.blit_hw)
//			if((VideoInfo.flags & 0x0200)!=0)
			// is hardware blitting available
				option |= SDL_HWACCEL;
		}

		Log.printLook("%s#testVideoMode SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 )",this.toString);
		//	画面がダブルバッファであることを要求する
		SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

		//	3Dモードならばdepth bufferが無いことには...
		if (use3d_rendering){
			Log.printLook("%s#testVideoMode SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,16)",this.toString);
			SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,16);
			//	16ビットのdepth buffer
		} else {
		}

		//	ステンシルバッファとアキュームレーションバッファは指定なし
		Log.printLook("%s#testVideoMode SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0)",this.toString);
		SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0);
		SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 0);
		SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 0);
		SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 0);
		SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 0);

		Log.printLook("%s#testVideoMode SDL_SetVideoMode(x,y,bpp,option)",this.toString);
		surface = SDL_SetVideoMode(x,y,bpp,option);
		SDL_WM_ToggleFullScreen(surface);
		if (!surface) {
			Log.printError("%s#testVideoMode SDL_WM_ToggleFullScreen fault!!",this.toString);
			return y4d_result.SDL_error; // failed
		}
		/*
			openGLの場合は、surfaceに対して書き込むのでは
			ないから、取得する必要がない。
		*/

		bTestScreenSuccess = true;	//	変更成功
		if (strCaption) {	//	キャプション設定されている
			Log.printLook("%s#testVideoMode SDL_WM_SetCaption",this.toString);
			SDL_WM_SetCaption(std.string.toStringz(strCaption), null);
		}

		screenX = x; screenY = y;
		Log.printLook("%s#testVideoMode setView() START",this.toString);
		setView();
		Log.printLook("%s#testVideoMode setView() END",this.toString);

		//	描画用のコンテクストも書き換えておかねば
		DrawContext d = getDrawContext();
		if (d){
			d.setScreenSize(x,y);
		}

		//	テクスチャー描画が基本なのでテクスチャーを有効にしておかねば
		glEnable(GL_TEXTURE_2D);
		Log.printLook("%s#testVideoMode END",this.toString);
		return y4d_result.no_error;
	}

	///	画面解像度の変更(2D/3D)
	/**
		引数の意味はtestVideoModeと同じ。
		変更できなければ非0が返る。変更できるとは限らないので
		beginScreenTest～endScreenTestを用いるべき。
	*/
	int		setVideoMode(int width,int height,int bpp) {
		beginScreenTest();
		testVideoMode(width,height,bpp);
		return endScreenTest();
	}

	///	実画面に描画を行なう(2D/3D)
	y4d_result	update() {
		if (!bTestScreenSuccess)
			return y4d_result.precondition_error;
			// サーフェスーが構築されてない

		//	openGLの場合は、SDL_UpdateRectではいけない
		glFlush();
		SDL_GL_SwapBuffers();
		return y4d_result.no_error;
	}

	///	ウィンドゥキャプションの設定(2D/3D)
	/**
		画面の解像度変更前/後、どちらに実行しても良い。
	*/
	void setCaption(char[] name, char[] iconFile) {
		strCaption = name;
		if (bTestScreenSuccess) {
			SDL_WM_SetCaption(std.string.toStringz(name), null);
			if ( !(iconFile is null) ) {
				SDL_WM_SetIcon( SDL_LoadBMP(cast(char*) iconFile), null );
			}
		} else {
			//	画面解像度変更、まだ行なわれていない
		}
	}

	///	このサーフェースの幅を得る
	/**
		setVideoModeで指定したxがこれ。
	*/
	uint	getWidth() { return screenX; }

	///	このサーフェースの高さを得る
	/**
		setVideoModeで指定したyがこれ。
	*/
	uint	getHeight() { return screenY; }

	///	画面のクリア(2D/3D)
	/**
		画面をクリアする色は、 setClearColor で設定された色
	*/
	void	clear(){
		glClear(GL_COLOR_BUFFER_BIT);

		/*
			GL_COLOR_BUFFER_BIT
			Indicates the buffers currently enabled for color writing.
			GL_DEPTH_BUFFER_BIT
			Indicates the depth buffer.
			GL_ACCUM_BUFFER_BIT
			Indicates the accumulation buffer.
			GL_STENCIL_BUFFER_BIT
			Indicates the stencil buffer.
		*/
	}

	///	clearするときの色を設定する(2D/3D)
	/**
		rgbaは各 0～255。
		パラメータはbyteでいいのだが、intからbyteに暗黙で変換しないので
		かえって使いにくいので、intをとるようになっている。
	*/
	void	setClearColor(int r,int g,int b,int a){
		glClearColor((cast(float)r)/255,(cast(float)g)/255,
			(cast(float)b)/255,(cast(float)a)/255);
	}

	///	clearするときの色を設定する(2D/3D)
	/**
		rgbは各 0～255。
		パラメータはbyteでいいのだが、intからbyteに暗黙で変換しないので
		かえって使いにくいので、intをとるようになっている。
	*/
	void	setClearColor(int r,int g,int b){
		setClearColor(r,g,b,0);
	}

	///	直線を引く(2D)
	/**
		※描画は二次元的な描画なのであとから描画したものが前面に描画される。

		DrawContext に基づくclipping、座標系の変換を行なう。
	*/
	void	drawLine(int x1,int y1,int x2,int y2){
		//	現在のcontextに即して変換する必要がある。
		DrawContext dc = getDrawContext();

		glBegin(GL_LINES);
			glVertex2f(x1*dc.rateX + dc.offsetRX,y1*dc.rateY + dc.offsetRY);
			glVertex2f(x2*dc.rateX + dc.offsetRX,y2*dc.rateY + dc.offsetRY);
		glEnd();
	}

	///	三角形ポリゴンを描く(2D)
	/**
		3点を繋ぐようにした四角形を描く。中は、塗りつぶし。
		※描画は二次元的な描画なのであとから描画したものが前面に描画される。

		DrawContext に基づくclipping、座標系の変換を行なう。
	*/
	void	drawPolygon(int x1,int y1,int x2,int y2,int x3,int y3){
		//	現在のcontextに即して変換する必要がある。
		DrawContext dc = getDrawContext();

		glBegin(GL_POLYGON);
			glVertex2f(x1*dc.rateX + dc.offsetRX,y1*dc.rateY + dc.offsetRY);
			glVertex2f(x2*dc.rateX + dc.offsetRX,y2*dc.rateY + dc.offsetRY);
			glVertex2f(x3*dc.rateX + dc.offsetRX,y3*dc.rateY + dc.offsetRY);
		glEnd();
	}

	///	長方形ポリゴンを描く(2D)
	/**
		4点を繋ぐようにした四角形を描く。要するに凸4角形。中は、塗りつぶし。
		※描画は二次元的な描画なのであとから描画したものが前面に描画される。

		DrawContext に基づくclipping、座標系の変換を行なう。
	*/
	void	drawPolygon(int x1,int y1,int x2,int y2,int x3,int y3,
	int x4,int y4){
		//	現在のcontextに即して変換する必要がある。
		DrawContext dc = getDrawContext();

		glBegin(GL_POLYGON);
			glVertex2f(x1*dc.rateX + dc.offsetRX,y1*dc.rateY + dc.offsetRY);
			glVertex2f(x2*dc.rateX + dc.offsetRX,y2*dc.rateY + dc.offsetRY);
			glVertex2f(x3*dc.rateX + dc.offsetRX,y3*dc.rateY + dc.offsetRY);
			glVertex2f(x4*dc.rateX + dc.offsetRX,y4*dc.rateY + dc.offsetRY);
		glEnd();
	}

	///	凸5角形を描く。(2D)
	/**
		5点を繋ぐようにした四角形を描く。要するに凸5角形。中は、塗りつぶし。
		※描画は二次元的な描画なのであとから描画したものが前面に描画される。

		DrawContext に基づくclipping、座標系の変換を行なう。
	*/
	void	drawPolygon(int x1,int y1,int x2,int y2,int x3,int y3,
		int x4,int y4,int x5,int y5){
		//	現在のcontextに即して変換する必要がある。
		DrawContext dc = getDrawContext();

		glBegin(GL_POLYGON);
			glVertex2f(x1*dc.rateX + dc.offsetRX,y1*dc.rateY + dc.offsetRY);
			glVertex2f(x2*dc.rateX + dc.offsetRX,y2*dc.rateY + dc.offsetRY);
			glVertex2f(x3*dc.rateX + dc.offsetRX,y3*dc.rateY + dc.offsetRY);
			glVertex2f(x4*dc.rateX + dc.offsetRX,y4*dc.rateY + dc.offsetRY);
			glVertex2f(x5*dc.rateX + dc.offsetRX,y5*dc.rateY + dc.offsetRY);
		glEnd();
	}

	///	色を設定する(2D/3D)
	/**
		ラインやポリゴンの描画色を設定する。
		rgbは各 0～255。
		パラメータはbyteでいいのだが、intからbyteに暗黙で変換しないので
		かえって使いにくいので、intをとるようになっている。
	*/
	void	setColor(int r_,int g_,int b_){
		glColor4ub(cast(ubyte) r_,cast(ubyte) g_,cast(ubyte) b_, 255);
		color.r = cast(ubyte) r_; 
		color.g = cast(ubyte) g_; 
		color.b = cast(ubyte) b_; 
		color.a = 255;
	}

	///	色を設定する(2D/3D)
	/**
		ラインやポリゴンの描画色を設定する。
	*/
	void	setColor(Color4ub c) {
		glColor4ub(c.r,c.g,c.b,c.a);
		color = c;
	}

	///	色を設定する(2D/3D)
	/**
		ラインやポリゴンの描画色を設定する。
		rgbaは各 0～255。
		パラメータはbyteでいいのだが、intからbyteに暗黙で変換しないので
		かえって使いにくいので、intをとるようになっている。
	*/
	void	setColor(int r_,int g_,int b_,int a_){
		glColor4ub(cast(ubyte) r_,cast(ubyte) g_,cast(ubyte) b_,cast(ubyte) a_);
		color.r = cast(ubyte) r_; 
		color.g = cast(ubyte) g_; 
		color.b = cast(ubyte) b_; 
		color.a = cast(ubyte) a_;
	}

	///	色をリセットする(2D/3D)
	/**
		ラインやポリゴンの描画色を(255,255,255)に設定する。
		テクスチャー貼り付けのときなど、setColorの値で乗算されるので、
		そのままの画像を表示したいならば(255,255,255)に設定しておく
		必要があるため。
	*/
	void	resetColor(){
		setColor(255,255,255);
	}

	///	色をセットする(2D/3D)
	/**
		ラインやポリゴンの描画色を(a,a,a)に設定する。
		aの範囲は0～255。<BR>
		テクスチャー貼り付けのときなど、setColorの値で乗算されるので、
		fade(薄く描画)したければ、(a,a,a)に設定する必要があるため。
	*/
	void	setColor(int a) {
		setColor(a,a,a);
	}

	///	setColorで設定されている色の取得
	void	getColor(out int r_,out int g_,out int b_){
		r_ = color.r; g_ = color.g; b_ = color.b;
	}

	///	setColorで設定されている色の取得
	void	getColor(out int r_,out int g_,out int b_,out int a_){
		r_ = color.r; g_ = color.g; b_ = color.b; a_ = color.a;
	}

	///	setColorで設定されている色の取得
	Color4ub getColor4ub() { return color; }

	///	画面にオフセットを加える(2D)
	/**
		表示する視界に2次元オフセット(ox,oy)を加える。
		(100,200)を設定すると、画面下が(100,200),画面右上が(100+640,200+480)
		の意味になる。画面を揺らしたりするときに使うと良い。
	*/
	void	setScreenOffset(int ox,int oy){
		offsetX = ox;
		offsetY = oy;
		setView();
	}

	///	視体積の設定(2D)
	/**
		ウィンドゥの左上が(x1,y1),右下が(x2,y2)になるように設定される。
		setScreenOffset,setVideoMode等で内部的に呼び出される
	*/
	void	setView(int x1,int y1,int x2,int y2){
		glLoadIdentity();
		glOrtho(x1,x2,y2,y1,0,256);	//	0～256 = depth
	}

	///	画像の描画(2D)
	/**
		テクスチャーを(x,y)に等倍で描画する。
		テクスチャーの色には、直前のsetColorの値が乗算される。<BR>

		色の変更　→　setColor(r,g,b)/setColor(a)/resetColor() <BR>
		ブレンド方法変更　→　blendなんちゃら関数 <BR>

		※　テクスチャーを描画するとき、
		glBindTextureを内部的に呼び出すので、これを設定している人は注意。

		描画を転送先でclipしたいときは、 getDrawContext で
		描画コンテクストを書き換えればok。
	*/
	void	blt(ITextureBase src,int x,int y){
		//	visitorパターンで実装しておく
		if (src)
			src.blt(getDrawContext(),x,y);
	}

	///	画像の描画(2D)
	/**
		bltの転送元矩形の指定できるバージョン。
		srcRectがnullのときはソース全域。<BR>

		高速化のため転送元矩形が転送元サーフェースからハミ出る場合の
		処理はしていないので、何とかしる。<BR>

		転送元矩形として、(right,top,left,bottom)を指定すれば
		左右反転して表示される。(left,bottom,right,top)を指定すれば
		上下反転して表示される。
	*/
	void	blt(ITextureBase src,int x,int y,Rect* srcRect){
		if (src)
			src.blt(getDrawContext(),x,y,srcRect);
	}

	///	画像の描画(2D)
	/**
		bltの転送元矩形と転送先サイズの指定できるバージョン。
		srcRectがnullのときはソース全域。
		dstSizeがnullのときは転送先全域(のサイズ)。

		転送元矩形として、(right,top,left,bottom)を指定すれば
		左右反転して表示される。(left,bottom,right,top)を指定すれば
		上下反転して表示される。
	*/
	void	blt(ITextureBase src,int x,int y,Rect* srcRect,Size* dstSize)
	{
		if (src)
			src.blt(getDrawContext(),x,y,srcRect,dstSize);
	}

	///	画像の描画(2D)
	/**
		bltの転送元矩形と転送先4点の指定できるバージョン。
		srcRectがnullのときはソース全域。
		転送元矩形が、転送先として指定された4点に移動。<BR>

		転送先は、
		point[0]が左上、point[1]が右上、point[2]が右下、point[3]が左下
		の点を指定しているものとする。

	<PRE>
		Screen screen = new Screen;
		screen.setVideoMode(640,480,0);
		Texture tex = new Texture;
		tex.load("title.png");

		while (GameFrame.pollEvent()==0){

			screen.clear();

			Point[4] points;
			points[0].setPoint(100,100);
			points[1].setPoint(400,100);
			points[2].setPoint(640,480);
			points[3].setPoint(0,480);
			screen.blt(tex2,0,0,null,points);	//	台形描画

			screen.update();
		}
	</PRE>
	*/
	void	blt(ITextureBase src,Rect* srcRect,Point[4] point)
	{
		if (src)
			src.blt(getDrawContext(),srcRect,point);
	}

	///	凸四角形→凸四角形への転送
	void	blt(ITextureBase src,Point[4] srcPoint,Point[4] dstPoint)
	{
		if (src)
			src.blt(getDrawContext(),srcPoint,dstPoint);
	}

	///	bltの回転機能つき
	/**
		指定のテクスチャを転送先の(x,y)にrad回して描画します。<BR>
		radの単位は、0～512で一周(2π)となる角度。
			回転の方向は、右まわり（時計まわり）。
		rateは、拡大率。1.0ならば等倍。

		baseは転送元画像どの点が(x,y)に来るかを指定します。<BR>
			0 : 左上。		1 : 上辺の中点　2:右上<BR>
			3 : 左辺の中点	4 : 画像中心	5:右辺の中点<BR>
			6 : 左下。　	7 : 下辺の中点	8:右下<BR>
	*/
	void	bltRotate(ITextureBase src,int x,int y,int rad,float rate,int base)
	{
		if (!src) return ;
		int w = cast(int)src.getWidth();
		int h = cast(int)src.getHeight();
		switch (base){
		case 0: w=0; h=0; break;
		case 1: w/=2; h=0; break;
		case 2: h=0; break;
		case 3: w=0; h/=2; break;
		case 4: w/=2; h/=2; break;
		case 5: h/=2; break;
		case 6: w=0; break;
		case 7: w/=2; break;
		case 8: break;
		default:
			throw new Exception("Invalid drawing position option. Screen#bltRotate");
		}
		bltRotate(src,cast(int)(x-rate*w),cast(int)(y-rate*h),null,rad,rate,w,h);
	}

	///	bltの回転機能つき
	/**
		指定のテクスチャを転送先の(x,y)にrad回して描画します。<BR>
		radの単位は、0～512で一周(2π)となる角度。
			回転の方向は、右まわり（時計まわり）。
		rateは、拡大率。1.0ならば等倍。
		(bx,by)は転送元の回転中心。(x,y)の地点が転送元の画像中心になるように
		したいならば、(x-bx*rate,y-by*rate)をこのメソッドのx,yに渡す必要がある。
	*/
	void	bltRotate(ITextureBase src,int x,int y,int rad,float rate,
		int bx,int by){
		bltRotate(src,x,y,null,rad,rate,bx,by);
	}

	///	bltの回転機能つき
	/**
		指定のテクスチャを転送先の(x,y)に半径rで描画します。<BR>
		radの単位は、0～512で一周(2π)となる角度。
			回転の方向は、右まわり（時計まわり）。
		rateは、拡大率。1.0ならば等倍。
		(bx,by)は転送元の画像の、回転中心。
		srcRectは転送元矩形。nullならば転送元テクスチャ全体。
	*/
	void	bltRotate(ITextureBase src,int x,int y,Rect*srcRect,
		int rad,float rate,
		int bx,int by){

		if (!src) return ;
		Rect sr;
		if (!srcRect) {
			sr.setRect(0,0,src.getWidth(),src.getHeight());
			srcRect = &sr;
		}

		int sx,sy;	//	転送元サイズ
		sx = cast(int)(srcRect.right  - srcRect.left);
		sy = cast(int)(srcRect.bottom - srcRect.top);
		if (sx==0 || sy==0) return ;
		if (sx<0) sx=-sx;
		if (sy<0) sy=-sy;

		int dx,dy;	//	転送先サイズ
		dx = cast(int)(sx*rate);
		dy = cast(int)(sy*rate);
		if (dx==0 || dy==0) return ;

		// 転送元の回転中心
		bx = cast(int)(bx*rate);
		by = cast(int)(by*rate);

		//	転送後の座標を計算する
//		int nSin = SinTable.get().sin(rad);
//		int nCos = SinTable.get().cos(rad);
		int nSin = sinTable.sin(rad);;
		int nCos = sinTable.cos(rad);

		Point[4] dstPoints;

		//	0.5での丸めのための修正項 →　0x8000
		int px = x + bx;
		int py = y + by;
		alias round!(int) round_int;
		int ax0 = - bx;
		int ay0 = - by;
		dstPoints[0].x = round_int.RShift(ax0 * nCos - ay0 * nSin,16)+px;
		dstPoints[0].y = round_int.RShift(ax0 * nSin + ay0 * nCos,16)+py;
		int ax1 = dx - bx;
		int ay1 =	 - by;
		dstPoints[1].x = round_int.RShift(ax1 * nCos - ay1 * nSin,16)+px;
		dstPoints[1].y = round_int.RShift(ax1 * nSin + ay1 * nCos,16)+py;
		int ax2 = dx - bx;
		int ay2 = dy - by;
		dstPoints[2].x = round_int.RShift(ax2 * nCos - ay2 * nSin,16)+px;
		dstPoints[2].y = round_int.RShift(ax2 * nSin + ay2 * nCos,16)+py;
		int ax3 =	 - bx;
		int ay3 = dy - by;
		dstPoints[3].x = round_int.RShift(ax3 * nCos - ay3 * nSin,16)+px;
		dstPoints[3].y = round_int.RShift(ax3 * nSin + ay3 * nCos,16)+py;
		//	変数無駄な代入が多いが、最適化されるかなぁ(´Д｀)

		src.blt(getDrawContext(),srcRect,dstPoints);
	}

	///	テクスチャーのバインドを解除する
	/**
		Texture.bind();
		でバインドしていたテクスチャーをunbindする。
	*/
	void	unbind(){
		glBindTexture(GL_TEXTURE_2D,0);
	}

	///	ブレンドを無効化する
	/**
		blt メソッド等で描画するときのblendを無効化する。
		(ディフォルトでは無効)
	*/
	void	disableBlend(){
		if (bBlend) {
			bBlend = false;
			glDisable(GL_BLEND);
		}
	}

	///	ブレンドを有効にする
	/**
		blt メソッド等で描画するときのblendを有効にする。
		(ディフォルトでは無効)<BR>

		無効にするには disableBlend を呼び出すこと。

	<PRE>
		glBlendFunc(srcFunc,dstFunc);
		//	転送元に適用する関数 , 転送先に適応する関数

		例)
		glBlendFunc(GL_DST_COLOR,GL_ZERO);
		ならば、
			転送先のピクセルの値
				＝　転送元のピクセル×転送先のピクセル　＋　０
		となり、転送元ピクセルで抜いたような形になる。

		指定できる定数は、以下の通り。
			定数 (fR,fG,fB,fA)
		sd	GL_ZERO (0,0,0,0)
		sd	GL_ONE (1,1,1,1)
		 d	GL_SRC_COLOR (Rs/kR ,Gs/kG,Bs/kB,As/kA)
		s	GL_ONE_MINUS_SRC_COLOR (1,1,1,1)-(Rs/kR,Gs/kG,Bs/kB,As/kA)
		s	GL_DST_COLOR (Rd/kR,Gd/kG,Bd/kB,Ad/kA)
		sd	GL_ONE_MINUS_DST_COLOR (1,1,1,1)-(Rd/kR,Gd/kG,Bd/kB,Ad/kA)
		sd	GL_SRC_ALPHA (As/kA,As/kA,As/kA,As/kA)
		sd	GL_ONE_MINUS_SRC_ALPHA (1,1,1,1)-(As/kA,As/kA,As/kA,As/kA)
		sd	GL_DST_ALPHA (Ad/kA,Ad/kA,Ad/kA,Ad/kA)
		sd	GL_ONE_MINUS_DST_ALPHA (1,1,1,1)-(Ad/kA,Ad/kA,Ad/kA,Ad/kA)
		s	GL_SRC_ALPHA_SATURATE (i,i,i,1)

		s : source(転送元) , d : destination(転送先)
		kR,kG,kB,kA : 255(RGBA8888のとき)
		i : min(As, kA-Ad) / kA

			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		なら、
			転送先　＝
				転送元の値×転送元のα　＋　転送先の値×（１－転送元のα）
		となり、いわゆる普通のαブレンドになる

	ブレンディングを有効にしていないときは、常にソースの色と転送先の色とは
	同じなので、glBlendFunc(GL_ONE, GL_ZERO)と同じ結果になる。
	ただしドライバの最適化により動作速度には違いが出る可能性があるので、
	ブレンディングが必要なければglDisable(GL_BLEND)で明示的に無効化すべき。
	</PRE>
	*/
	void	enableBlend(){
		if (!bBlend) {
			glEnable(GL_BLEND);
			bBlend = true;
		}
	}

	///	ブレンドとしてaddColorを指定する
	/**
		blt メソッド等で描画するときのblendを有効にする。<BR>
		無効にするには disableBlend を呼び出すこと。

		ブレンド方法としては、addColor(加色合成)を指定する。
		すなわち、glBlendFunc(GL_ONE,GL_ONE);を行なっている。<BR>

		内部的には、
<PRE>
		enableBlend();
		glBlendFunc(GL_ONE,GL_ONE);
</PRE>
		を行なっている。

		α付きサーフェースでなくともaddColorは出来るので、
		これを使って転送すると高速化を図れることがある。<BR>

		光の表現をするときなどに使うと良い。<BR>

		http://www.platz.or.jp/~moal/ablend.html
		が詳しい。
	*/
	void	blendAddColor() {
		enableBlend();
		glBlendFunc(GL_ONE,GL_ONE);
	}

	///	転送元がα付きサーフェースのときのaddcolor
	/**
		blendAddColor の α付きサーフェースのとき用。<BR>

		内部的には
<PRE>
		enableBlend();
		glBlendFunc(GL_SRC_ALPHA,GL_ONE)
</PRE>
		を行なっている。
	*/
	void	blendAddColorAlpha() {
		enableBlend();
		glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	}

	///	ブレンドとしてsubColorを指定する
	/**
		blt メソッド等で描画するときのblendを有効にする。<BR>
		これを使って転送すると高速化を図れることがある。<BR>

		ブレンド方法としては、subColor(減色合成)を指定する。
		すなわち、glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR);を行なっている。

		内部的には、
<PRE>
		enableBlend();
		glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR);
</PRE>
		を行なっている。

		α付きサーフェースでなくともsubColorは出来るので、
		これを使って転送すると高速化を図れることがある。<BR>

		http://www.platz.or.jp/~moal/subtract.html
		が詳しい。
	*/
	void	blendSubColor() {
		enableBlend();
		glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR);
	}

	///	転送元がα付きサーフェースのときのsubcolor
	/**
		blendSubColor の α付きサーフェースのとき用。<BR>

		内部的には
<PRE>
		enableBlend();
		glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_ALPHA);
</PRE>
		を行なっている。
	*/
	void	blendSubColorAlpha() {
		enableBlend();
		glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_ALPHA);
	}

	///	ブレンドとして、αブレンドを指定する
	/**
		blt メソッド等で描画するときのblendを有効にする。<BR>
		ブレンド方法として、
<PRE>
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
</PRE>
		を指定している。

		これにより、転送元のα値により、αブレンドが行なわれる。
		テクスチャーのカラーキー付き転送も、実際はカラーキーの部分のα値を
		0にすることにより実現しているので、この転送方式を用いないと、
		カラーキー付き転送は実現できない。
	*/
	void	blendSrcAlpha() {
		enableBlend();
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}

	/// openGLを用いた、簡単な文字列の描画
	/**
		対応しているのは、数字とアルファベットのみ。
		openGLは初期化済みと仮定している。<BR>

		文字列を描画していく。指定された座標(x,y)から、指定されたsizeで
		右側に向かって描画していく。2D polygonを使用。<BR>

		簡単なデバッグ用だと想定している。
	<PRE>
		screen.setLineWidth(2);
		screen.drawString("0123456789",0,0,10);
	</PRE>
		もし必要ならば、変換行列を設定して、回転・移動を行なえば良い。
	*/
	void	drawString(char[] txt,int x,int y,int size){
		//	表示に対応している文字
		static char[] moji = "0123456789"
						"ABCDEFGHIJKLMNOPQRSTUVWXYZ";

		static ushort[] mojiData = [
		/*
			 ---	 1
			|	|  2   3		8(＼) ・ 9(／)
			 ---	 4
			|	|  5   6		10(＼)・11(／)
			 ---	 7
		*/
			0b00001110111,0b00000100100,0b00001011101,0b00001101101, // 0,1,2,3
			0b00000101110,0b00001101011,0b00001111011,0b00000100101, // 4,5,6,7
			0b00001111111,0b00000101111,0b00001111101,0b00001111010, // 8,9,a,b
			0b00001010011,0b00001111100,0b00001011011,0b00000011011, // c,d,e,f
			0b01001111011,0b00000111110,0b00000100100,0b00001100100, // g,h,i,j
			0b01100010010,0b00001010010,0b00110110111,0b00000111000, // k,l,m,n
			0b00000001111,0b00000011111,0b00100100111,0b01100010011, // o,p,q,r
			0b00001101011,0b00001011010,0b00001110000,0b01000100000, // s,t,u,v
			0b11000110000,0b11000000000,0b00001101110,0b10001001000, // w,x,y,z
		];

		txt = toupper(txt);
//		float lineWidth = glGet(GL_LINE_WIDTH);
//		glLineWidth((cast(float)size)/6 + 1);

		foreach(char c;txt){
			int f = std.string.find(moji,c);
			if (f==-1) goto next;

			ushort u = mojiData[f];

			if (u & 1) drawLine(x,y,x+size,y);
			if (u & 2) drawLine(x,y,x,y+size/2);
			if (u & 4) drawLine(x+size,y,x+size,y+size/2);
			if (u & 8) drawLine(x,y+size/2,x+size,y+size/2);
			u>>=4;
			if (u & 1) drawLine(x,y+size/2,x,y+size);
			if (u & 2) drawLine(x+size,y+size/2,x+size,y+size);
			if (u & 4) drawLine(x,y+size,x+size,y+size);
			u>>=3;
			if (u & 1) drawLine(x,y,x+size,y+size/2);
			if (u & 2) drawLine(x+size,y,x,y+size/2);
			if (u & 4) drawLine(x,y+size/2,x+size,y+size);
			if (u & 8) drawLine(x+size,y+size/2,x,y+size);

		next:;
			x += size*1.3;
		}
//		glLineWidth(lineWidth);
	}

	///	drawLine,drawStringの線の太さをピクセル単位で指定する。
	void	setLineWidth(float u){
		//	現在のcontextに即して変換する必要がある
		DrawContext dc = getDrawContext();

		glLineWidth(u*dc.rateX);
		//	x方向だけ考慮に入れておけば良いか．．
	}

	///	描画用のコンテクストを取得
	/**
		値を変更すると、その変更した値に従って描画される。
	*/
	DrawContext getDrawContext() { return drawContext; }

	///	描画用のコンテクストの設定
	/**
		この変更した値に従って描画される。
		以前のsetDrawContextで設定されていた値を返す。
		(いらなければ使う必要はない)
	*/
	DrawContext setDrawContext(DrawContext v) {
		DrawContext old = drawContext;
		drawContext = v;
		return old;
	}

	//--- 以下、3D描画に必要なもの(あとまわし)

	///	3D描画を有効にする
	/**
		3D描画(depthバッファを必要とするような描画)を行なう場合は、
		setVideoModeの前にこれを実行しておくこと。<BR>

		※　必要もしないのに depthバッファつきのサーフェースを
		要求してサーフェース作成に失敗し、プログラムが動作しない事態を
		回避するため、3D機能を用いるときに明示的に指定するような
		仕様にしました。

		default:disable状態
	*/
	void	enable3D(){
		use3d_rendering = true;
	}

	///	3D描画を無効にする(default)
	void	disable3D(){
		use3d_rendering = false;
	}
	
	SDL_Surface* getScreenSurface() {
		return surface;
	}
	
	/// フレームバッファをテクスチャにコピーする
	Texture backup(bool blend_=false) {
		
		Texture t1 = new Texture();
		Texture t2 = new Texture();

		if (!bTestScreenSuccess) {
			// スクリーンが完成していないのに呼びださんでほしい...
			return t1;
		}
		
		if (!blend_) {
			disableBlend();
		}
		
		Surface s1 = new Surface();
		Surface s2 = new Surface();
		s1.createDIB(screenX, screenY, false);
		t1.setSurface( s1 );
		s2.createDIB(screenX, screenY, false);
		t2.setSurface( s2 );

		t1.bind();
		
		glCopyTexSubImage2D(
			t1.getTextureType(),
			0,
			0,
			0,
			0,
			0,
			screenX,
			screenY);

		t1.unbind();
		blt(t1,0,0);
		
		t2.bind();

		glCopyTexSubImage2D(
			t2.getTextureType(),
			0,
			0,
			0,
			0,
			0,
			screenX,
			screenY);
			
		t2.unbind();

		blt(t2,0,0);
		
		return t2;
	}

	this() {
		sinTable = SinTable.get();
		drawContext = new DrawContext;
	}

protected:
	DrawContext	drawContext;	//!<	描画用のコンテクスト
	bool bTestScreen;			//!<	スクリーンテスト中か
	bool bTestScreenSuccess;	//!<　	スクリーンテストに成功した。
	char[] strCaption;			//!<	ウィンドゥキャプション
	int screenX,screenY;		//!<	スクリーンのサイズ
	int	offsetX,offsetY;		//!<	スクリーンのオフセット値
	bool	bBlend;				//!<	ブレンドモードか
	Color4ub	color;			//!<	setColorで設定されている色
	SDL_Surface* surface;		//!<	surface
	bool	use3d_rendering;	//!<	3Dレンダリング等を使うときはこちら
	SinTable sinTable;

	///	視体積の設定(こちらは内部的に使用される)
	void	setView(){
		setView(offsetX,offsetY,offsetX+screenX,offsetY+screenY);
	}

private:
	//	これ、privateにしないといけないのだが、
	//	privateにすると、この↓テンプレートのthisが見えなくなるらしく、
	//	他のmoduleで、このScreenクラスを派生させるとコンパイルエラーになる
	//	(コンパイラのバグ)
	singleton!(SDLVideoInitializer) initializer;
	static class SDLVideoInitializer {
		y4d_result init(){
			if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
				return y4d_result.SDL_error;
			success = true;
			return y4d_result.no_error;
		}
		~this() {
			if (success) {
				SDL_QuitSubSystem(SDL_INIT_VIDEO);
				Log.print("SubSystem Quit");
			}
		}
	private:
		bool success;
	}
}

static this() {} // for D's bug
