#include <stdio.h>
#include <math.h>
#include <eggx.h>

// ============================ EGGX

int win;

#define WW (400)
#define HH (600)

#define COL 6 // 並んでいるブロックの列数
#define RAW 2 // 同、行数
#define BKSIZE 20.0 // ブロックのサイズ
#define BASIZE 5.0 // ボールのサイズ

int bkAlive[COL][RAW]; // ブロックの生死 (1:live, 0:dead)
float bkX[COL][RAW], bkY[COL][RAW], bkC[COL][RAW];

#define WALLNUM 5 // 壁がいくつの線分から出来ているか
float wallXY[WALLNUM+1][2]={
	{10, 20}, // 左下
	{WW/2, 10}, // 中央下
	{WW-11, 20}, // 右下
	{WW-11, HH-11}, // 右上
	{10, HH-11}, // 左上
	{10, 20}}; // 左下（戻ってきた）




// ========================== 二線分の交差チェック
/*
 http://www.hiramine.com/programming/graphics/2d_segmentintersection.html
 を参考に。
 線分 AB (ax,ay)-(bx,by) と CD (cx,cy)-(dx,dy) が交差しているかどうかを調べる
 r, s が共に 0-1 の間にあれば衝突と判定
 ただし、
 戻り値は衝突時は r そのもの。範囲は 0.0 <= r <= 1.0 である。
 非衝突時は 9.0 を返す。
 ただし、二線分が並行な場合は 99.0 であり、
 二線分が同一（重なっている）の場合は 999.0 を返す
 この r を用いて交差座標位置を求める
 */

#define NOCROSS  9.0
#define PARALLEL 99.0
#define DOUBLE   999.0

float colCheck(float ax, float ay,
			   float bx, float by,
			   float cx, float cy,
			   float dx, float dy)
{
	float denom; // 途中計算における分母(denominator)
	float acx, acy; // ベクター AC を表現する
	float r, s;
	
	denom = (bx - ax) * (dy - cy) - (by - ay) * (dx - cx);
	if( denom == 0.0 ) return PARALLEL; // 二線は並行である

	// ベクター AC を得る（C - A にあたる） 
	acx = cx - ax; acy = cy - ay;
	
	r=( ( dy - cy ) * acx - ( dx - cx ) * acy ) / denom;
	s=( ( by - ay ) * acx - ( bx - ax ) * acy ) / denom;
	
	if( (r == 0.0)&&(s == 0.0)) return DOUBLE; // 二線は同一である
	
	if( (0.0 <= r) && (r <= 1.0) && (0.0 <= s) && (s <= 1.0)) {
		return r; // 交差した
	} else {
		return NOCROSS; // 交差しない
	}
		
}

// 線分 A, B から角を出す
float theta(float ax, float ay, float bx, float by)
{
	return atan2f(by-ay, bx-ax);
}

// 反射角を得る（壁CDに対してABが反射する場合）
float bounceRect(float ax, float ay,
				 float bx, float by,
				 float cx, float cy,
				 float dx, float dy)
{
	float th0, th1, th2;
	th1=theta(ax, ay, bx, by);
	th2=theta(cx, cy, dx, dy);
	th0=th1 - th2;
	return M_PI - th0 + th2 + M_PI; // 反射角
}


// ============================================ ゲーム関連

// 指定された位置、色に線を引く
void drawLine(float ax, float ay, float bx, float by, int color)
{
	newpen(win, color);
	drawline(win, ax, ay, bx, by);
}

// 壁を描く
void drawWall()
{
	int i;
	for(i=0; i<WALLNUM; i++) {
		drawLine(wallXY[i][0], wallXY[i][1], 
				 wallXY[i+1][0], wallXY[i+1][1], 
				 2); // 色番号はここにハードコードする
	}
}

// 指定された位置にボールを描く
void drawBall(float x, float y)
{
	newpen(win, 1); // 色番号はここにハードコードする
	drawcirc(win, x, y, BASIZE, BASIZE);
}


// ========================== 補助サブルーチン群

// 初期設定
void setup(void)
{
	win=gopen(WW, HH);
	winname(win, "Collision test");
	layer(win, 0, 1);  /* 表示は 0 番、描画は 1 番レイヤーで */
	drawWall();
	copylayer(win, 1, 0); /* レイヤー 1 番の内容を 0 にコピー */
}

// 終了処理
void closeall(void)
{
	// ggetch(); // quit 関数があるので外す
	gclose(win);
}


// ============================================ メインルーチン


int main (int argc, const char * argv[]) {
	float ballx0, bally0; // ボールの現在の位置
	float ballx1, bally1; // ボールの現在の進行方向
	float ballang, ballspd; // 進行方向と速度
	int i, minI; // 壁など衝突した際のオブジェクトの番号（その最小ケースについても保存）
	float r, minR; // 衝突した際の距離比率（最小値についても保存）
	float isx, isy; // 交差位置（＝衝突位置）（intersection x,y)
	
	setup();

	ballx0=WW/2.0; bally0=30.0;
	ballang=M_PI_4; // ちょっと右
	ballspd=10.0; // 速度はここにハードコード
	
	while(1) {
		gclr(win);
		drawWall();
		// ボールの位置移動（仮）
		ballx1=ballx0+cos(ballang)*ballspd;
		bally1=bally0+sin(ballang)*ballspd;

		// 壁との衝突をチェック
		minR=NOCROSS;
		for(i=0; i<WALLNUM; i++) { // 全ての壁について個別にチェック
			r=colCheck(ballx0, bally0, ballx1, bally1,
					   wallXY[i][0],wallXY[i][1],wallXY[i+1][0],wallXY[i+1][1]);
			if(minR>r) { // より出発点に近いところで交差が見つかった
				minR=r; // より短い距離情報に更新
				minI=i; // そのときの壁番号も保持
			}
		}
		if(minR<NOCROSS) { // 最低一つは交差した
			//交点をボールの軌道から求める（現在地から r ぶんだけ進んだ位置）
			isx = ballx0 + minR * ( ballx1 - ballx0 );
			isy = bally0 + minR * ( bally1 - bally0 );
			// 反射角を得る（壁wallXY[minI]に対してballが反射する方向）
			ballang=bounceRect(ballx0, bally0, ballx1, bally1,
						  wallXY[minI][0],wallXY[minI][1],wallXY[minI+1][0],wallXY[minI+1][1]);
			// 交点 (isx, isy) から角 th 方向に微少に進んだ位置を次の位置とする）
			ballx1 = isx + 2.0 * cosf(ballang);
			bally1 = isy + 2.0 * sinf(ballang);
		}
		
		drawBall(ballx1, bally1); // 新しい位置でボールを描画
		copylayer(win, 1, 0); /* レイヤー 1 番の内容を 0 にコピー */
		ballx0=ballx1; bally0=bally1; // 位置情報を一世代進める
		msleep(40); 
	}
	
	closeall();
    return 0;
}
