module y4d_math.sintable;

private import std.math;
private import y4d_math.round;
private import ytl.exception;
private import ytl.singleton;


///	高速なsin,cos,atanを提供。
/**
	sin,cos,atanのテーブル処理して返します。高速なsin,cosが実現できます。
	0～511で一周（360゜）	結果は<<16されて返ります。

	singleton テンプレートでも使って、そこ経由でアクセスしてください。
*/
class SinTable {
public:
	///	cosを求める。n:0-511で一周。結果は<<16されて返る
	int	cos(int n) { return m_alCosTable[n & 511]; }

	///	sinを求める。n:0-511で一周。結果は<<16されて返る
	int	sin(int n) { return m_alCosTable[(n+384) & 511]; }

	///	cos(n)*r の整数部を返す。n:0-511で一周。(小数部丸めあり)
	int	cos(int n,int r) {
		int	l = cast(int)(y4d_math.round.round!(long).RShift(cast(long)cos(n) * r,16));
		return l;
	}

	///	sin(n)*r の整数部を返す。n:0-511で一周。(小数部丸めあり)
	int	sin(int n,int r) {
		long lng = cast(long)sin(n)*r;
		int	l = cast(int)(y4d_math.round.round!(long).RShift(cast(long)sin(n) * r,16));
		return l;
	}

	///	cos(n)を浮動小数で返す。n:0-511で一周。
	double cos_d(int n) { return cos(n)/cast(double)65536.0; }

	///	sin(n)を浮動小数で返す。n:0-511で一周。
	double sin_d(int n) { return sin(n)/cast(double)65536.0; }

	///	0.5+cos(n)*0.5を浮動小数で返す。n:0-511で一周。
	/**
		1からはじまり0との間をいったりきたりするのに便利。
	*/
	double cos1_0(int n) { return cos(n)/cast(double)65536.0/2 + 0.5; }

	///	1.0-cos1_0を返す関数。
	/**
		0からはじまり1との間をいったりきたりするのに使うと便利。
	*/
	double cos0_1(int n) { return 1.0-cos1_0(n); }

	///	arctanを求める。C言語のatan2と同じ。結果は0-65535(1周が65536)
	/**
		高速なArcTanを提供。C言語のatan2と同じ。
		結果は0-65535(1周が65536)

		(x,y)が(0,0)のときは Y4D_Error が throwされる
		throwされて嫌なら、事前にチェックしる！
	*/
	ushort	atan(int x,int y) 
	{
		if (x==0 && y==0)
			throw new y4d_error(this,cast(char[]) "atanで引数が(0,0)");
		if (y < 0) return cast(ushort) (atan0(-x,-y) + 0x8000);
		return atan0(x,y);
	}

	this(){
//		const double PI = 3.1415926535897932384626433832795;
		for(int i=0;i<512;++i){
			m_alCosTable[i] = cast(int)((std.math.cos(i*PI/256) * 65536));
		}
		for(int i=0;i<256;++i){
			m_alAtanTable[i] = cast(ushort)(std.math.atan((cast(double)i/256))*(65536/2)/PI);
		}
	}
	
	///	singletonオブジェクトを返すバージョン
	static	SinTable get() { return singleton!(SinTable).get(); }

private:

	///	cos table
	int		m_alCosTable[512];

	///	atan table
	ushort	m_alAtanTable[256];

	//	y>=0専用のAtan
	ushort	atan0(int x,int y) {
		if (x<0) return cast(ushort) (atan1(y,-x) + 0x4000);
		return atan1(x,y);
	}

	//	x>=0,y>=0専用のAtan
	ushort	atan1(int x,int y) {
		if (x==y) return 0x2000;
		if (y>x) return cast(ushort) (0x4000 - atan2(y,x));
		return atan2(x,y);
	}

	//	x>y , x>=0 , y>=0専用のAtan
	ushort	atan2(int x,int y) {
		if (x==0) return 0;
		ushort r = cast(ushort)(((cast(long)(y))<<8)/x);
		return m_alAtanTable[r];
		//	x>yより両辺をxで割って0<y/x<1。よって0<=(y<<8)/x<256
	}
}
