/************************************************************************/
/* 基本グラフィックス描画ルーチン                                       */
/************************************************************************/

/* 基本グラフィックス描画ルーチンヘッダ */
#include "graphics.h"

/* LCDパネル初期化ルーチンヘッダ */
#include "LCDpanel.h"


/* グラフィックス初期化 */
void GRPH_Initialize(void)
{
	/* LCDパネル初期化 */
	LCDpanel_Initialize();
	/* Setup Initial frame buffer address (GRA1) */
	GRPH_SelectPlane((PIXEL *)GRA1_VRAM_BASE);
}


/***** 低レベルグラフィックス描画関数 *****/


/* 画面クリア */
void GRPH_Clear(PIXEL c)
{
	int i;
	PIXEL *p;

	p=(PIXEL *)GRPH_Buffer;
	for(i=0;i<GRPH_width*GRPH_height;i++){
		*p++=c;
	}
}

void GRPH_Copy(PIXEL *p1, PIXEL *p2)
{
	int i;

	for (i = 0; i < GRPH_width * GRPH_height; i++)
		*p1++ = *p2++;
}


/* 点描画 */
void GRPH_Pset(int x, int y, PIXEL c)
{
	PIXEL *p;

	if ((x >= GRPH_clip_window_sx) && (y >= GRPH_clip_window_sy) &&
	    (x <= GRPH_clip_window_ex) && (y <= GRPH_clip_window_ey))
	{
		p=(PIXEL *)(GRPH_Buffer) + x + y * GRPH_width;
		*p = c;
	}
}


int abs(int i)
{
	return i<0 ? -i : i;
}

int sign(int i)
{
	return i<0 ? -1 : 1;
}

int ClipLine(int sx, int sy, int ex, int ey, 
			  int *clippedSx, 
			  int *clippedSy, 
			  int *clippedEx, 
			  int *clippedEy)
{
	int	tempVal;

/* Y増加方向への描画に正規化 */

	if (sy > ey)
	{
		*clippedSx = ex;
		*clippedSy = ey;
		*clippedEx = sx;
		*clippedEy = sy;
	}
	else
	{
		*clippedSx = sx;
		*clippedSy = sy;
		*clippedEx = ex;
		*clippedEy = ey;
	}
	if (*clippedEy < GRPH_clip_window_sy) return 0;	/* 全て画面外 */
	if (*clippedSy > GRPH_clip_window_ey) return 0;	/* 全て画面外 */

/* clippedSyが描画範囲外(<GRPH_clip_window_sy)の場合、
   (0,GRPH_clip_window_sy)のY軸との交点を、
   新しい(ClippedSx,ClippedSy)とする*/

	if (*clippedSy < GRPH_clip_window_sy)
	{
		if (sy != ey)
		{
			*clippedSx = (sx-ex)*(GRPH_clip_window_sy-sy)/(sy-ey)+sx;
		}
		else
		{
			/* sy = ey の場合、Devide by 0になるのでその対策 */
			*clippedSx = ((sx-ex)*(GRPH_clip_window_sy-sy)>=0 ? 32767 : -32768);
		}
		*clippedSy = GRPH_clip_window_sy;
	}

/* clippedEyが描画範囲外(>GRPH_clip_window_ey)の場合、
   (0,GRPH_clip_window_ey)のY軸との交点を、
   新しい(ClippedEx,ClippedEy)とする*/

	if (*clippedEy > GRPH_clip_window_ey)
	{
		if (sy != ey)
		{
			*clippedEx = (sx-ex)*(GRPH_clip_window_ey-sy)/(sy-ey)+sx;
		}
		else
		{
			/* sy = eyの場合、Devide by 0になるのでその対策 */
			*clippedEx = ((sx-ex)*(GRPH_clip_window_sy-sy)>=0 ? 32767 : -32768);
		}
		*clippedEy = GRPH_clip_window_ey;
	}

/* X増加方向への描画に正規化 */
	if (*clippedSx > *clippedEx)
	{
		tempVal = *clippedSx;
		*clippedSx = *clippedEx;
		*clippedEx = tempVal;

		tempVal = *clippedSy;
		*clippedSy = *clippedEy;
		*clippedEy = tempVal;
	}

	if (*clippedEx < GRPH_clip_window_sx) return 0;	/* 全て画面外 */
	if (*clippedSx > GRPH_clip_window_ex) return 0;	/* 全て画面外 */

/* clippedSxが描画範囲外(<GRPH_clip_window_sx)の場合、
   (GRPH_clip_window_sx,0)のX軸との交点を、
   新しい(ClippedSx,ClippedSy)とする */

	if (*clippedSx < GRPH_clip_window_sx)
	{
		if (sx != ex)
		{
			*clippedSy = (sy-ey)*(GRPH_clip_window_sx-sx)/(sx-ex)+sy;
		}
		else
		{
			/* sx = exの場合、Devide by 0になるのでその対策 */
			*clippedSy = ((sy-ey)*(GRPH_clip_window_sx-sx)>=0 ? 32767 : -32768);
		}
		*clippedSx = GRPH_clip_window_sx;
	}

/* clippedExが描画範囲外(>=GRPH_clip_window_ex)の場合、
   (GRPH_clip_window_ex, 0)のX軸との交点を
   新しい(ClippedSx,ClippedSy)とする*/

	if (*clippedEx > GRPH_clip_window_ex)
	{
		if (sx != ex)
		{
			*clippedEy = (sy-ey)*(GRPH_clip_window_ex-sx)/(sx-ex)+sy;
		}
		else
		{
			/* sx = exの場合、Devide by 0になるのでその対策 */
			*clippedEy = ((sy-ey)*(GRPH_clip_window_ex-sx)>=0 ? 32767 : -32768);
		}
		*clippedEx = GRPH_clip_window_ex;
	}

	if (*clippedSy < *clippedEy)
	{
		if (*clippedEy < GRPH_clip_window_sy) return 0;
	}
	else
	{
		if (*clippedSy > GRPH_clip_window_ey) return 0;
	}

	return 1;

}

void _HorizontalLine(int sx, int sy, int width, PIXEL c)
{
	PIXEL	*p;

	p=(PIXEL *)(GRPH_Buffer) + sx + sy * GRPH_width;
	for (;width>0;width--)
	{
		*(p++) = c;
	}
}

void _VerticalLine(int sx, int sy, int height, PIXEL c)
{
	PIXEL	*p;

	p=(PIXEL *)(GRPH_Buffer) + sx + sy * GRPH_width;
	for (;height>0;height--)
	{
		*p = c;
		p += GRPH_width;
	}
}


void HorizontalLine(int sx, int sy, int ex, PIXEL c)
{

	int clippedSx, clippedEx;

	if (sy < GRPH_clip_window_sy) return;
	if (sy > GRPH_clip_window_ey) return;
	if (sx > ex)
	{
		clippedSx = ex;
		clippedEx = sx;
	}
	else
	{
		clippedSx = sx;
		clippedEx = ex;
	}
	if (clippedEx < GRPH_clip_window_sx) return;
	if (clippedSx > GRPH_clip_window_ex) return;

	if (clippedSx < GRPH_clip_window_sx) clippedSx = GRPH_clip_window_sx;
	if (clippedEx > GRPH_clip_window_ex) clippedEx = GRPH_clip_window_ex;

	_HorizontalLine(clippedSx, sy, clippedEx - clippedSx + 1, c);
}

void VerticalLine(int sx, int sy, int ey, PIXEL c)
{

	int clippedSy, clippedEy;

	if (sx < GRPH_clip_window_sx) return;
	if (sx > GRPH_clip_window_ex) return;
	if (sy > ey)
	{
		clippedSy = ey;
		clippedEy = sy;
	}
	else
	{
		clippedSy = sy;
		clippedEy = ey;
	}
	if (clippedEy < GRPH_clip_window_sy) return;
	if (clippedSy > GRPH_clip_window_ey) return;

	if (clippedSy < GRPH_clip_window_sy) clippedSy = GRPH_clip_window_sy;
	if (clippedEy > GRPH_clip_window_ey) clippedEy = GRPH_clip_window_ey;

	_VerticalLine(sx, clippedSy, clippedEy - clippedSy + 1, c);
}


/* ライン描画 */
void GRPH_Line(int sx, int sy, int ex, int ey, PIXEL c)
{
	int clippedSx, clippedSy, clippedEx, clippedEy;
	unsigned int remainX , remainY;
	unsigned int remainderMax , remainderMin, remainder;
	unsigned int remainingPixels;
	int	dX, dY;
	int	dXY1, dXY2;
	PIXEL	*p;

/* 描画範囲部分の線分のみにする*/

	if (ClipLine(sx, sy, ex, ey, &clippedSx, &clippedSy, &clippedEx, &clippedEy)==0) return;

/* 水平線の場合 */
	if (clippedSy == clippedEy)
	{
		HorizontalLine(clippedSx, clippedSy, clippedEx, c);
		return;
	}

/* 垂直線の場合 */
	if (clippedSx == clippedEx)
	{
		VerticalLine(clippedSx, clippedSy, clippedEy, c);
		return;
	}


/* 描画方向の決定と初期化 */

	/* X方向 */
	if (clippedSx > clippedEx)
	{
		dX = -1;
		remainX = clippedSx - clippedEx;
	}
	else
	{
		dX = 1;
		remainX = clippedEx - clippedSx;
	}

	/* Y方向 */
	if (clippedSy > clippedEy)
	{
		dY = -GRPH_width;
		remainY = clippedSy - clippedEy;
	}
	else
	{
		dY = GRPH_width;
		remainY = clippedEy - clippedSy;
	}

	/* 線の傾き45度を境に、X軸Y軸のどちらをベースに描画するか決定 */
	if (remainX > remainY)
	{
		remainderMax = remainX;
		remainderMin = remainY;
		remainingPixels = remainX;
		dXY1 = dX;
		dXY2 = dY;
	}
	else
	{
		remainderMax = remainY;
		remainderMin = remainX;
		remainingPixels = remainY;
		dXY1 = dY;
		dXY2 = dX;
	}

	/* +0.5して、ピクセルの中心で計算する*/
	remainder = remainderMax >> 1;

	/* ライン描画 */
	p=((PIXEL *)(GRPH_Buffer)) + clippedSx + clippedSy * GRPH_width;

	for (;;)
	{
		*p=c;
		if (remainingPixels == 0) break;
		remainingPixels--;
		p += dXY1;
		if (remainder < remainderMin)
		{
			remainder += remainderMax;
			p += dXY2;
		}
		remainder -= remainderMin;
	}
}


/* ボックス描画(fが1ならボックスフィル) */
void GRPH_Box(int sx, int sy, int ex, int ey, PIXEL c, int f)
{

	int leftX, topY;
	int rightX, bottomY;
	int y;

/* 各コーナーの位置を決定 */

	if (sx > ex)
	{
		leftX = ex;
		rightX = sx;
	}
	else
	{
		leftX = sx;
		rightX = ex;
	}

	if (sy > ey)
	{
		topY = ey;
		bottomY = sy;
	}
	else
	{
		topY = sy;
		bottomY = ey;
	}

	if (f)
	{
		for (y = topY ; y <= bottomY; y++)
		{
			HorizontalLine(leftX, y, rightX, c);
		}
	}
	else
	{
		HorizontalLine(leftX, topY, rightX, c);
		for (y = topY+1; y < bottomY; y++)
		{
			GRPH_Pset(leftX, y, c);
			GRPH_Pset(rightX, y, c);
		}
		HorizontalLine(leftX, bottomY, rightX, c);
	}
}


/* サークル描画(fが1ならサークルフィル) */
void GRPH_Circle(int x, int y, int rh, int rv, PIXEL c, int f)
{
	int	dx, dy, dr, dr1, dr2, dr3;
	int	absDR1, absDR2, absDR3;

	int	rv2, rh2;

	if (rv == rh)
	{
	/* 真円の描画 */
	/*
		座標計算は、(0, R)から(R, 0)まで本来の半径Rとの誤差が最小の方向に進む
		但し、円の対称を利用し45度の円弧の座標計算のみで描画する

		現在の座標(X,Y)、次の座標への差分(dx,dy), 本来の半径Rとすると

		現在の座標の半径の誤差Eは以下で表される
			E = X^2 + Y^2 - R^2

		次の座標の誤差Enextは、以下になる
			Enext = (X+dx)^2 + (Y+dy) -R^2
				  = X^2 + 2Xdx+ dx^2 + Y^2 + 2Ydy + dy^2 - R^2
				  = E + 2Xdx + 2Ydy + dx^2 + dy^2
		これより、次の進む可能性のある三つの候補の座標の半径の誤差を求める
			E(x+1, y  ) = E + 2x + 1
			E(x  , y-1) = E - 2y + 1
			E(x+1, y-1) = E + 2x - 2y + 2
		この三つの誤差から絶対値で一番小さいものを次の座標とする。
	*/

		dx = 0;
		dy = rv;
		dr = 0;
		for(;;)
		{
			/*円の対称性を利用し一つの座標から8つの点の座標を求める*/
			if (f)
			{

				HorizontalLine(x + dx, y + dy, x - dx, c);
				HorizontalLine(x + dx, y - dy, x - dx, c);
				HorizontalLine(x + dy, y + dx, x - dy, c);
				HorizontalLine(x + dy, y - dx, x - dy, c);
			}
			else
			{
				GRPH_Pset(x + dx, y + dy, c);
				GRPH_Pset(x + dx, y - dy, c);
				GRPH_Pset(x - dx, y + dy, c);
				GRPH_Pset(x - dx, y - dy, c);

				GRPH_Pset(x + dy, y + dx, c);
				GRPH_Pset(x + dy, y - dx, c);
				GRPH_Pset(x - dy, y + dx, c);
				GRPH_Pset(x - dy, y - dx, c);
			}

			/*円弧8分の1(45度)を描画したら終了*/
			if (dx >= dy) break;

			absDR1 = dr1 = dr + 2*dx + 1;
			absDR2 = dr2 = dr - 2*dy + 1;
			absDR3 = dr3 = dr + 2*dx - 2*dy + 2;

			if (absDR1 < 0) absDR1 = - absDR1;
			if (absDR2 < 0) absDR2 = - absDR2;
			if (absDR3 < 0) absDR3 = - absDR3;

			if (absDR1 < absDR2)
			{
				if (absDR1 < absDR3)
				{
					++dx;
					dr = dr1;
				}
				else
				{
					++dx;
					--dy;
					dr = dr3;
				}
			}
			else
			{
				if (absDR2 < absDR3)
				{
					--dy;
					dr = dr2;
				}
				else
				{
					++dx;
					--dy;
					dr = dr3;
				}
			}
		}
	}
	else
	{
	/* 楕円の描画 */
	/*
		座標計算は、(0, rv)から(rh, 0)まで本来の半径Rとの誤差が最小の方向に進む
		但し、楕円対称を利用し90度の円弧の座標計算のみで描画する

		現在の座標(X,Y)、次の座標への差分(dx,dy), 本来の半径Rとすると

		楕円の式
			x^2/rh^2 + y^2/rv^2 = 1
		より、現在の座標の半径の誤差Etは以下で表される
			Et = X^2/rh^2 + Y^2/rv^2 - 1
			Et*rh^2*rv^2 = rv^2*x^2+rh^2*y^2-rh^2*rv^2
		但し、rvとrhは固定値なので、係数としてまとめ誤差Eとし計算しやすくし、
		誤差の式は、
			E = rv^2*x^2+rh^2*y^2-rh^2*rv^2
		となる。
		次の座標の誤差Enextは、以下になる
			Enext = rv^2*(X+dx)^2 + rh^2*(Y+dy) -rh^2*rv^2
				  =   rv^2*X^2 + rv^2*2Xdx + rv^2*dx^2
				    + rh^2*Y^2 + rh^2*2Ydy + rh^2*dy^2
				    - rh^2*rv^2
				  = E + rv^2(2Xdx+dx^2) + rh^2(2Ydy+dy^2)
		これより、次の進む可能性のある三つの候補の座標の半径の誤差を求める
			E(x+1, y  ) = E + rv^2(2X+1)
			E(x  , y-1) = E - rh^2(2Y-1)
			E(x+1, y-1) = E + rv^2(2X+1) - rh^2(2Y-1)
		この三つの誤差から絶対値で一番小さいものを次の座標とする。
	*/

		rv2 = rv * rv;
		rh2 = rh * rh;
		dx = 0;
		dy = rv;
		dr = 0;
		for(;;)
		{
			/*楕円の対称性を利用し一つの座標から4つの点の座標を求める*/
			if (f)
			{
				HorizontalLine(x + dx, y + dy, x - dx, c);
				HorizontalLine(x + dx, y - dy, x - dx, c);
			}
			else
			{
				GRPH_Pset(x + dx, y + dy, c);
				GRPH_Pset(x + dx, y - dy, c);
				GRPH_Pset(x - dx, y + dy, c);
				GRPH_Pset(x - dx, y - dy, c);
			}

			if ((dx == rh) && (dy == 0)) break;

			absDR1 = dr1 = dr + 2*dx*rv2 + rv2;
			absDR2 = dr2 = dr - 2*dy*rh2 + rh2;
			absDR3 = dr3 = dr + 2*dx*rv2 + rv2 - 2*dy*rh2 + rh2;

			if (absDR1 < 0) absDR1 = - absDR1;
			if (absDR2 < 0) absDR2 = - absDR2;
			if (absDR3 < 0) absDR3 = - absDR3;

			if (absDR1 < absDR2)
			{
				if (absDR1 < absDR3)
				{
					++dx;
					dr = dr1;
				}
				else
				{
					++dx;
					--dy;
					dr = dr3;
				}
			}
			else
			{
				if (absDR2 < absDR3)
				{
					--dy;
					dr = dr2;
				}
				else
				{
					++dx;
					--dy;
					dr = dr3;
				}
			}
		}
	}
}

/* ペイント描画 */

/* Pain sub (再帰的に呼ばれる) */
void PaintALine(int x, int y, PIXEL c, PIXEL bc)
{
	PIXEL	*leftscanp, *rightscanp;
	int		sx, ex;

	/* 左右方向に指定色をScanする */
	leftscanp = rightscanp = (PIXEL *)(GRPH_Buffer) + x + y * GRPH_width;
	
	/* 指定位置(x,y)は、bcと同色が前提なので、
	   その部分のチェックは省き、(x-1, y)と(x+1,y)からscanする。
	*/
	leftscanp--;
	for (sx = x-1; sx >= GRPH_clip_window_sx ; sx--)
	{
		if (*leftscanp != bc) break;
		leftscanp--;
	}
	leftscanp++;
	sx++;

	rightscanp++;
	for (ex = x+1; ex <= GRPH_clip_window_ex; ex++)
	{
		if (*rightscanp != bc) break;
		rightscanp++;
	}
	ex--;

	/* Scan結果を元に、指定色の部分を塗りつぶす */
	HorizontalLine(sx, y, ex, c);

	/* 上部ライン(sx, y-1) to (ex, y-1)と
	   下部ライン(sx, y+1) to (ex, y+1)の指定色部分を探し、
	   そこを起点として再びペイント開始する*/
	for (; sx <= ex ; sx++)
	{
		if (y > GRPH_clip_window_sy)
		{
			if (*(leftscanp-GRPH_width) == bc) PaintALine(sx, y-1, c, bc);
		}
		if (y < GRPH_clip_window_ey)
		{
			if (*(leftscanp+GRPH_width) == bc) PaintALine(sx, y+1, c, bc);
		}
		leftscanp++;
	}
}

/* Paint Main */
void GRPH_Paint(int x, int y, PIXEL c)
{
	PIXEL	backcolor;

	if ((x < GRPH_clip_window_sx) ||
	    (x > GRPH_clip_window_ex) ||
	    (y < GRPH_clip_window_sy) ||
	    (y > GRPH_clip_window_ey)) return;
	backcolor = *((PIXEL *)(GRPH_Buffer) + x + y * GRPH_width);
	PaintALine(x, y, c, backcolor);
	return;
}



