//purinさんに言われてマジックナンバーを駆逐した。
#include "Main.h"
#include "Collision.h"
#include <math.h>

CCollision::CCollision(POINT pt[], float offset_l, float offset_u, float offset_r, float offset_d, int col_type,CGameMain* game)
	:OffsetL(offset_l),OffsetU(offset_u),OffsetR(offset_r),OffsetD(offset_d),ColType(0){
	Game = game;
	ZeroMemory(mPt, sizeof(mPt));
	for (int i = 0; i < 8; i++) {
		mPt[i].x = pt[i].x;
		mPt[i].y = pt[i].y;
	}
}

//seg1,2（当たり判定）が線分、outT1,2が線分の内分比、outPosが交点（出力用）
bool CCollision::ColSegments(Segment &seg1, Segment &seg2, float* outT1, float* outT2, Vector2* outPos) {
	Vector2 v = seg2.s - seg1.s;
	float Crs_v1_v2 = Vector2Cross(&seg1.v, &seg2.v);
	if (Crs_v1_v2 == 0.0F) {
		return false;
	}

	float Crs_v_v1 = Vector2Cross(&v, &seg1.v);
	float Crs_v_v2 = Vector2Cross(&v, &seg2.v);

	float t1 = Crs_v_v2 / Crs_v1_v2;
	float t2 = Crs_v_v1 / Crs_v1_v2;

	if (outT1)
		*outT1 = Crs_v_v2 / Crs_v1_v2;
	if (outT2)
		*outT2 = Crs_v_v1 / Crs_v1_v2;

	const float eps = 0.00001f;
	if (t1 + eps < 0 || t1 - eps > 1 || t2 + eps < 0 || t2 - eps > 1) {
		// 交差していない
		return false;
	}
	//出力用。
	if (outPos)
		*outPos = seg1.s + seg1.v * t1;

	return true;
}

Vector2 CCollision::GetMatrixPos(float myx, float myy) {
	DWORD cwidth = Game->MapData().GetChipWidth();
	DWORD cheight = Game->MapData().GetChipHeight();
	int x = (int)(myx / cwidth);
	int y = (int)(myy / cheight);
	return Vector2((float)x * cwidth, (float)y * cheight);
}

POINT CCollision::CheckMap(int x, int y,float addx, float addy) {
	POINT pt[] = {
		{ x + mPt[0].x,	y + mPt[0].y },//左上
		{ x + mPt[1].x,	y + mPt[1].y },//上真ん中
		{ x + mPt[2].x,	y + mPt[2].y },//右上

		{ x + mPt[3].x,	y + mPt[3].y },//左真ん中
		{ x + mPt[4].x,	y + mPt[4].y },//右真ん中

		{ x + mPt[5].x,	y + mPt[5].y },//左下
		{ x + mPt[6].x,	y + mPt[6].y },//下真ん中
		{ x + mPt[7].x,	y + mPt[7].y },//右下
	};
	POINT res{ x,y };
	DWORD cwidth = Game->MapData().GetChipWidth();
	DWORD cheight = Game->MapData().GetChipHeight();
	//レイヤー1が四角形床。
	for (int i = 0; i < 8; i++) {
		int index = Game->MapData().GetValue(1, pt[i].x / cwidth, pt[i].y / cheight);
		//斜め床の判定。
		int indexslope = GetChipLayerNum(x, y);
		int src_x = (index % Game->GetBitCount()) * cwidth;
		int src_y = (index / Game->GetBitCount()) * cheight;
		//坂道の当たり判定があるのなら、-2を返す。
		if (indexslope != 0) {
			res.x = -2;
			res.y = -2;
			return res;
		}
		//坂道の当たり判定がない時は床の当たり判定を行う。
		if (src_x == 0 && src_y == 0) {
			res.x = pt[i].x / cwidth; // 壁の座標を代入
			res.y = pt[i].y / cheight;
			return res; // 壁の座標を返す
		}
	}
	res.x = -1;//ここまで来たら当たっていないということ
	res.y = -1;
	return res;
}

int CCollision::GetChipLayerNum(int x, int y) {
	POINT pt[] = {
		{ x + mPt[0].x,	y + mPt[0].y },//左上
		{ x + mPt[1].x,	y + mPt[1].y },//上真ん中
		{ x + mPt[2].x,	y + mPt[2].y },//右上

		{ x + mPt[3].x,	y + mPt[3].y },//左真ん中
		{ x + mPt[4].x,	y + mPt[4].y },//右真ん中

		{ x + mPt[5].x,	y + mPt[5].y },//左下
		{ x + mPt[6].x,	y + mPt[6].y },//下真ん中
		{ x + mPt[7].x,	y + mPt[7].y },//右下
	};
	DWORD cwidth = Game->MapData().GetChipWidth();
	DWORD cheight = Game->MapData().GetChipHeight();
	//for文で処理を行っているのは、当たり判定のレイヤー数が増えるかもしれないから。
	for (int i = 0; i <= MaxLayerNum; i++) {
		for (int j = 0; j < 8; j++) {
			int index = Game->MapData().GetValue(i + 2, pt[j].x / cwidth, pt[j].y / cheight);
			int src_x = (index % Game->GetBitCount()) * cwidth;
			int src_y = (index / Game->GetBitCount()) * cheight;
			//もしレイヤ番号に、衝突していたら。
			if (src_x == 0 && src_y == 0) {
				//レイヤー番号を返す。
				return i + 2;
			}
		}
	}
	//念のため。
	return 0;
}

POINT CCollision::CheckSlopeBlock(int x, int y, int layernum, int addy) {
	POINT pt[] = {
		{ x + mPt[0].x,	y + mPt[0].y },//左上
		{ x + mPt[1].x,	y + mPt[1].y },//上真ん中
		{ x + mPt[2].x,	y + mPt[2].y },//右上

		{ x + mPt[3].x,	y + mPt[3].y },//左真ん中
		{ x + mPt[4].x,	y + mPt[4].y },//右真ん中

		{ x + mPt[5].x,	y + mPt[5].y },//左下
		{ x + mPt[6].x,	y + mPt[6].y },//下真ん中
		{ x + mPt[7].x,	y + mPt[7].y },//右下
	};
	POINT res = { x,y };
	DWORD cwidth = Game->MapData().GetChipWidth();
	DWORD cheight = Game->MapData().GetChipHeight();
	for(int i = 0; i < 8; i++){
		int index = Game->MapData().GetValue(layernum , pt[i].x / cwidth, pt[i].y / cheight);
		int src_x = (index % Game->GetBitCount()) * cwidth;
		int src_y = (index / Game->GetBitCount()) * cheight;
		//坂道の当たり判定がない時は床の当たり判定を行う。
		if (src_x == 0 && src_y == 0) {
			res.x = pt[i].x / cwidth; // 壁の座標を代入
			res.y = pt[i].y / cheight;
			return res; // 壁の座標を返す
		}
	}
	res.x = -1;
	res.y = -1;
	return res;
}

Vector2 CCollision::CheckSlope(int x, int y, Segment& Slope, int layernum,int slopecond,float addy) {
	//当たり判定。
	POINT pt[] = {
		{ x + mPt[0].x,	y + mPt[0].y },//左上
		{ x + mPt[1].x,	y + mPt[1].y },//上真ん中
		{ x + mPt[2].x,	y + mPt[2].y },//右上

		{ x + mPt[3].x,	y + mPt[3].y },//左真ん中
		{ x + mPt[4].x,	y + mPt[4].y },//右真ん中

		{ x + mPt[5].x,	y + mPt[5].y },//左下
		{ x + mPt[6].x,	y + mPt[6].y },//下真ん中
		{ x + mPt[7].x,	y + mPt[7].y },//右下
	};
	DWORD cwidth = Game->MapData().GetChipWidth();
	DWORD cheight = Game->MapData().GetChipHeight();
	Segment Player;
	switch (slopecond) {
	//1は下の下り坂。
	case 1:
		Player.s = { (float)pt[2].x,(float)pt[2].y };
		Player.v = { (float)pt[5].x,(float)pt[5].y };
		break;
	//2は上の上り坂。
	case 2:
		Player.s = { (float)pt[7].x,(float)pt[7].y };
		Player.v = { (float)pt[0].x,(float)pt[0].y };
		break;
	//3は上の上り坂。
	case 3:
		Player.s = { (float)pt[5].x,(float)pt[5].y };
		Player.v = { (float)pt[2].x,(float)pt[2].y };
		break;
	//0は下の上り坂。（0以外は想定していないのだ。）
	default:
		Player.s = { (float)pt[0].x,(float)pt[0].y };
		Player.v = { (float)pt[7].x,(float)pt[7].y };
		break;
	}
	Vector2 res = { (float)x,(float)y };
	//デバッグ用。
	float T1;
	float T2;
	Vector2 Vres;
	int PlayerCond;
	//式自体はあっているが…。レイヤーの数値獲得に原因がありそうだ。
	//プレイヤーの終点のX座標が、坂道のX座標の間なら、1。間でないのなら、0。
	if (Slope.s.x >= Player.v.x && Player.v.x < Slope.v.x || Slope.s.x <= Player.v.x && Player.v.x > Slope.v.x) {
		PlayerCond = 1;
	}
	else {
		PlayerCond = 0;
	}
	//衝突した場合は座標に内分を足し、そうでない場合は内分を引く。
	//（間違っているかもしれない…。）
	if (PlayerCond != 0) {
		if (ColSegments(Slope,Player,&T1,&T2,&Vres)) {
			res.x = x-T1;
			res.y = y-T1;
			addy = T1;
			return res;
		}
		else {
			res.x = x + T1;
			res.y = y + T1;
			addy = T1;
		}
	}
	else {
		//なるほど、あり得ない座標を代入したら、レイヤーの数値獲得もおかしくなるわけか…。
		res.x = -2;
		res.y = -2;
		return res;
	}
	//ここまで来たら衝突していないということ。
	res.x = -1;
	res.y = -1;
	return res;
}

void CCollision::GetXPosition(float* myx, float* myy, float* addx, float* addy) {
	//座標を一時的に保存する。
	float newx;
	float tempx = *myx;
	float tempaddx = *addx;

	newx = tempx + tempaddx;
	//ブロックとしての衝突判定。
	POINT block = CheckMap((int)newx, (int)*myy, *addx, *addy);
	const int OK = -1;
	const int SLOPE = -2;
	//X座標が衝突していなかったら。
	if (block.x == OK) {
		*myx = newx;
	}
	//X座標が坂道のレイヤーにいたら。
	else if(block.x == SLOPE){
		//ブロックのレイヤー番号の獲得。
		int Layer = GetChipLayerNum((int)newx, (int)*myy);
		//線分の変数の宣言。
		Segment Slope;
		//スロープの条件分離。
		int SlopeCond;
		switch (Layer) {
		case 2:
			Slope.s = GetMatrixPos(newx, *myy + 16.0F);
			Slope.v = GetMatrixPos(newx + 16.0F, *myy);
			SlopeCond = 0;
			break;
		default:
			SlopeCond = -1;
			break;
		}
		//ブロックとしての当たり判定。
		POINT slopeblock = CheckSlopeBlock((int)newx, (int)*myy, Layer);
		//坂道としての当たり判定。
		Vector2 slope = CheckSlope((int)newx, (int)*myy, Slope, Layer,SlopeCond);
		const float SLOPEOK = -1.0F;
		const float BLOCK = -2.0F;
		//-2を返したら、ブロックとして扱う。
		if (slope.x == SLOPEOK) {
			*myx = newx;
		}
		else if (slope.x == BLOCK) {
			//めり込み防止をする。
			*myx = newx;
		}
		else {
			//めり込み防止をする。
			*myx = slope.x;
		}
	}
	//ブロックの当たり判定。
	else {
		if (tempaddx > 0) {
			*myx = (float)((block.x - 1) * 16 + 8 + OffsetR - 0.1f);
		}
		else {
			*myx = (float)((block.x + 1) * 16 + 8 + OffsetL + 0.1f);
		}
		*addx = 0;//当たっているので移動させない。
	}
}

void CCollision::GetYPosition(float* myx, float* myy, float* addx, float* addy, int* jcount, bool* gflag) {
	//座標を一時的に保存する。
	float tempaddx = *addx;
	float newy = *myy + *addy;
	//ブロックとしての衝突判定。
	POINT block = CheckMap((int)*myx, (int)newy,*addx,*addy);
	const int OK = -1;
	const int SLOPE = -2;
	//Y座標が衝突していなかったら。
	if (block.y == OK) {
		*myy = newy;
		*gflag = false;
	}
	//Y座標が坂道のレイヤーにいたら。
	else if(block.y == SLOPE){
		//ブロックのレイヤー番号の獲得。
		int Layer = GetChipLayerNum((int)*myx, (int)newy);
		//坂道の線分の宣言。
		Segment Slope;
		//スロープの条件分離。
		int SlopeCond;
		switch (Layer) {
		case 2:
			Slope.s = GetMatrixPos(*myx, newy + 16.0F);
			Slope.v = GetMatrixPos(*myx + 16.0F, newy);
			SlopeCond = 0;
			break;
		default:
			SlopeCond = -1;
			break;
		}
		//Y座標の変化。
		float saddy = 0;
		//ブロックとしての当たり判定。
		POINT slopeblock = CheckSlopeBlock((int)*myx, (int)newy, Layer);
		//坂道としての当たり判定。
		Vector2 slope = CheckSlope((int)*myx, (int)newy, Slope, Layer, SlopeCond, saddy);
		const float SLOPEOK = -1.0F;
		const float BLOCK = -2.0F;
		//-2を返したら、ブロックとして扱う。
		if (slope.y == SLOPEOK) {
			*gflag = true;
			//Y座標の変化。
			*myy = *myy + saddy;
		}
		//歩いていた場合は、
		else if (tempaddx != 0 && slope.y != BLOCK) {
			*gflag = true;
		}
		else if (slope.y == BLOCK) {
			*gflag = true;
		}
	}
	//ブロックの当たり判定。
	else {
		if (*addy < 0) {
			*myy = (float)((block.y + 1) * 16 + 8 + OffsetU + 0.1f);
		}
		else {
			*myy = (float)((block.y - 1) * 16 + 8 + OffsetD - 0.1f);
			*jcount = 0;
			*gflag = true;
		}
		*addy = 0;
	}
}

void CCollision::GetMove(float* myx, float* myy, float* addx, float* addy, int* jcount, bool* gflag) {
	GetXPosition(myx, myy, addx, addy);

	if (*addy > GRAVMAX)
	{
		*addy = GRAVMAX;
	}
	else
	{
		*addy += GRAV;
	}

	GetYPosition(myx, myy, addx, addy, jcount, gflag);
}