#define OLC_PGE_APPLICATION
#include "pixelGameEngine.h"
#include "geometry2d.h"
#include <strstream>
#include <algorithm>
#include "main.h"
#include <optional>

using namespace olc;

FaceBall* game;

Enemy::Enemy(EnemyID id,vec3d pos,float rot,float radius)
	:id(id), health(game->enemyData[id].health), fireDelay(0), blinking(game->enemyData[id].blinking),
	Object({game->enemyData[id].mesh,pos,rot,radius}) {}

EnemyID Enemy::GetID() {
	return id;
}

void Enemy::Hurt(int damage) {
	lastHitTime = 0.2f;
	for (Triangle& t : mesh.tris) {
		t.col[0] = BLACK;
		t.col[1] = BLACK;
		t.col[2] = BLACK;
	}
	health-=damage;
	blinkingAmt = 1;
}

bool Enemy::isDead() {
	return health <= 0;
}

void Enemy::increaseDeathTimer(float fElapsedTime) {
	deathTimer += fElapsedTime;
}

bool Enemy::deathAnimationOver() {
	return deathTimer > 3.0f;
}

void Enemy::increaseColorFactor(float fElapsedTime) {
	colorFactor += 50 * fElapsedTime;
}

float Enemy::getColorFactor() {
	return colorFactor;
}

void Enemy::decreaseColorFactor() {
	colorFactor--;
}

Phase Enemy::GetPhase() {
	return phase;
}

void Enemy::SetPhase(Phase phase) {
	this->phase = phase;
}

bool Enemy::CanShoot() {
	return fireDelay <= 0;
}

void Enemy::ShootBullet(int myIndex) {
	fireDelay = game->enemyData[GetID()].fireDelay;
	game->bullets.push_back({ game->bullet, {pos.x,pos.y+0.15f,pos.z},rot,0.2f,{std::cosf(rot) * game->shotSpd,std::sinf(rot) * game->shotSpd }, YELLOW,false,GetID(),myIndex,blinking});
}

void Enemy::ReloadBullet(float fElapsedTime) {
	fireDelay -= fElapsedTime;
}

void Enemy::reduceLastHitTimer(float fElapsedTime) {
	lastHitTime -= fElapsedTime;
	if (lastHitTime <= 0.0f) {
		for (Triangle&t:mesh.tris) {
			t.col[0] = WHITE;
			t.col[1] = WHITE;
			t.col[2] = WHITE;
		}
	}
}

bool Enemy::isLastHitTimerActive() {
	return lastHitTime > 0.0f;
}

bool Enemy::isExplosive() {
	return game->enemyData[id].explosive;
}

void Enemy::setExploded(bool exploded) {
	this->exploded = exploded;
	game->RegenerateMinimap();
}

Enemy Enemy::freshCopy(bool randomizeLoc){
	vf2d newPos = game->GetRandomizedSpawnPosition();
	return Enemy{id,(randomizeLoc)?vec3d{newPos.x,0,newPos.y}:pos,rot,radius};
}

bool Enemy::isBlinking() {
	return blinking;
}

bool Enemy::isExploded() {
	return exploded;
}

bool Enemy::Update(float fElapsedTime) {
	if (!isDead()) {
		aliveTime += fElapsedTime;
		if (isBlinking()) {
			blinkingAmt = std::sinf(30 * aliveTime);
		}
	}
	else {
		if (!deathAnimationOver()) {
			std::vector<Triangle>& tris = mesh.tris;
			uint8_t darkenAmt = 0;
			while (getColorFactor() >= 1) {
				darkenAmt++;
				decreaseColorFactor();
			}
			if (isExploded()) {
				if (!flippedTriangles) {
					flippedTriangles = true;
					for (Triangle& t : tris) {
						vec3d temp = t.p[1];
						t.p[1] = t.p[2];
						t.p[2] = t.p[1];
					}
				}
				for (Triangle& t : tris) {
					float dir = std::atan2f(t.p[0].z, t.p[0].x);
					t.p[0].x += std::cosf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 100 / 100.f) * fElapsedTime * 3;
					t.p[0].z += std::sinf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 100 / 100.f) * fElapsedTime * 3;
					if (t.p[0].y > 0.041f) {
						t.p[0].y = std::max(0.04f, t.p[0].y - 0.8f * fElapsedTime * 3);
					}
					dir = std::atan2f(t.p[1].z, t.p[1].x);
					t.p[1].x += std::cosf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 100 / 100.f) * fElapsedTime * 3;
					t.p[1].z += std::sinf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 100 / 100.f) * fElapsedTime * 3;
					if (t.p[1].y > 0.041f) {
						t.p[1].y = std::max(0.04f, t.p[1].y - 0.8f * fElapsedTime * 3);
					}
					dir = std::atan2f(t.p[2].z, t.p[2].x);
					t.p[2].x += std::cosf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 70 / 100.f) * fElapsedTime * 3;
					t.p[2].z += std::sinf(dir + (rand() % 200 / 200.f) - 1) * (rand() % 70 / 100.f) * fElapsedTime * 3;
					if (t.p[2].y > 0.041f) {
						t.p[2].y = std::max(0.04f, t.p[2].y - 0.8f * fElapsedTime * 3);
					}
					if (darkenAmt > 0) {
						t.col[0] -= {darkenAmt, darkenAmt, darkenAmt};
						t.col[1] -= {darkenAmt, darkenAmt, darkenAmt};
						t.col[2] -= {darkenAmt, darkenAmt, darkenAmt};
					}
				}
			}
			else {
				for (Triangle& t : tris) {
					if (t.p[0].y > 0.1) {
						t.p[0].y = std::max(0.1f, t.p[0].y - 0.3f * fElapsedTime);
					}
					else {
						float dir = std::atan2f(t.p[0].z, t.p[0].x);
						t.p[0].x += std::cosf(dir) * 0.04f * fElapsedTime;
						t.p[0].z += std::sinf(dir) * 0.04f * fElapsedTime;
					}
					if (t.p[1].y > 0.1) {
						t.p[1].y = std::max(0.1f, t.p[1].y - 0.3f * fElapsedTime);
					}
					else {
						float dir = std::atan2f(t.p[1].z, t.p[1].x);
						t.p[1].x += std::cosf(dir) * 0.06f * fElapsedTime;
						t.p[1].z += std::sinf(dir) * 0.06f * fElapsedTime;
					}
					if (t.p[2].y > 0.1) {
						t.p[2].y = std::max(0.1f, t.p[2].y - 0.3f * fElapsedTime);
					}
					else {
						float dir = std::atan2f(t.p[2].z, t.p[2].x);
						t.p[2].x += std::cosf(dir) * 0.05f * fElapsedTime;
						t.p[2].z += std::sinf(dir) * 0.05f * fElapsedTime;
					}
					if (darkenAmt > 0) {
						t.col[0] -= {darkenAmt, darkenAmt, darkenAmt};
						t.col[1] -= {darkenAmt, darkenAmt, darkenAmt};
						t.col[2] -= {darkenAmt, darkenAmt, darkenAmt};
					}
				}
			}
			if (isExploded()) {
				increaseDeathTimer(fElapsedTime * 3);
				increaseColorFactor(fElapsedTime * 3);
			}
			else {
				increaseDeathTimer(fElapsedTime);
				increaseColorFactor(fElapsedTime);
			}
		}
		else
		if (!finishedAnimation) {
			finishedAnimation = true;
			if (isExploded()) {
				for (Triangle& t : mesh.tris) {
					t.p[0].y = 0.04f + game->restingTriangleYDepth;
					t.p[1].y = 0.04f + game->restingTriangleYDepth;
					t.p[2].y = 0.04f + game->restingTriangleYDepth;
					game->restingTriangleYDepth += 0.000001f;
					if (game->restingTriangleYDepth > 0.001f) {
						game->restingTriangleYDepth = 0.f;
					}
				}
			}
		}
		else 
		if (fElapsedTime > 3/120.f) {
			mesh.tris.clear();
			return false;
		}
	}
	return true;
}

//Has Camo powerup code so camo masks the player position.
vf2d Enemy::GetPlayerPosition() {
	if (game->PlayerHasCamo()) {
		return { float(int(id) % game->MAP_SIZE.x) + 0.5f,float(int(id) % game->MAP_SIZE.y)+0.5f };
	}
	else {
		return game->GetPlayerPos();
	}
}

void Enemy::OnDeathEvent() {

	game->AddScore(game->enemyData[id].health*10);
	game->SubtractTag();
	if (game->enemyData[id].powerupDrop != PowerupType::NONE) {
		game->SpawnPowerup(game->enemyData[id].powerupDrop, pos);
	}
	if (game->PlayerHasMapUpgrade()){
		game->RegenerateMinimap();
	}
}

vf2d FaceBall::GetPlayerPos() {
	return { player.GetPos().x,player.GetPos().z };
}

bool FaceBall::PlayerHasCamo() {
	return camoDuration >= 0;
}

void FaceBall::InitializeEnemyData() {
	enemyData[EnemyID::NONE] = { "VOID",undefined,BLACK };
	enemyData[EXIT] = { "EXIT",undefined,GREEN };
	enemyData[START] = { "SPAWN POSITION",undefined,{128,64,0} };
	enemyData[SHOOTME] = { "SHOOTME",enemy_ShootMe,YELLOW,1,1,0.5,2,1,0.2f,true };
	enemyData[SHOOTME2] = { "SHOOTME2",enemy_ShootMe2,YELLOW,1,1,PI / 6,2,1,0.35f,true };
	enemyData[ISHOOT] = { "ISHOOT",enemy_IShoot,YELLOW,1,1,0.5,2,3,0.4f,true };
	enemyData[SHOOTME_ARMOR] = { "SHOOTME",enemy_ShootMe,YELLOW,6,1,0.5,2,1,0.3f,true,PowerupType::ARMOR,true };
	enemyData[ISHOOT_MAP] = { "ISHOOT",enemy_IShoot,YELLOW,7,1,0.5,2,2,0.3f,true,PowerupType::MAP,true };
	enemyData[SONAR] = { "Sonar",enemy_Sonar,RED,5,1,PI / 8,2,1 };
	enemyData[COIN] = { "Coin",undefined,BLUE };
	enemyData[POWERUP_ARMOR] = { "Armor",undefined,{96,0,96} };
	enemyData[POWERUP_SPEED] = { "Speed",undefined,{96,0,96} };
	enemyData[POWERUP_SHOT] = { "Shot",undefined,{96,0,96} };
	enemyData[SPECIAL_CAMO] = { "Camouflage",undefined,DARK_RED };
	enemyData[SPECIAL_STOP] = { "Stop",undefined,DARK_RED };
	enemyData[SPECIAL_SHIELD] = { "Shield",undefined,DARK_RED };
	enemyData[AREA_MAP] = { "Map",undefined,GREEN };
}

void FaceBall::InitializePowerupColors() {
	powerupData[PowerupType::ARMOR] = { "Armor UP",DARK_GREEN };
	powerupData[PowerupType::SPEED] =	{ "Speed UP", DARK_BLUE};
	powerupData[PowerupType::SHOTS] =	{ "Shots UP", DARK_RED};
	powerupData[PowerupType::STOP] =	{ "< STOP >", RED};
	powerupData[PowerupType::SHIELD] = { "< Invincibility Shield >", CYAN};
	powerupData[PowerupType::CAMO] =	{ "< Camouflage >", MAGENTA};
	powerupData[PowerupType::MAP] =	{ "< Minimap >", GREEN};
	powerupData[PowerupType::COIN] = { "Coin", BLUE };
}

Powerup::Powerup(Mesh mesh, vec3d pos, float rot, PowerupType type) 
	: type(type), col(game->powerupData[type].col), Object{ mesh,pos,rot,0.375f } {}

bool Powerup::Update(float fElapsedTime) {
	aliveTime += fElapsedTime;
	colorCycle = std::sinf(10 * aliveTime);
	col = game->powerupData[type].col * colorCycle;
	opened = false; //Open state is false until a player causes it to open.
	return true;
}

void FaceBall::SpawnPowerup(PowerupType type, vec3d pos) {
	Mesh mesh = (type <= PowerupType::SHOTS) ? powerup : powerup2;
	for (Triangle& t : mesh.tris) {
		t.col[0] = powerupData[type].col;
		t.col[1] = powerupData[type].col;
		t.col[2] = powerupData[type].col;
	}
	powerups.push_back({ mesh,pos,0,type });
}

void FaceBall::ConvertBulletColor(Mesh& bullet, Pixel col) {
	for (Triangle& t : bullet.tris) {
		t.col[0] = col;
		t.col[1] = col;
		t.col[2] = col;
	}
}

void FaceBall::LoadLevel(int level)
{
	mode = GAMEMODE::GAME;
	screenAlpha=screenAlpha2=0;
	this->level = level;
	restingTriangleYDepth = 0.f;
	exitWallsCleared = false;
	hudShakeTime = 0;
	std::vector<std::vector<Tile>>mapData = editor.LoadLevel(level);
	MAP_SIZE = { (int)mapData[0].size(),(int)mapData.size() };
	map.clear();
	mapWalls.tris.clear();
	mapFloor.tris.clear();
	objects.clear();
	bullets.clear();
	enemies.clear();
	powerups.clear();
	wave2Enemies.clear();
	wave3Enemies.clear();
	knownGoodPositions.clear();
	tagsRemaining=10;
	exitCoords = { 0,0 };
	for (int y = 0; y < MAP_SIZE.y; y++) {
		std::vector<MapSquare>row;
		for (int x = 0; x < MAP_SIZE.x; x++) {
			row.push_back({});
			mapFloor.tris.push_back({ {{(float)x,0,(float)y},{(float)x,0,(float)y + 1},{(float)x + 1,0,(float)y}},{{0,0},{0,1},{1,0}},{WHITE,WHITE,WHITE}, floor_tex });
			mapFloor.tris.push_back({ {{(float)x + 1,0,(float)y},{(float)x,0,(float)y + 1},{(float)x + 1,0,(float)y + 1}},{{1,0},{0,1},{1,1}},{WHITE,WHITE,WHITE}, floor_tex });
			EnemyID id = mapData[y][x].enemyId;
			if (id>=SHOOTME&& id < POWERUP_ARMOR) {
				knownGoodPositions.push_back({x + 0.5f,y + 0.5f});
				if (mapData[y][x].wave1) {
					enemies.push_back({ id,vec3d{x + 0.5f,0,y + 0.5f},((int)mapData[y][x].facingDir - 1) * PI / 2,enemyData[id].radius });
				} else
				if (mapData[y][x].wave2) {
					wave2Enemies.push_back({ id,vec3d{x + 0.5f,0,y + 0.5f},((int)mapData[y][x].facingDir - 1) * PI / 2,enemyData[id].radius });
				}
				else {
					wave3Enemies.push_back({ id,vec3d{x + 0.5f,0,y + 0.5f},((int)mapData[y][x].facingDir - 1) * PI / 2,enemyData[id].radius });
				}
			}
			if (id >= POWERUP_ARMOR) {
				SpawnPowerup(PowerupType((int)id-55), vec3d{ x + 0.5f,0,y + 0.5f });
			}
			if (id == EXIT) {
				exitCoords = { x,y };
			}
			if (id == START) {
				spawnLoc = { x + 0.5f,0.3,y + 0.5f };
				player.UpdatePos(spawnLoc);
				spawnFacingDir = mapData[y][x].facingDir;
				fYaw = (int(spawnFacingDir) - 1) * PI / 2;
			}
		}
		map.push_back(row);
	}
	for (int y = 0; y < MAP_SIZE.y; y++) {
		for (int x = 0; x < MAP_SIZE.x; x++) {
			int wallData = 0;
			if (mapData[y][x].wallN) {
				wallData |= NORTH;
			}
			if (mapData[y][x].wallE) {
				wallData |= EAST;
			}
			if (mapData[y][x].wallS) {
				wallData |= SOUTH;
			}
			if (mapData[y][x].wallW) {
				wallData |= WEST;
			}
			AddWall(wallData, { x,y });
			if (map[y][x].wallN != NULL) {
				Decal*exitWallTex = wall_tex;
				if (vi2d{ x,y } == vi2d{exitCoords.x, exitCoords.y + 1}) {
					exitWallTex = exit_wall_tex;
				}
				if (vi2d{x,y} != exitCoords) {
					mapWalls.tris.push_back({ {{(float)x,1,(float)y},{(float)x,0,(float)y},{(float)x + 1,1,(float)y}},{{0,0},{0,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x,0,(float)y},{(float)x + 1,0,(float)y},{(float)x + 1,1,(float)y}},{{0,1},{1,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
			}
			if (map[y][x].wallS != NULL) {
				Decal* exitWallTex = wall_tex;
				if (vi2d{ x,y } == vi2d{ exitCoords.x, exitCoords.y - 1 }) {
					exitWallTex = exit_wall_tex;
				}
				if (vi2d{ x,y } != exitCoords) {
					mapWalls.tris.push_back({ {{(float)x + 1,1,(float)y + 1},{(float)x,0,(float)y + 1},{(float)x,1,(float)y + 1}},{{0,0},{1,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x + 1,1,(float)y + 1},{(float)x + 1,0,(float)y + 1},{(float)x,0,(float)y + 1}},{{0,0},{0,1},{1,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
			}
			if (map[y][x].wallW != NULL) {
				Decal* exitWallTex = wall_tex;
				if (vi2d{ x,y } == vi2d{ exitCoords.x+1, exitCoords.y }) {
					exitWallTex = exit_wall_tex;
				}
				if (vi2d{ x,y } != exitCoords) {
					mapWalls.tris.push_back({ {{(float)x,1,(float)y},{(float)x,1,(float)y + 1}, {(float)x,0,(float)y + 1}},{{1,0},{0,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x,0,(float)y},{(float)x,1,(float)y}, {(float)x,0,(float)y + 1}}, {{1,1},{1,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
			}
			if (map[y][x].wallE != NULL) {
				Decal* exitWallTex = wall_tex;
				if (vi2d{ x,y } == vi2d{ exitCoords.x - 1, exitCoords.y }) {
					exitWallTex = exit_wall_tex;
				}
				if (vi2d{ x,y } != exitCoords) {
					mapWalls.tris.push_back({ {{(float)x + 1,0,(float)y + 1},{(float)x + 1,1,(float)y + 1},{(float)x + 1,1,(float)y}},{{1,1},{1,0},{0,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x + 1,0,(float)y + 1} ,{(float)x + 1,1,(float)y},{(float)x + 1,0,(float)y}},{{1,1},{0,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
			}
		}
	}
	walls = { mapWalls };
	MapWallsObjectIndex = objects.size()-1;
	objects.push_back({ mapFloor,{0,0,0},0,0 });
	//objects.push_back({ game->mapExit,{(float)exitCoords.x+0.5f,0,(float)exitCoords.y+0.5f},0,0.4f });
	exit = { game->mapExit,{(float)exitCoords.x + 0.5f,0.02f,(float)exitCoords.y + 0.5f},0,0.4f };
	RegenerateMinimap();
}

void FaceBall::RegenerateMinimap(){
	vf2d mapCenter = {float(ScreenWidth()-364),float(164)};
	vf2d mapSprSize = {float(mapSpr->width),float(mapSpr->height)};
	SetDrawTarget(mapSpr);
	Clear(BLACK);
	const int MAP_VISIBILITY=6;
	for (int x=0;x<MAP_SIZE.x;x++){
		for (int y=0;y<MAP_SIZE.y;y++){
			float dist = std::sqrtf(std::pow(player.GetPos().x-x,2)+std::pow(player.GetPos().z-y,2));
			if (dist<MAP_VISIBILITY){
				try{
					MapSquare&square=map.at(y).at(x);
					vf2d squareLoc = vf2d{x*minimapGridSize.x,y*minimapGridSize.y};
					uint8_t transparency=uint8_t((1-(float(dist)/MAP_VISIBILITY))*255);
					if (square.wallN){
						DrawLine(squareLoc,squareLoc+vf2d{1.f,0.f}*minimapGridSize,Pixel{255,255,255,transparency});
					}
					if (square.wallS){
						DrawLine(squareLoc+vf2d{0,1.f}*minimapGridSize,squareLoc+vf2d{1,1}*minimapGridSize,Pixel{255,255,255,transparency});
					}
					if (square.wallE){
						DrawLine(squareLoc+vf2d{1,0}*minimapGridSize,squareLoc+vf2d{1,1}*minimapGridSize,Pixel{255,255,255,transparency});
					}
					if (square.wallW){
						DrawLine(squareLoc,squareLoc+vf2d{0,1}*minimapGridSize,Pixel{255,255,255,transparency});
					}
					if (hasMapUpgrade){
						for (Enemy&e:enemies){
							if (!e.isDead()){
								if (vi2d{int(e.pos.x),int(e.pos.z)}==vi2d{x,y}){
									FillCircle(vi2d{int(squareLoc.x+0.5*minimapGridSize.x),int(squareLoc.y+0.5*minimapGridSize.y)},3,enemyData[e.GetID()].col*(1-(float(dist)/MAP_VISIBILITY)));
								}
							}
						}
					}
				} catch (std::out_of_range const& exc) {
					std::cout << exc.what() << '\n';
				}
			}
		}
	}
	float dist = std::sqrtf(std::pow(player.GetPos().x-exitCoords.x,2)+std::pow(player.GetPos().z-exitCoords.y,2));
	if (tagsRemaining>0){
		vf2d squareLoc = vf2d{exitCoords.x*minimapGridSize.x,exitCoords.y*minimapGridSize.y};
		uint8_t transparency=uint8_t((1-(float(dist)/MAP_VISIBILITY))*255);
		DrawLine(squareLoc,squareLoc+vf2d{1.f,0.f}*minimapGridSize,Pixel{DARK_RED.r,DARK_RED.g,DARK_RED.b,transparency});
		DrawLine(squareLoc+vf2d{0,1.f}*minimapGridSize,squareLoc+vf2d{1,1}*minimapGridSize,Pixel{DARK_RED.r,DARK_RED.g,DARK_RED.b,transparency});
		DrawLine(squareLoc+vf2d{1,0}*minimapGridSize,squareLoc+vf2d{1,1}*minimapGridSize,Pixel{DARK_RED.r,DARK_RED.g,DARK_RED.b,transparency});
		DrawLine(squareLoc,squareLoc+vf2d{0,1}*minimapGridSize,Pixel{DARK_RED.r,DARK_RED.g,DARK_RED.b,transparency});
	}
	FillCircle(vi2d{int(exitCoords.x*minimapGridSize.x+0.5*minimapGridSize.x),int(exitCoords.y*minimapGridSize.x+0.5*minimapGridSize.y)},3,MAGENTA*(1-(float(dist)/MAP_VISIBILITY)));
	SetDrawTarget(nullptr);
	mapDecal->Update();
}

bool FaceBall::CheckCollision(vec3d movementVector,vf2d pos,float radius){
	utils::geom2d::circle<float>newPos{ {pos.x + movementVector.x,pos.y + movementVector.z},radius };
	bool collisionOccured = false;
	if (pos.x - radius > game->MAP_SIZE.x || pos.x + radius<0 || pos.y - radius>game->MAP_SIZE.y || pos.y + radius < 0) {
		return true;
	}
	for (int y = -1; y <= 1; y++) {
		for (int x = -1; x <= 1; x++) {
			int offsetX = (int)pos.x + x;
			int offsetY = (int)pos.y + y;
			if (offsetX >= 0 && offsetX < game->MAP_SIZE.x && offsetY >= 0 && offsetY < game->MAP_SIZE.y) {
				MapSquare tile = game->map[offsetY][offsetX];
				if (tile.wallN != NULL) {
					utils::geom2d::line<float>wall{ {(float)offsetX,(float)offsetY},{(float)offsetX+1,(float)offsetY} };
					if (((int)(newPos.pos.x-radius)==offsetX||
						(int)(newPos.pos.x + radius) == offsetX)
						&&
						newPos.pos.y > wall.start.y && wall.start.y > newPos.pos.y - radius) {
						collisionOccured = true;
						goto breakOutOfCollisionLoop;
					}
				}
				if (tile.wallE != NULL) {
					utils::geom2d::line<float>wall{ {(float)offsetX+1,(float)offsetY},{(float)offsetX + 1,(float)offsetY+1} };
					if (((int)(newPos.pos.y - radius) == offsetY ||
						(int)(newPos.pos.y + radius) == offsetY)
						&& newPos.pos.x<wall.start.x && wall.start.x < newPos.pos.x + radius) {
						collisionOccured = true;
						goto breakOutOfCollisionLoop;
					}
				}
				if (tile.wallS != NULL) {
					utils::geom2d::line<float>wall{ {(float)offsetX,(float)offsetY+1},{(float)offsetX + 1,(float)offsetY+1} };
					if (((int)(newPos.pos.x - radius) == offsetX ||
						(int)(newPos.pos.x + radius) == offsetX)
						&& newPos.pos.y < wall.start.y && wall.start.y < newPos.pos.y + radius) {
						collisionOccured = true;
						goto breakOutOfCollisionLoop;
					}
				}
				if (tile.wallW != NULL) {
					utils::geom2d::line<float>wall{ {(float)offsetX,(float)offsetY},{(float)offsetX,(float)offsetY + 1} };
					if (((int)(newPos.pos.y - radius) == offsetY ||
						(int)(newPos.pos.y + radius) == offsetY)
						&& newPos.pos.x > wall.start.x && wall.start.x > newPos.pos.x - radius) {
						collisionOccured = true;
						goto breakOutOfCollisionLoop;
					}
				}
			}
		}
	}
	breakOutOfCollisionLoop:
	return collisionOccured;
}

EnemyData FaceBall::GetData(EnemyID id)
{
	return enemyData[id];
}

vec3d
FaceBall::Matrix_MultiplyVector(mat4x4& m, vec3d& i)
{
	vec3d v;
	v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0];
	v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1];
	v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2];
	v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3];
	return v;
};

mat4x4 FaceBall::Matrix_MakeIdentity()
{
	mat4x4 matrix;
	matrix.m[0][0] = 1.0f;
	matrix.m[1][1] = 1.0f;
	matrix.m[2][2] = 1.0f;
	matrix.m[3][3] = 1.0f;
	return matrix;
};

mat4x4 FaceBall::Matrix_MakeRotationX(float fAngleRad)
{
	mat4x4 matrix;
	matrix.m[0][0] = 1.0f;
	matrix.m[1][1] = cosf(fAngleRad);
	matrix.m[1][2] = sinf(fAngleRad);
	matrix.m[2][1] = -sinf(fAngleRad);
	matrix.m[2][2] = cosf(fAngleRad);
	matrix.m[3][3] = 1.0f;
	return matrix;
}

mat4x4 FaceBall::Matrix_MakeRotationY(float fAngleRad)
{
	mat4x4 matrix;
	matrix.m[0][0] = cosf(fAngleRad);
	matrix.m[0][2] = sinf(fAngleRad);
	matrix.m[2][0] = -sinf(fAngleRad);
	matrix.m[1][1] = 1.0f;
	matrix.m[2][2] = cosf(fAngleRad);
	matrix.m[3][3] = 1.0f;
	return matrix;
}

mat4x4 FaceBall::Matrix_MakeRotationZ(float fAngleRad)
{
	mat4x4 matrix;
	matrix.m[0][0] = cosf(fAngleRad);
	matrix.m[0][1] = sinf(fAngleRad);
	matrix.m[1][0] = -sinf(fAngleRad);
	matrix.m[1][1] = cosf(fAngleRad);
	matrix.m[2][2] = 1.0f;
	matrix.m[3][3] = 1.0f;
	return matrix;
}

mat4x4 FaceBall::Matrix_MakeTranslation(float x, float y, float z)
{
	mat4x4 matrix;
	matrix.m[0][0] = 1.0f;
	matrix.m[1][1] = 1.0f;
	matrix.m[2][2] = 1.0f;
	matrix.m[3][3] = 1.0f;
	matrix.m[3][0] = x;
	matrix.m[3][1] = y;
	matrix.m[3][2] = z;
	return matrix;
}

mat4x4 FaceBall::Matrix_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar)
{
	float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f);
	mat4x4 matrix;
	matrix.m[0][0] = fAspectRatio * fFovRad;
	matrix.m[1][1] = fFovRad;
	matrix.m[2][2] = fFar / (fFar - fNear);
	matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear);
	matrix.m[2][3] = 1.0f;
	matrix.m[3][3] = 0.0f;
	return matrix;
}

mat4x4 FaceBall::Matrix_MultiplyMatrix(mat4x4& m1, mat4x4& m2)
{
	mat4x4 matrix;
	for (int c = 0; c < 4; c++)
		for (int r = 0; r < 4; r++)
			matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c];
	return matrix;
}

mat4x4 FaceBall::Matrix_PointAt(vec3d& pos, vec3d& target, vec3d& up)
{
	// Calculate new forward direction
	vec3d newForward = Vector_Sub(target, pos);
	newForward = Vector_Normalise(newForward);

	// Calculate new Up direction
	vec3d a = Vector_Mul(newForward, Vector_DotProduct(up, newForward));
	vec3d newUp = Vector_Sub(up, a);
	newUp = Vector_Normalise(newUp);

	// New Right direction is easy, its just cross product
	vec3d newRight = Vector_CrossProduct(newUp, newForward);

	// Construct Dimensioning and Translation Matrix
	mat4x4 matrix;
	matrix.m[0][0] = newRight.x;	matrix.m[0][1] = newRight.y;	matrix.m[0][2] = newRight.z;	matrix.m[0][3] = 0.0f;
	matrix.m[1][0] = newUp.x;		matrix.m[1][1] = newUp.y;		matrix.m[1][2] = newUp.z;		matrix.m[1][3] = 0.0f;
	matrix.m[2][0] = newForward.x;	matrix.m[2][1] = newForward.y;	matrix.m[2][2] = newForward.z;	matrix.m[2][3] = 0.0f;
	matrix.m[3][0] = pos.x;			matrix.m[3][1] = pos.y;			matrix.m[3][2] = pos.z;			matrix.m[3][3] = 1.0f;
	return matrix;

}

mat4x4 FaceBall::Matrix_QuickInverse(mat4x4& m) // Only for Rotation/Translation Matrices
{
	mat4x4 matrix;
	matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f;
	matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f;
	matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f;
	matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]);
	matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]);
	matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]);
	matrix.m[3][3] = 1.0f;
	return matrix;
}

vec3d FaceBall::Vector_Add(vec3d& v1, vec3d& v2)
{
	return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
}

vec3d FaceBall::Vector_Sub(vec3d& v1, vec3d& v2)
{
	return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
}

vec3d FaceBall::Vector_Mul(vec3d& v1, float k)
{
	return { v1.x * k, v1.y * k, v1.z * k };
}

vec3d FaceBall::Vector_Div(vec3d& v1, float k)
{
	return { v1.x / k, v1.y / k, v1.z / k };
}

float FaceBall::Vector_DotProduct(vec3d& v1, vec3d& v2)
{
	return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

float FaceBall::Vector_Length(vec3d& v)
{
	return sqrtf(Vector_DotProduct(v, v));
}

vec3d FaceBall::Vector_Normalise(vec3d& v)
{
	float l = Vector_Length(v);
	return { v.x / l, v.y / l, v.z / l };
}

vec3d FaceBall::Vector_CrossProduct(vec3d& v1, vec3d& v2)
{
	vec3d v;
	v.x = v1.y * v2.z - v1.z * v2.y;
	v.y = v1.z * v2.x - v1.x * v2.z;
	v.z = v1.x * v2.y - v1.y * v2.x;
	return v;
}

vec3d FaceBall::Vector_IntersectPlane(vec3d& plane_p, vec3d& plane_n, vec3d& lineStart, vec3d& lineEnd, float& t)
{
	plane_n = Vector_Normalise(plane_n);
	float plane_d = -Vector_DotProduct(plane_n, plane_p);
	float ad = Vector_DotProduct(lineStart, plane_n);
	float bd = Vector_DotProduct(lineEnd, plane_n);
	t = (-plane_d - ad) / (bd - ad);
	vec3d lineStartToEnd = Vector_Sub(lineEnd, lineStart);
	vec3d lineToIntersect = Vector_Mul(lineStartToEnd, t);
	return Vector_Add(lineStart, lineToIntersect);
}


int FaceBall::Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, Triangle& in_tri, Triangle& out_tri1, Triangle& out_tri2)
{
	// Make sure plane normal is indeed normal
	plane_n = Vector_Normalise(plane_n);

	// Return signed shortest distance from point to plane, plane normal must be normalised
	auto dist = [&](vec3d& p)
	{
		vec3d n = Vector_Normalise(p);
		return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Vector_DotProduct(plane_n, plane_p));
	};

	// Create two temporary storage arrays to classify points either side of plane
	// If distance sign is positive, point lies on "inside" of plane
	vec3d* inside_points[3];  int nInsidePointCount = 0;
	vec3d* outside_points[3]; int nOutsidePointCount = 0;
	vec2d* inside_tex[3]; int nInsideTexCount = 0;
	vec2d* outside_tex[3]; int nOutsideTexCount = 0;
	Pixel* inside_col[3]; int nInsideColCount = 0;
	Pixel* outside_col[3]; int nOutsideColCount = 0;


	// Get signed distance of each point in triangle to plane
	float d0 = dist(in_tri.p[0]);
	float d1 = dist(in_tri.p[1]);
	float d2 = dist(in_tri.p[2]);

	if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.uv[0]; inside_col[nInsideColCount++] = &in_tri.col[0]; }
	else {
		outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.uv[0]; outside_col[nOutsideColCount++] = &in_tri.col[0];
	}
	if (d1 >= 0) {
		inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.uv[1]; inside_col[nInsideColCount++] = &in_tri.col[1];
	}
	else {
		outside_points[nOutsidePointCount++] = &in_tri.p[1];  outside_tex[nOutsideTexCount++] = &in_tri.uv[1]; outside_col[nOutsideColCount++] = &in_tri.col[1];
	}
	if (d2 >= 0) {
		inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.uv[2]; inside_col[nInsideColCount++] = &in_tri.col[2];
	}
	else {
		outside_points[nOutsidePointCount++] = &in_tri.p[2];  outside_tex[nOutsideTexCount++] = &in_tri.uv[2]; outside_col[nOutsideColCount++] = &in_tri.col[2];
	}

	// Now classify triangle points, and break the input triangle into
	// smaller output triangles if required. There are four possible
	// outcomes...

	if (nInsidePointCount == 0)
	{
		// All points lie on the outside of plane, so clip whole triangle
		// It ceases to exist

		return 0; // No returned triangles are valid
	}

	if (nInsidePointCount == 3)
	{
		// All points lie on the inside of plane, so do nothing
		// and allow the triangle to simply pass through
		out_tri1 = in_tri;

		return 1; // Just the one returned original triangle is valid
	}

	if (nInsidePointCount == 1 && nOutsidePointCount == 2)
	{
		// Triangle should be clipped. As two points lie outside
		// the plane, the triangle simply becomes a smaller triangle

		// Copy appearance info to new triangle
		out_tri1.col[0] = in_tri.col[0];
		out_tri1.col[1] = in_tri.col[1];
		out_tri1.col[2] = in_tri.col[2];
		out_tri1.tex = in_tri.tex;

		// The inside point is valid, so keep that...
		out_tri1.p[0] = *inside_points[0];
		out_tri1.uv[0] = *inside_tex[0];
		out_tri1.col[0] = *inside_col[0];

		// but the two new points are at the locations where the
		// original sides of the triangle (lines) intersect with the plane
		float t;
		out_tri1.p[1] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t);
		out_tri1.uv[1].u = t * (outside_tex[0]->u - inside_tex[0]->u) + inside_tex[0]->u;
		out_tri1.uv[1].v = t * (outside_tex[0]->v - inside_tex[0]->v) + inside_tex[0]->v;
		out_tri1.col[1].r = t * (outside_col[0]->r - inside_col[0]->r) + inside_col[0]->r;
		out_tri1.col[1].g = t * (outside_col[0]->g - inside_col[0]->g) + inside_col[0]->g;
		out_tri1.col[1].b = t * (outside_col[0]->b - inside_col[0]->b) + inside_col[0]->b;
		//out_tri1.col[1].a = t * (outside_col[0]->a - inside_col[0]->a) + inside_col[0]->a;
		out_tri1.uv[1].w = t * (outside_tex[0]->w - inside_tex[0]->w) + inside_tex[0]->w;

		out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t);
		out_tri1.uv[2].u = t * (outside_tex[1]->u - inside_tex[0]->u) + inside_tex[0]->u;
		out_tri1.uv[2].v = t * (outside_tex[1]->v - inside_tex[0]->v) + inside_tex[0]->v;
		out_tri1.col[2].r = t * (outside_col[1]->r - inside_col[0]->r) + inside_col[0]->r;
		out_tri1.col[2].g = t * (outside_col[1]->g - inside_col[0]->g) + inside_col[0]->g;
		out_tri1.col[2].b = t * (outside_col[1]->b - inside_col[0]->b) + inside_col[0]->b;
		//out_tri1.col[2].a = t * (outside_col[1]->a - inside_col[0]->a) + inside_col[0]->a;
		out_tri1.uv[2].w = t * (outside_tex[1]->w - inside_tex[0]->w) + inside_tex[0]->w;

		return 1; // Return the newly formed single triangle
	}

	if (nInsidePointCount == 2 && nOutsidePointCount == 1)
	{
		// Triangle should be clipped. As two points lie inside the plane,
		// the clipped triangle becomes a "quad". Fortunately, we can
		// represent a quad with two new triangles

		// Copy appearance info to new triangles
		out_tri1.col[0] = in_tri.col[0];
		out_tri2.col[0] = in_tri.col[0];
		out_tri1.col[1] = in_tri.col[1];
		out_tri2.col[1] = in_tri.col[1];
		out_tri1.col[2] = in_tri.col[2];
		out_tri2.col[2] = in_tri.col[2];
		out_tri1.tex = in_tri.tex;
		out_tri2.tex = in_tri.tex;

		// The first triangle consists of the two inside points and a new
		// point determined by the location where one side of the triangle
		// intersects with the plane
		out_tri1.p[0] = *inside_points[0];
		out_tri1.p[1] = *inside_points[1];
		out_tri1.uv[0] = *inside_tex[0];
		out_tri1.uv[1] = *inside_tex[1];
		out_tri1.col[0] = *inside_col[0];
		out_tri1.col[1] = *inside_col[1];

		float t;
		out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t);
		out_tri1.uv[2].u = t * (outside_tex[0]->u - inside_tex[0]->u) + inside_tex[0]->u;
		out_tri1.uv[2].v = t * (outside_tex[0]->v - inside_tex[0]->v) + inside_tex[0]->v;
		out_tri1.col[2].r = t * (outside_col[0]->r - inside_col[0]->r) + inside_col[0]->r;
		out_tri1.col[2].g = t * (outside_col[0]->g - inside_col[0]->g) + inside_col[0]->g;
		out_tri1.col[2].b = t * (outside_col[0]->b - inside_col[0]->b) + inside_col[0]->b;
		//out_tri1.col[2].a = t * (outside_col[0]->a - inside_col[0]->a) + inside_col[0]->a;
		out_tri1.uv[2].w = t * (outside_tex[0]->w - inside_tex[0]->w) + inside_tex[0]->w;

		// The second triangle is composed of one of he inside points, a
		// new point determined by the intersection of the other side of the
		// triangle and the plane, and the newly created point above
		out_tri2.p[0] = *inside_points[1];
		out_tri2.uv[0] = *inside_tex[1];
		out_tri2.col[0] = *inside_col[1];
		out_tri2.p[1] = out_tri1.p[2];
		out_tri2.uv[1] = out_tri1.uv[2];
		out_tri2.col[1] = out_tri1.col[2];
		out_tri2.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t);
		out_tri2.uv[2].u = t * (outside_tex[0]->u - inside_tex[1]->u) + inside_tex[1]->u;
		out_tri2.uv[2].v = t * (outside_tex[0]->v - inside_tex[1]->v) + inside_tex[1]->v;
		out_tri1.col[2].r = t * (outside_col[0]->r - inside_col[1]->r) + inside_col[1]->r;
		out_tri1.col[2].g = t * (outside_col[0]->g - inside_col[1]->g) + inside_col[1]->g;
		out_tri1.col[2].b = t * (outside_col[0]->b - inside_col[1]->b) + inside_col[1]->b;
		//out_tri1.col[2].a = t * (outside_col[0]->a - inside_col[1]->a) + inside_col[1]->a;
		out_tri2.uv[2].w = t * (outside_tex[0]->w - inside_tex[1]->w) + inside_tex[1]->w;
		return 2; // Return two newly formed triangles which form a quad
	}
}

void FaceBall::RenderBulletMesh(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Bullet& b,bool translucent) {
	for (auto& tri : b.mesh.tris) {
		tri.col[0] = b.col;
		tri.col[1] = b.col;
		tri.col[2] = b.col;
	}
	RenderMesh(matView, vecTrianglesToRaster, b,translucent);
}

void FaceBall::RenderMesh(mat4x4&matView,std::vector<Triangle>&vecTrianglesToRaster, Object&o,bool translucent) {
	for (auto& tri : o.mesh.tris) {
		if (translucent&&tri.tex != exit_wall_tex && tri.tex != bullet_tex ||!translucent&&(tri.tex==exit_wall_tex || tri.tex == bullet_tex)) { continue; }
		Triangle triProjected, triTransformed, triViewed;
		mat4x4 localMat = Matrix_MakeIdentity();
		mat4x4 rotMat = Matrix_MakeRotationY(o.rot);
		localMat = Matrix_MultiplyMatrix(localMat, rotMat);
		mat4x4 matTrans = Matrix_MakeTranslation(o.pos.x, o.pos.y, o.pos.z);
		localMat = Matrix_MultiplyMatrix(localMat, matTrans);

		triTransformed.p[0] = Matrix_MultiplyVector(localMat, tri.p[0]);
		triTransformed.p[1] = Matrix_MultiplyVector(localMat, tri.p[1]);
		triTransformed.p[2] = Matrix_MultiplyVector(localMat, tri.p[2]);
		triTransformed.uv[0] = tri.uv[0];
		triTransformed.uv[1] = tri.uv[1];
		triTransformed.uv[2] = tri.uv[2];
		triTransformed.col[0] = tri.col[0];
		triTransformed.col[1] = tri.col[1];
		triTransformed.col[2] = tri.col[2];
		triTransformed.tex = tri.tex;

		vec3d normal, line1, line2;
		line1 = Vector_Sub(triTransformed.p[1], triTransformed.p[0]);
		line2 = Vector_Sub(triTransformed.p[2], triTransformed.p[0]);

		normal = Vector_CrossProduct(line1, line2);
		normal = Vector_Normalise(normal);

		vec3d vCameraRay = Vector_Sub(triTransformed.p[0], freeRoam ? freeRoamCamera : player.GetPos());

		if (Vector_DotProduct(normal, vCameraRay) < 0) {
			vec3d light_dir = Vector_Mul(vLookDir, -1);
			light_dir = Vector_Normalise(light_dir);

			float dp = std::max(0.7f, Vector_DotProduct(light_dir, normal));

			triViewed.p[0] = Matrix_MultiplyVector(matView, triTransformed.p[0]);
			triViewed.p[1] = Matrix_MultiplyVector(matView, triTransformed.p[1]);
			triViewed.p[2] = Matrix_MultiplyVector(matView, triTransformed.p[2]);
			triViewed.uv[0] = triTransformed.uv[0];
			triViewed.uv[1] = triTransformed.uv[1];
			triViewed.uv[2] = triTransformed.uv[2];
			triViewed.col[0] = Pixel(triTransformed.col[0].r * dp * dp, triTransformed.col[0].g * dp * dp, triTransformed.col[0].b * dp * dp);
			triViewed.col[1] = Pixel(triTransformed.col[1].r * dp * dp, triTransformed.col[1].g * dp * dp, triTransformed.col[1].b * dp * dp);
			triViewed.col[2] = Pixel(triTransformed.col[2].r * dp * dp, triTransformed.col[2].g * dp * dp, triTransformed.col[2].b * dp * dp);
			Pixel originalCol[3] = { triViewed.col[0],triViewed.col[1],triViewed.col[2] };
			float dist = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[0].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[0].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[0].z, 2));
			float dist2 = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[1].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[1].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[1].z, 2));
			float dist3 = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[2].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[2].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[2].z, 2));
			float colorMult = dist > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist + PI / 2);
			float colorMult2 = dist2 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist2 + PI / 2);
			float colorMult3 = dist3 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist3 + PI / 2);
			triViewed.col[0] = Pixel(triViewed.col[0].r * colorMult, triViewed.col[0].g * colorMult, triViewed.col[0].b * colorMult);
			triViewed.col[1] = Pixel(triViewed.col[1].r * colorMult2, triViewed.col[1].g * colorMult2, triViewed.col[1].b * colorMult2);
			triViewed.col[2] = Pixel(triViewed.col[2].r * colorMult3, triViewed.col[2].g * colorMult3, triViewed.col[2].b * colorMult3);
			triViewed.tex = triTransformed.tex;

			for (Bullet& b : bullets) {
				float dist = std::sqrtf(std::powf(b.pos.x - triTransformed.p[0].x, 2) + std::powf(b.pos.y - triTransformed.p[0].y, 2) + std::powf(b.pos.z - triTransformed.p[0].z, 2));
				float dist2 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[1].x, 2) + std::powf(b.pos.y - triTransformed.p[1].y, 2) + std::powf(b.pos.z - triTransformed.p[1].z, 2));
				float dist3 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[2].x, 2) + std::powf(b.pos.y - triTransformed.p[2].y, 2) + std::powf(b.pos.z - triTransformed.p[2].z, 2));
				float colorMult = (dist < 2 ? std::sinf(0.75 * dist + PI / 2) * 4 :1);
				float colorMult2 = (dist2 < 2 ? std::sinf(0.75 * dist2 + PI / 2) * 4 : 1);
				float colorMult3 = (dist3 < 2 ? std::sinf(0.75 * dist3 + PI / 2) * 4 : 1);
				Pixel lightCol = b.col / 2 + Pixel{ 128, 128, 128 };
				if (dist < 2) {triViewed.col[0] = Pixel(std::min(255, std::max((int)originalCol[0].r, (int)(originalCol[0].r * colorMult / float(255.f/lightCol.r)))), std::min(255, std::max((int)originalCol[0].g, (int)(originalCol[0].g * colorMult / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[0].b, (int)(originalCol[0].b * colorMult / float(255.f / lightCol.b)))));}
				if (dist2 < 2) {triViewed.col[1] = Pixel(std::min(255, std::max((int)originalCol[1].r, (int)(originalCol[1].r * colorMult2 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[1].g, (int)(originalCol[1].g * colorMult2 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[1].b, (int)(originalCol[1].b * colorMult2 / float(255.f / lightCol.b)))));}
				if (dist3 < 2) {triViewed.col[2] = Pixel(std::min(255, std::max((int)originalCol[2].r, (int)(originalCol[2].r * colorMult3 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[2].g, (int)(originalCol[2].g * colorMult3 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[2].b, (int)(originalCol[2].b * colorMult3 / float(255.f / lightCol.b)))));}
			}
			//triViewed.col = triTransformed.col;

			int nClippedTriangles = 0;
			Triangle clipped[2];
			nClippedTriangles = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]);

			for (int n = 0; n < nClippedTriangles; n++) {
				// Project triangles from 3D --> 2D
				triProjected.p[0] = Matrix_MultiplyVector(matProj, clipped[n].p[0]);
				triProjected.p[1] = Matrix_MultiplyVector(matProj, clipped[n].p[1]);
				triProjected.p[2] = Matrix_MultiplyVector(matProj, clipped[n].p[2]);
				triProjected.col[0] = clipped[n].col[0];
				triProjected.col[1] = clipped[n].col[1];
				triProjected.col[2] = clipped[n].col[2];
				triProjected.tex = clipped[n].tex;
				triProjected.uv[0] = clipped[n].uv[0];
				triProjected.uv[1] = clipped[n].uv[1];
				triProjected.uv[2] = clipped[n].uv[2];
				triProjected.uv[0].u = triProjected.uv[0].u / triProjected.p[0].w;
				triProjected.uv[1].u = triProjected.uv[1].u / triProjected.p[1].w;
				triProjected.uv[2].u = triProjected.uv[2].u / triProjected.p[2].w;

				triProjected.uv[0].v = triProjected.uv[0].v / triProjected.p[0].w;
				triProjected.uv[1].v = triProjected.uv[1].v / triProjected.p[1].w;
				triProjected.uv[2].v = triProjected.uv[2].v / triProjected.p[2].w;

				triProjected.uv[0].w = 1.0f / triProjected.p[0].w;
				triProjected.uv[1].w = 1.0f / triProjected.p[1].w;
				triProjected.uv[2].w = 1.0f / triProjected.p[2].w;


				triProjected.p[0] = Vector_Div(triProjected.p[0], triProjected.p[0].w);
				triProjected.p[1] = Vector_Div(triProjected.p[1], triProjected.p[1].w);
				triProjected.p[2] = Vector_Div(triProjected.p[2], triProjected.p[2].w);

				triProjected.p[0].x *= -1.0f;
				triProjected.p[1].x *= -1.0f;
				triProjected.p[2].x *= -1.0f;
				triProjected.p[0].y *= -1.0f;
				triProjected.p[1].y *= -1.0f;
				triProjected.p[2].y *= -1.0f;

				// Scale into view
				vec3d vOffsetView = { 1,1,0 };
				triProjected.p[0] = Vector_Add(triProjected.p[0], vOffsetView);
				triProjected.p[1] = Vector_Add(triProjected.p[1], vOffsetView);
				triProjected.p[2] = Vector_Add(triProjected.p[2], vOffsetView);
				triProjected.p[0].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[0].y *= 0.5f * (float)ScreenHeight();
				triProjected.p[1].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[1].y *= 0.5f * (float)ScreenHeight();
				triProjected.p[2].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[2].y *= 0.5f * (float)ScreenHeight();

				vecTrianglesToRaster.push_back(triProjected);
			}
		}
	}
}

void FaceBall::RenderPowerupMesh(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Powerup&p) {
	for (int i = 0; i < 2; i++) {
		for (auto& tri : p.mesh.tris) {
			Triangle triProjected, triTransformed, triViewed;
			mat4x4 localMat = Matrix_MakeIdentity();
			mat4x4 rotMat = Matrix_MakeRotationY(p.rot);
			if (i) {
				mat4x4 flipMat = Matrix_MakeRotationX(PI);
				rotMat = Matrix_MultiplyMatrix(rotMat, flipMat);
			}
			localMat = Matrix_MultiplyMatrix(localMat, rotMat);
			mat4x4 matTrans = Matrix_MakeTranslation(p.pos.x, p.pos.y+(i?p.opened?0.65f:0.375f:p.opened?-0.125f:0), p.pos.z);
			localMat = Matrix_MultiplyMatrix(localMat, matTrans);
			
			triTransformed.p[0] = Matrix_MultiplyVector(localMat, tri.p[0]);
			triTransformed.p[1] = Matrix_MultiplyVector(localMat, tri.p[1]);
			triTransformed.p[2] = Matrix_MultiplyVector(localMat, tri.p[2]);
			triTransformed.uv[0] = tri.uv[0];
			triTransformed.uv[1] = tri.uv[1];
			triTransformed.uv[2] = tri.uv[2];
			triTransformed.col[0] = tri.col[0] * p.colorCycle;
			triTransformed.col[1] = tri.col[1] * p.colorCycle;
			triTransformed.col[2] = tri.col[2] * p.colorCycle;
			triTransformed.tex = tri.tex;

			vec3d normal, line1, line2;
			line1 = Vector_Sub(triTransformed.p[1], triTransformed.p[0]);
			line2 = Vector_Sub(triTransformed.p[2], triTransformed.p[0]);

			normal = Vector_CrossProduct(line1, line2);
			normal = Vector_Normalise(normal);

			vec3d vCameraRay = Vector_Sub(triTransformed.p[0], freeRoam ? freeRoamCamera : player.GetPos());

			if (Vector_DotProduct(normal, vCameraRay) < 0) {
				vec3d light_dir = Vector_Mul(vLookDir, -1);
				light_dir = Vector_Normalise(light_dir);

				float dp = std::max(0.7f, Vector_DotProduct(light_dir, normal));

				triViewed.p[0] = Matrix_MultiplyVector(matView, triTransformed.p[0]);
				triViewed.p[1] = Matrix_MultiplyVector(matView, triTransformed.p[1]);
				triViewed.p[2] = Matrix_MultiplyVector(matView, triTransformed.p[2]);
				triViewed.uv[0] = triTransformed.uv[0];
				triViewed.uv[1] = triTransformed.uv[1];
				triViewed.uv[2] = triTransformed.uv[2];
				triViewed.col[0] = Pixel(triTransformed.col[0].r * dp * dp, triTransformed.col[0].g * dp * dp, triTransformed.col[0].b * dp * dp);
				triViewed.col[1] = Pixel(triTransformed.col[1].r * dp * dp, triTransformed.col[1].g * dp * dp, triTransformed.col[1].b * dp * dp);
				triViewed.col[2] = Pixel(triTransformed.col[2].r * dp * dp, triTransformed.col[2].g * dp * dp, triTransformed.col[2].b * dp * dp);
				Pixel originalCol[3] = { triViewed.col[0],triViewed.col[1],triViewed.col[2] };
				float dist = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[0].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[0].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[0].z, 2));
				float dist2 = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[1].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[1].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[1].z, 2));
				float dist3 = std::sqrtf(std::powf((freeRoam ? freeRoamCamera : player.GetPos()).x - triTransformed.p[2].x, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).y - triTransformed.p[2].y, 2) + std::powf((freeRoam ? freeRoamCamera : player.GetPos()).z - triTransformed.p[2].z, 2));
				float colorMult = dist > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist + PI / 2);
				float colorMult2 = dist2 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist2 + PI / 2);
				float colorMult3 = dist3 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist3 + PI / 2);
				triViewed.col[0] = Pixel(triViewed.col[0].r * colorMult, triViewed.col[0].g * colorMult, triViewed.col[0].b * colorMult);
				triViewed.col[1] = Pixel(triViewed.col[1].r * colorMult2, triViewed.col[1].g * colorMult2, triViewed.col[1].b * colorMult2);
				triViewed.col[2] = Pixel(triViewed.col[2].r * colorMult3, triViewed.col[2].g * colorMult3, triViewed.col[2].b * colorMult3);
				triViewed.tex = triTransformed.tex;

				for (Bullet& b : bullets) {
					float dist = std::sqrtf(std::powf(b.pos.x - triTransformed.p[0].x, 2) + std::powf(b.pos.y - triTransformed.p[0].y, 2) + std::powf(b.pos.z - triTransformed.p[0].z, 2));
					float dist2 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[1].x, 2) + std::powf(b.pos.y - triTransformed.p[1].y, 2) + std::powf(b.pos.z - triTransformed.p[1].z, 2));
					float dist3 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[2].x, 2) + std::powf(b.pos.y - triTransformed.p[2].y, 2) + std::powf(b.pos.z - triTransformed.p[2].z, 2));
					float colorMult = (dist < 2 ? std::sinf(0.75 * dist + PI / 2) * 4 : 1);
					float colorMult2 = (dist2 < 2 ? std::sinf(0.75 * dist2 + PI / 2) * 4 : 1);
					float colorMult3 = (dist3 < 2 ? std::sinf(0.75 * dist3 + PI / 2) * 4 : 1);
					Pixel lightCol = b.col / 2 + Pixel{ 128, 128, 128 };
					if (dist < 2) { triViewed.col[0] = Pixel(std::min(255, std::max((int)originalCol[0].r, (int)(originalCol[0].r * colorMult / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[0].g, (int)(originalCol[0].g * colorMult / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[0].b, (int)(originalCol[0].b * colorMult / float(255.f / lightCol.b))))); }
					if (dist2 < 2) { triViewed.col[1] = Pixel(std::min(255, std::max((int)originalCol[1].r, (int)(originalCol[1].r * colorMult2 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[1].g, (int)(originalCol[1].g * colorMult2 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[1].b, (int)(originalCol[1].b * colorMult2 / float(255.f / lightCol.b))))); }
					if (dist3 < 2) { triViewed.col[2] = Pixel(std::min(255, std::max((int)originalCol[2].r, (int)(originalCol[2].r * colorMult3 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[2].g, (int)(originalCol[2].g * colorMult3 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[2].b, (int)(originalCol[2].b * colorMult3 / float(255.f / lightCol.b))))); }
				}
				//triViewed.col = triTransformed.col;

				int nClippedTriangles = 0;
				Triangle clipped[2];
				nClippedTriangles = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]);

				for (int n = 0; n < nClippedTriangles; n++) {
					// Project triangles from 3D --> 2D
					triProjected.p[0] = Matrix_MultiplyVector(matProj, clipped[n].p[0]);
					triProjected.p[1] = Matrix_MultiplyVector(matProj, clipped[n].p[1]);
					triProjected.p[2] = Matrix_MultiplyVector(matProj, clipped[n].p[2]);
					triProjected.col[0] = clipped[n].col[0];
					triProjected.col[1] = clipped[n].col[1];
					triProjected.col[2] = clipped[n].col[2];
					triProjected.tex = clipped[n].tex;
					triProjected.uv[0] = clipped[n].uv[0];
					triProjected.uv[1] = clipped[n].uv[1];
					triProjected.uv[2] = clipped[n].uv[2];
					triProjected.uv[0].u = triProjected.uv[0].u / triProjected.p[0].w;
					triProjected.uv[1].u = triProjected.uv[1].u / triProjected.p[1].w;
					triProjected.uv[2].u = triProjected.uv[2].u / triProjected.p[2].w;

					triProjected.uv[0].v = triProjected.uv[0].v / triProjected.p[0].w;
					triProjected.uv[1].v = triProjected.uv[1].v / triProjected.p[1].w;
					triProjected.uv[2].v = triProjected.uv[2].v / triProjected.p[2].w;

					triProjected.uv[0].w = 1.0f / triProjected.p[0].w;
					triProjected.uv[1].w = 1.0f / triProjected.p[1].w;
					triProjected.uv[2].w = 1.0f / triProjected.p[2].w;


					triProjected.p[0] = Vector_Div(triProjected.p[0], triProjected.p[0].w);
					triProjected.p[1] = Vector_Div(triProjected.p[1], triProjected.p[1].w);
					triProjected.p[2] = Vector_Div(triProjected.p[2], triProjected.p[2].w);

					triProjected.p[0].x *= -1.0f;
					triProjected.p[1].x *= -1.0f;
					triProjected.p[2].x *= -1.0f;
					triProjected.p[0].y *= -1.0f;
					triProjected.p[1].y *= -1.0f;
					triProjected.p[2].y *= -1.0f;

					// Scale into view
					vec3d vOffsetView = { 1,1,0 };
					triProjected.p[0] = Vector_Add(triProjected.p[0], vOffsetView);
					triProjected.p[1] = Vector_Add(triProjected.p[1], vOffsetView);
					triProjected.p[2] = Vector_Add(triProjected.p[2], vOffsetView);
					triProjected.p[0].x *= 0.5f * (float)ScreenWidth();
					triProjected.p[0].y *= 0.5f * (float)ScreenHeight();
					triProjected.p[1].x *= 0.5f * (float)ScreenWidth();
					triProjected.p[1].y *= 0.5f * (float)ScreenHeight();
					triProjected.p[2].x *= 0.5f * (float)ScreenWidth();
					triProjected.p[2].y *= 0.5f * (float)ScreenHeight();

					vecTrianglesToRaster.push_back(triProjected);
				}
			}
		}
	}
	if (p.opened) {
		Mesh mesh;
		float uv = ((int)p.type - 1) * 0.125f;
		mesh.tris.push_back({ {{-0.25,0,0},{0.25,0,0},{-0.25,0.5,0}},{{uv,1},{uv + 0.125f,1},{uv,0}},{WHITE,WHITE,WHITE},powerups_tex });
		mesh.tris.push_back({ {{-0.25,0.5,0},{0.25,0,0},{0.25,0.5,0}},{{uv,0},{uv + 0.125f,1},{uv + 0.125f,0}},{WHITE,WHITE,WHITE},powerups_tex });
		Object billboard = {
			mesh,p.pos,std::atan2f(player.GetPos().z - p.pos.z,player.GetPos().x - p.pos.x) - PI / 2,0.25f
		};
		RenderMesh(matView, vecTrianglesToRaster, billboard, false);
	}
}

void FaceBall::RenderMeshDeathScreen(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Object& o) {
	for (auto& tri : o.mesh.tris) {
		Triangle triProjected, triTransformed, triViewed;
		mat4x4 localMat = Matrix_MakeIdentity();
		mat4x4 rotMat = Matrix_MakeRotationY(o.rot);
		localMat = Matrix_MultiplyMatrix(localMat, rotMat);
		mat4x4 matTrans = Matrix_MakeTranslation(o.pos.x, o.pos.y, o.pos.z);
		localMat = Matrix_MultiplyMatrix(localMat, matTrans);

		triTransformed.p[0] = Matrix_MultiplyVector(localMat, tri.p[0]);
		triTransformed.p[1] = Matrix_MultiplyVector(localMat, tri.p[1]);
		triTransformed.p[2] = Matrix_MultiplyVector(localMat, tri.p[2]);
		triTransformed.uv[0] = tri.uv[0];
		triTransformed.uv[1] = tri.uv[1];
		triTransformed.uv[2] = tri.uv[2];
		triTransformed.col[0] = tri.col[0];
		triTransformed.col[1] = tri.col[1];
		triTransformed.col[2] = tri.col[2];
		triTransformed.tex = tri.tex;

		vec3d normal, line1, line2;
		line1 = Vector_Sub(triTransformed.p[1], triTransformed.p[0]);
		line2 = Vector_Sub(triTransformed.p[2], triTransformed.p[0]);

		normal = Vector_CrossProduct(line1, line2);
		normal = Vector_Normalise(normal);
		vec3d camera = { 0.758110702,0.222716771,3.00489759 };
		vec3d vCameraRay = Vector_Sub(triTransformed.p[0], camera);

		if (Vector_DotProduct(normal, vCameraRay) < 0) {
			vec3d light_dir = Vector_Mul(vLookDir, -1);
			light_dir = Vector_Normalise(light_dir);

			float dp = std::max(0.7f, Vector_DotProduct(light_dir, normal));

			triViewed.p[0] = Matrix_MultiplyVector(matView, triTransformed.p[0]);
			triViewed.p[1] = Matrix_MultiplyVector(matView, triTransformed.p[1]);
			triViewed.p[2] = Matrix_MultiplyVector(matView, triTransformed.p[2]);
			triViewed.uv[0] = triTransformed.uv[0];
			triViewed.uv[1] = triTransformed.uv[1];
			triViewed.uv[2] = triTransformed.uv[2];
			triViewed.col[0] = Pixel(triTransformed.col[0].r * dp * dp, triTransformed.col[0].g * dp * dp, triTransformed.col[0].b * dp * dp);
			triViewed.col[1] = Pixel(triTransformed.col[1].r * dp * dp, triTransformed.col[1].g * dp * dp, triTransformed.col[1].b * dp * dp);
			triViewed.col[2] = Pixel(triTransformed.col[2].r * dp * dp, triTransformed.col[2].g * dp * dp, triTransformed.col[2].b * dp * dp);
			Pixel originalCol[3] = { triViewed.col[0],triViewed.col[1],triViewed.col[2] };
			float dist = std::sqrtf(std::powf(camera.x - triTransformed.p[0].x, 2) + std::powf(camera.y - triTransformed.p[0].y, 2) + std::powf(camera.z - triTransformed.p[0].z, 2));
			float dist2 = std::sqrtf(std::powf(camera.x - triTransformed.p[1].x, 2) + std::powf(camera.y - triTransformed.p[1].y, 2) + std::powf(camera.z - triTransformed.p[1].z, 2));
			float dist3 = std::sqrtf(std::powf(camera.x - triTransformed.p[2].x, 2) + std::powf(camera.y - triTransformed.p[2].y, 2) + std::powf(camera.z - triTransformed.p[2].z, 2));
			float colorMult = dist > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist + PI / 2);
			float colorMult2 = dist2 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist2 + PI / 2);
			float colorMult3 = dist3 > 5 * PI / 3 ? 0 : std::sinf(0.3 * dist3 + PI / 2);
			triViewed.col[0] = Pixel(triViewed.col[0].r * colorMult, triViewed.col[0].g * colorMult, triViewed.col[0].b * colorMult);
			triViewed.col[1] = Pixel(triViewed.col[1].r * colorMult2, triViewed.col[1].g * colorMult2, triViewed.col[1].b * colorMult2);
			triViewed.col[2] = Pixel(triViewed.col[2].r * colorMult3, triViewed.col[2].g * colorMult3, triViewed.col[2].b * colorMult3);
			triViewed.tex = triTransformed.tex;

			for (Bullet& b : bullets) {
				float dist = std::sqrtf(std::powf(b.pos.x - triTransformed.p[0].x, 2) + std::powf(b.pos.y - triTransformed.p[0].y, 2) + std::powf(b.pos.z - triTransformed.p[0].z, 2));
				float dist2 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[1].x, 2) + std::powf(b.pos.y - triTransformed.p[1].y, 2) + std::powf(b.pos.z - triTransformed.p[1].z, 2));
				float dist3 = std::sqrtf(std::powf(b.pos.x - triTransformed.p[2].x, 2) + std::powf(b.pos.y - triTransformed.p[2].y, 2) + std::powf(b.pos.z - triTransformed.p[2].z, 2));
				float colorMult = (dist < 2 ? std::sinf(0.75 * dist + PI / 2) * 4 : 1);
				float colorMult2 = (dist2 < 2 ? std::sinf(0.75 * dist2 + PI / 2) * 4 : 1);
				float colorMult3 = (dist3 < 2 ? std::sinf(0.75 * dist3 + PI / 2) * 4 : 1);
				Pixel lightCol = b.col / 2 + Pixel{ 128, 128, 128 };
				if (dist < 2) { triViewed.col[0] = Pixel(std::min(255, std::max((int)originalCol[0].r, (int)(originalCol[0].r * colorMult / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[0].g, (int)(originalCol[0].g * colorMult / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[0].b, (int)(originalCol[0].b * colorMult / float(255.f / lightCol.b))))); }
				if (dist2 < 2) { triViewed.col[1] = Pixel(std::min(255, std::max((int)originalCol[1].r, (int)(originalCol[1].r * colorMult2 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[1].g, (int)(originalCol[1].g * colorMult2 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[1].b, (int)(originalCol[1].b * colorMult2 / float(255.f / lightCol.b))))); }
				if (dist3 < 2) { triViewed.col[2] = Pixel(std::min(255, std::max((int)originalCol[2].r, (int)(originalCol[2].r * colorMult3 / float(255.f / lightCol.r)))), std::min(255, std::max((int)originalCol[2].g, (int)(originalCol[2].g * colorMult3 / float(255.f / lightCol.g)))), std::min(255, std::max((int)originalCol[2].b, (int)(originalCol[2].b * colorMult3 / float(255.f / lightCol.b))))); }
			}
			//triViewed.col = triTransformed.col;

			int nClippedTriangles = 0;
			Triangle clipped[2];
			nClippedTriangles = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]);

			for (int n = 0; n < nClippedTriangles; n++) {
				// Project triangles from 3D --> 2D
				triProjected.p[0] = Matrix_MultiplyVector(matProj, clipped[n].p[0]);
				triProjected.p[1] = Matrix_MultiplyVector(matProj, clipped[n].p[1]);
				triProjected.p[2] = Matrix_MultiplyVector(matProj, clipped[n].p[2]);
				triProjected.col[0] = clipped[n].col[0];
				triProjected.col[1] = clipped[n].col[1];
				triProjected.col[2] = clipped[n].col[2];
				triProjected.tex = clipped[n].tex;
				triProjected.uv[0] = clipped[n].uv[0];
				triProjected.uv[1] = clipped[n].uv[1];
				triProjected.uv[2] = clipped[n].uv[2];
				triProjected.uv[0].u = triProjected.uv[0].u / triProjected.p[0].w;
				triProjected.uv[1].u = triProjected.uv[1].u / triProjected.p[1].w;
				triProjected.uv[2].u = triProjected.uv[2].u / triProjected.p[2].w;

				triProjected.uv[0].v = triProjected.uv[0].v / triProjected.p[0].w;
				triProjected.uv[1].v = triProjected.uv[1].v / triProjected.p[1].w;
				triProjected.uv[2].v = triProjected.uv[2].v / triProjected.p[2].w;

				triProjected.uv[0].w = 1.0f / triProjected.p[0].w;
				triProjected.uv[1].w = 1.0f / triProjected.p[1].w;
				triProjected.uv[2].w = 1.0f / triProjected.p[2].w;


				triProjected.p[0] = Vector_Div(triProjected.p[0], triProjected.p[0].w);
				triProjected.p[1] = Vector_Div(triProjected.p[1], triProjected.p[1].w);
				triProjected.p[2] = Vector_Div(triProjected.p[2], triProjected.p[2].w);

				triProjected.p[0].x *= -1.0f;
				triProjected.p[1].x *= -1.0f;
				triProjected.p[2].x *= -1.0f;
				triProjected.p[0].y *= -1.0f;
				triProjected.p[1].y *= -1.0f;
				triProjected.p[2].y *= -1.0f;

				// Scale into view
				vec3d vOffsetView = { 1,1,0 };
				triProjected.p[0] = Vector_Add(triProjected.p[0], vOffsetView);
				triProjected.p[1] = Vector_Add(triProjected.p[1], vOffsetView);
				triProjected.p[2] = Vector_Add(triProjected.p[2], vOffsetView);
				triProjected.p[0].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[0].y *= 0.5f * (float)ScreenHeight();
				triProjected.p[1].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[1].y *= 0.5f * (float)ScreenHeight();
				triProjected.p[2].x *= 0.5f * (float)ScreenWidth();
				triProjected.p[2].y *= 0.5f * (float)ScreenHeight();

				vecTrianglesToRaster.push_back(triProjected);
			}
		}
	}
}

void FaceBall::RenderWorld() {
	// Set up rotation matrices
	mat4x4 matRotZ, matRotX, matWorld;

	matRotZ = Matrix_MakeRotationZ(fTheta * 0.5f);
	matRotX = Matrix_MakeRotationX(fTheta);

	//matTrans = Matrix_MakeTranslation(0.0f, 0.0f, 0.0f);
	matWorld = Matrix_MakeIdentity();
	matWorld = Matrix_MultiplyMatrix(matRotZ, matRotX);
	//matWorld = Matrix_MultiplyMatrix(matWorld, matTrans);

	vec3d vUp = { 0,1,0 };
	vec3d vTarget = { 0,sinf(freeRoam?freeRoamCamera_pitch:pitch),cosf(freeRoam ? freeRoamCamera_pitch : pitch) };
	mat4x4 matCameraRot = Matrix_MakeRotationY((freeRoam?freeRoamCamera_yaw:fYaw)-PI/2);
	vLookDir = Matrix_MultiplyVector(matCameraRot, vTarget);
	vec3d playerCenter = { player.GetPos().x, player.GetPos().y, player.GetPos().z };
	vTarget = Vector_Add(freeRoam ? freeRoamCamera : playerCenter, vLookDir);
	mat4x4 matCamera = Matrix_PointAt(freeRoam?freeRoamCamera: playerCenter, vTarget, vUp);
	mat4x4 matView = Matrix_QuickInverse(matCamera);

	std::vector<Triangle>vecTrianglesToRaster;
	std::vector<Triangle>vecTrianglesToRasterTranslucent;

	// Draw Triangles
	for (auto& obj : objects) {
		RenderMesh(matView,vecTrianglesToRaster,obj);
	}
	RenderMesh(matView, vecTrianglesToRaster, exit);
	for (Triangle& t : exit.mesh.tris) {
		t.col[0] = t.col[1] = t.col[2] = {0,(uint8_t)(std::abs(std::sinf(2*PI*gameTimer)) * 255),0};
	}
	for (auto& enemy : enemies) {
		if (enemy.blinkingAmt > 0.4f) {
			RenderMesh(matView, vecTrianglesToRaster, enemy);
		}
	}
	for (auto& bullet : bullets) {
		RenderBulletMesh(matView, vecTrianglesToRasterTranslucent, bullet);
	}
	for (auto& powerup : powerups) {
		RenderPowerupMesh(matView, vecTrianglesToRasterTranslucent, powerup);
	}
	RenderMesh(matView, vecTrianglesToRaster, walls);
	RenderMesh(matView, vecTrianglesToRasterTranslucent, walls, true);

	//std::sort(vecTrianglesToRaster.begin(),vecTrianglesToRaster.end(),[](triangle&t1,triangle&t2){return (t1.p[0].z+t1.p[1].z+t1.p[2].z)/3.0f>(t2.p[0].z+t2.p[1].z+t2.p[2].z)/3.0f;});
	ClearBuffer(BLACK, true);
	triRenderCount = 0;
	for (auto& triToRaster : vecTrianglesToRaster) {
		Triangle clipped[2];
		std::list<Triangle>listTriangles;
		listTriangles.push_back(triToRaster);
		int nNewTriangles = 1;

		for (int p = 0; p < 4; p++)
		{
			int nTrisToAdd = 0;
			while (nNewTriangles > 0)
			{
				// Take triangle from front of queue
				Triangle test = listTriangles.front();
				listTriangles.pop_front();
				nNewTriangles--;

				// Clip it against a plane. We only need to test each
				// subsequent plane, against subsequent new triangles
				// as all triangles after a plane clip are guaranteed
				// to lie on the inside of the plane. I like how this
				// comment is almost completely and utterly justified
				switch (p)
				{
				case 0:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 1:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, (float)ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 2:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 3:	nTrisToAdd = Triangle_ClipAgainstPlane({ (float)ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				}

				// Clipping may yield a variable number of triangles, so
				// add these new ones to the back of the queue for subsequent
				// clipping against next planes
				for (int w = 0; w < nTrisToAdd; w++)
					listTriangles.push_back(clipped[w]);
			}
			nNewTriangles = listTriangles.size();
		}

		for (auto& t : listTriangles) {
			// Rasterize triangle
			SetDecalStructure(DecalStructure::LIST);
			SetDecalMode(DecalMode::NORMAL);
			DrawPolygonDecal(t.tex, {
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
				}, {
					{t.uv[0].u,t.uv[0].v},
					{t.uv[1].u,t.uv[1].v},
					{t.uv[2].u,t.uv[2].v},
				}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { t.col[0],t.col[1],t.col[2]});
			/*SetDecalMode(DecalMode::WIREFRAME);
			DrawPolygonDecal(nullptr,{
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
			},{
				{0,0},
				{0,0},
				{0,0},
			}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { WHITE,WHITE,WHITE});*/
			SetDecalStructure(DecalStructure::FAN);
			triRenderCount++;
		}
	}
	std::sort(vecTrianglesToRasterTranslucent.begin(), vecTrianglesToRasterTranslucent.end(), [](Triangle& t1, Triangle& t2) {return (t1.p[0].z + t1.p[1].z + t1.p[2].z) / 3.0f > (t2.p[0].z + t2.p[1].z + t2.p[2].z) / 3.0f; });
	for (auto& triToRaster : vecTrianglesToRasterTranslucent) {
		Triangle clipped[2];
		std::list<Triangle>listTriangles;
		listTriangles.push_back(triToRaster);
		int nNewTriangles = 1;

		for (int p = 0; p < 4; p++)
		{
			int nTrisToAdd = 0;
			while (nNewTriangles > 0)
			{
				// Take triangle from front of queue
				Triangle test = listTriangles.front();
				listTriangles.pop_front();
				nNewTriangles--;

				// Clip it against a plane. We only need to test each
				// subsequent plane, against subsequent new triangles
				// as all triangles after a plane clip are guaranteed
				// to lie on the inside of the plane. I like how this
				// comment is almost completely and utterly justified
				switch (p)
				{
				case 0:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 1:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, (float)ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 2:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 3:	nTrisToAdd = Triangle_ClipAgainstPlane({ (float)ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				}

				// Clipping may yield a variable number of triangles, so
				// add these new ones to the back of the queue for subsequent
				// clipping against next planes
				for (int w = 0; w < nTrisToAdd; w++)
					listTriangles.push_back(clipped[w]);
			}
			nNewTriangles = listTriangles.size();
		}

		for (auto& t : listTriangles) {
			// Rasterize triangle
			SetDecalStructure(DecalStructure::LIST);
			SetDecalMode(DecalMode::NORMAL);
			DrawPolygonDecal(t.tex, {
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
				}, {
					{t.uv[0].u,t.uv[0].v},
					{t.uv[1].u,t.uv[1].v},
					{t.uv[2].u,t.uv[2].v},
				}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { t.col[0],t.col[1],t.col[2] });
			/*SetDecalMode(DecalMode::WIREFRAME);
			DrawPolygonDecal(nullptr,{
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
			},{
				{0,0},
				{0,0},
				{0,0},
			}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { BLACK,BLACK,BLACK });*/
			SetDecalStructure(DecalStructure::FAN);
			triRenderCount++;
		}
	}
}

void FaceBall::HandleKeys(float fElapsedTime) {
	vec3d vForward = Vector_Mul(vLookDir, std::min(player.GetRadius()-0.00001f,moveSpd*fElapsedTime));
	if (freeRoam) {
		if (GetKey(DOWN).bHeld) {
			freeRoamCamera_pitch -= 1 * fElapsedTime;
		}
		if (GetKey(UP).bHeld) {
			freeRoamCamera_pitch += 1 * fElapsedTime;
		}
	}
	else {
		pitch = 0;
		if (GetMouse(0).bPressed) {
			if (bullets.size() < shotLimit) {
				bullets.push_back({ bullet,{player.GetPos().x,player.GetPos().y - 0.15f, player.GetPos().z},fYaw,0.125f,{shotSpd * std::cosf(fYaw),shotSpd * std::sinf(fYaw)},GREEN,true });
			}
		}
	}
	if (GetKey(Q).bHeld) {
		mat4x4 leftMat = Matrix_MakeRotationY(-PI / 2);
		vec3d vLeftStrafe = Matrix_MultiplyVector(leftMat, vForward);
		vLeftStrafe = { vLeftStrafe.x,0,vLeftStrafe.z };
		if (freeRoam) {
			freeRoamCamera = Vector_Add(freeRoamCamera, vLeftStrafe);
		}
		else {
			if (!CheckCollision({ vLeftStrafe.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius())&&
				CheckPowerupCollision({ vLeftStrafe.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius())==-1) {
				vec3d xMovement{ vLeftStrafe.x,0,0 };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(xMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0?0.2f:0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), xMovement));
			}
			if (!CheckCollision({ 0,0,vLeftStrafe.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ 0,0,vLeftStrafe.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d zMovement{ 0,0,vLeftStrafe.z };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(zMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), zMovement));
			}
		}
	}
	if (GetKey(E).bHeld) {
		mat4x4 leftMat = Matrix_MakeRotationY(PI / 2);
		vec3d vRightStrafe = Matrix_MultiplyVector(leftMat, vForward);
		vRightStrafe = { vRightStrafe.x,0,vRightStrafe.z };
		if (freeRoam) {
			freeRoamCamera = Vector_Add(freeRoamCamera, vRightStrafe);
		}
		else {
			if (!CheckCollision({ vRightStrafe.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ vRightStrafe.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d xMovement{ vRightStrafe.x,0,0 };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(xMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), xMovement));
			}
			if (!CheckCollision({ 0,0,vRightStrafe.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ 0,0,vRightStrafe.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d zMovement{ 0,0,vRightStrafe.z };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(zMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), zMovement));
			}
		}
	}
	if (GetKey(W).bHeld) {
		if (freeRoam) {
			freeRoamCamera = Vector_Add(freeRoamCamera, vForward);
		}
		else {
			if (!CheckCollision({ vForward.x,0,0 }, {player.GetPos().x,player.GetPos().z},player.GetRadius()) &&
				CheckPowerupCollision({ vForward.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d xMovement{ vForward.x,0,0 };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(xMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), xMovement));
			}
			if (!CheckCollision({ 0,0,vForward.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ 0,0,vForward.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d zMovement{ 0,0,vForward.z };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(zMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), zMovement));
			}
			float distanceToExit = std::sqrtf(std::powf(player.GetPos().x - exit.pos.x, 2) + std::powf(player.GetPos().z - exit.pos.z, 2));
			if (distanceToExit < player.GetRadius() + exit.radius) {
				screenAlpha2=screenAlpha=0;
				mode = LEVELCOMPLETE;
			}
		}
	}
	if (GetKey(S).bHeld) {
		vec3d vReverse = { -vForward.x,-vForward.y,-vForward.z };
		if (freeRoam) {
			freeRoamCamera = Vector_Add(freeRoamCamera, vReverse);
		}
		else {
			if (!CheckCollision({ vReverse.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ vReverse.x,0,0 }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d xMovement{ vReverse.x,0,0 };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(xMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), xMovement));
			}
			if (!CheckCollision({ 0,0,vReverse.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) &&
				CheckPowerupCollision({ 0,0,vReverse.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius()) == -1) {
				vec3d zMovement{ 0,0,vReverse.z };
				int enemyCollisionIndex = CheckExplosiveEnemyCollision(zMovement, { player.GetPos().x,player.GetPos().z }, player.GetRadius());
				if (enemyCollisionIndex != -1) {
					Enemy& e = enemies[enemyCollisionIndex];
					e.Hurt(999);
					e.setExploded(true);
					wave2Enemies.push_back(e.freshCopy(true));
					HurtPlayer(e.GetID(), 1, e.isBlinking());
					hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
				}
				player.UpdatePos(Vector_Add(player.GetPos(), zMovement));
			}
		}
	}
	if (GetKey(olc::A).bHeld) {
		if (freeRoam) {
			freeRoamCamera_yaw -= turnSpd * fElapsedTime;
		}
		else {
			fYaw -= turnSpd * fElapsedTime;
			if (hudOffset < 20) {
				hudOffset = std::min(20.f, hudOffset + 128 * fElapsedTime);
			}
		}
	}
	if (GetKey(olc::D).bHeld) {
		if (freeRoam) {
			freeRoamCamera_yaw += turnSpd * fElapsedTime;
		}
		else {
			fYaw += turnSpd * fElapsedTime;
			if (hudOffset > -20) {
				hudOffset = std::max(-20.f,hudOffset-128 * fElapsedTime);
			}
		}
	}
	if (GetKey(olc::F1).bPressed) {
		freeRoam = !freeRoam;
	}
	if (vi2d{int(player.GetPos().x),int(player.GetPos().z)}!=playerRoundedCoords){
		playerRoundedCoords = vi2d{int(player.GetPos().x),int(player.GetPos().z)};
		RegenerateMinimap();
	}
}

void FaceBall::AddWall(int dir, vi2d gridSquare) {
	if (dir & NORTH) {
		map[gridSquare.y][gridSquare.x].wallN = dot;
		if (gridSquare.y > 0) {
			map[gridSquare.y - 1][gridSquare.x].wallS = dot;
		}
	}
	if (dir & WEST) {
		map[gridSquare.y][gridSquare.x].wallW = dot;
		if (gridSquare.x > 0) {
			map[gridSquare.y][gridSquare.x - 1].wallE = dot;
		}
	}
	if (dir & SOUTH) {
		map[gridSquare.y][gridSquare.x].wallS = dot;
		if (gridSquare.y < map.size() - 1) {
			map[gridSquare.y + 1][gridSquare.x].wallN = dot;
		}
	}
	if (dir & EAST) {
		map[gridSquare.y][gridSquare.x].wallE = dot;
		if (gridSquare.x < map[0].size() - 1) {
			map[gridSquare.y][gridSquare.x + 1].wallW = dot;
		}
	}
}

bool FaceBall::OnUserCreate()
{
	game = this;
	sAppName = "Faceball 2030";
	matProj = Matrix_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f);
	dot = new Decal(new Sprite("assets/dot.png"));
	circle = new Decal(new Sprite("assets/circle.png"));
	arrow = new Decal(new Sprite("assets/arrow.png"));
	enemy_ShootMe_tex = new Decal(new Sprite("assets/enemies/ShootMe.png"));
	YAZAWA = new Decal(new Sprite("assets/yazawa.png"));
	wall_tex = new Decal(new Sprite("assets/wall.png"));
	bullet_tex = new Decal(new Sprite("assets/enemies/bullet.png"));
	floor_tex = new Decal(new Sprite("assets/floor.png"));
	enemy_Sonar_tex = new Decal(new Sprite("assets/enemies/Sonar.png"));
	hud = new Decal(new Sprite("assets/hud.png"));
	exit_wall_tex = new Decal(new Sprite("assets/exitwall.png"));
	enemy_ShootMe2_tex = new Decal(new Sprite("assets/enemies/ShootMe2.png"));
	enemy_IShoot_tex = new Decal(new Sprite("assets/enemies/IShoot.png"));
	life4 = new Decal(new Sprite("assets/life4.png"));
	life3 = new Decal(new Sprite("assets/life3.png"));
	life2 = new Decal(new Sprite("assets/life2.png"));
	life1 = new Decal(new Sprite("assets/life1.png"));
	crosshair = new Decal(new Sprite("assets/crosshair.png"));
	hudmeter = new Decal(new Sprite("assets/hudmeter.png"));
	powerup_tex = new Decal(new Sprite("assets/powerup.png"));
	powerup2_tex = new Decal(new Sprite("assets/powerup2.png"));
	powerups_tex = new Decal(new Sprite("assets/powerups.png"));

	enemy_ShootMe = { "assets/enemies/ShootMe.obj", enemy_ShootMe_tex };
	enemy_ShootMe2 = { "assets/enemies/ShootMe2.obj", enemy_ShootMe2_tex };
	enemy_Sonar = { "assets/enemies/Sonar.obj", enemy_Sonar_tex };
	enemy_IShoot = { "assets/enemies/IShoot.obj", enemy_IShoot_tex};
	bullet = { "assets/enemies/bullet.obj",bullet_tex };
	powerup = { "assets/Powerup.obj",powerup_tex};
	powerup2 = { "assets/Powerup2.obj",powerup2_tex };
	mapExit = { "assets/Exit.obj",dot };
	mapWalls.texture = wall_tex;
	mapFloor.texture = floor_tex;

	mapSpr=new Sprite(minimapGridSize.x*20,minimapGridSize.y*20);
	mapDecal=new Decal(mapSpr);

	InitializeEnemyData();
	InitializePowerupColors();

	LoadLevel(1);

	return true;
}

int FaceBall::CheckPowerupCollision(vec3d movementVector, vf2d pos, float radius) {
	vf2d newpos = { pos.x + movementVector.x,pos.y + movementVector.z };
	for (int i = 0; i < game->powerups.size(); i++) {
		Powerup& p = game->powerups[i];
		float dist = std::sqrtf(std::powf(newpos.x - p.pos.x, 2) + std::powf(newpos.y - p.pos.z, 2));
		if (dist < radius + p.radius) {
			lastPowerupCollidedWith = i; //This function is only meant for the player to collide with things. If this is needed for other things, we will have to refactor/reconsider this line as they would modify the player's last powerup touched variable which is a no-no.
			return i;
		}
	}
	return -1;
}

//Collisions will not occur if the player is dead.
bool FaceBall::CheckPlayerCollision(vec3d movementVector, vf2d pos, float radius) {
	if (game->hp <= 0) { return false; }
	vf2d newpos = { pos.x + movementVector.x,pos.y + movementVector.z };

	float dist = std::sqrtf(std::powf(newpos.x - game->player.GetPos().x, 2) + std::powf(newpos.y - game->player.GetPos().z, 2));
	return dist < radius + game->player.GetRadius();
}

//This returns the index of an enemy that collided (Or -1 if nothing was collided with).
//Collisions will not occur if the enemy is dead.
int FaceBall::CheckEnemyCollision(vec3d movementVector, vf2d pos, float radius,int ignoreIndex) {
	vf2d newpos = { pos.x + movementVector.x,pos.y + movementVector.z };
	for (int i = 0; i < game->enemies.size();i++) {
		if (i != ignoreIndex) {
			Enemy& e = game->enemies[i];
			if (!e.isDead()) {
				float dist = std::sqrtf(std::powf(newpos.x - e.pos.x, 2) + std::powf(newpos.y - e.pos.z, 2));
				if (dist < radius + e.radius) {
					return i;
				}
			}
		}
	}
	return -1;
}

//This returns the index of only explosive enemies that collided (Or -1 if nothing was collided with).
//Collisions will not occur if the enemy is dead.
int FaceBall::CheckExplosiveEnemyCollision(vec3d movementVector, vf2d pos, float radius, int ignoreIndex) {
	vf2d newpos = { pos.x + movementVector.x,pos.y + movementVector.z };
	for (int i = 0; i < game->enemies.size(); i++) {
		if (i != ignoreIndex) {
			Enemy& e = game->enemies[i];
			if (e.isExplosive()&&!e.isDead()) {
				float dist = std::sqrtf(std::powf(newpos.x - e.pos.x, 2) + std::powf(newpos.y - e.pos.z, 2));
				if (dist < radius + e.radius) {
					return i;
				}
			}
		}
	}
	return -1;
}


bool Bullet::Update(float fElapsedTime) {
	vec3d adjustedSpd = { spd.x * fElapsedTime,0,spd.y * fElapsedTime };
	if (friendly) {
		int collided_enemy = FaceBall::CheckEnemyCollision(adjustedSpd, { pos.x,pos.z }, 0.1);
		if (collided_enemy!=-1) {
			Enemy& enemy = game->enemies[collided_enemy];
			enemy.Hurt();
			if (enemy.isDead()) {
				enemy.OnDeathEvent();
			}
			return false;
		}
	}
	else {
		if (FaceBall::CheckPlayerCollision(adjustedSpd,{pos.x,pos.z},0.1)) {
			game->HurtPlayer(shooterID,1,shooterBlinking);
			game->hudShakeTime = 0.2f;
			return false;
		} else
		{
			int enemyCollisionIndex = FaceBall::CheckEnemyCollision(adjustedSpd, { pos.x,pos.z }, 0.1, shooterIndex);
			if (enemyCollisionIndex != -1) {
				Enemy& enemy = game->enemies[enemyCollisionIndex];
				enemy.Hurt(0);
				return false;
			}
		}
	}
	if (!FaceBall::CheckCollision(adjustedSpd, {pos.x,pos.z}, 0.05)) {
		pos.x += adjustedSpd.x;
		pos.z += adjustedSpd.z;
	}
	else {
		return false;
	}
	return true;
}

void FaceBall::HurtPlayer(EnemyID id,int damage,bool blinking) {
	if (respawnTimer<=0.0f){
		if (shieldDuration >= 0) {
			damage = 0;
		}
		hp = std::max(0, hp - damage);
		if (hp <= 0) {
			RegenerateMinimap();
			respawnTimer = 3.0f;
		}
		lastHitBy = id;
		lastHitByBlinking = blinking;
		if (shieldDuration < 0) {
			screenCol = enemyData[lastHitBy].col;
		}
		else {
			screenCol = WHITE;
		}
	}
}

void FaceBall::Display3DKillerModel() {
	// Set up rotation matrices
	mat4x4 matRotZ, matRotX, matWorld;

	matRotZ = Matrix_MakeRotationZ(0);
	matRotX = Matrix_MakeRotationX(0);

	//matTrans = Matrix_MakeTranslation(0.0f, 0.0f, 0.0f);
	matWorld = Matrix_MakeIdentity();
	matWorld = Matrix_MultiplyMatrix(matRotZ, matRotX);
	//matWorld = Matrix_MultiplyMatrix(matWorld, matTrans);

	vec3d vUp = { 0,1,0 };
	vec3d vTarget = { 0,sinf(0.0944495052),cosf(0.0944495052) };
	mat4x4 matCameraRot = Matrix_MakeRotationY(3.12987614 - PI / 2);
	vLookDir = Matrix_MultiplyVector(matCameraRot, vTarget);
	vec3d camera = { 0.758110702,0.222716771,3.00489759 };
	vTarget = Vector_Add(camera, vLookDir);
	mat4x4 matCamera = Matrix_PointAt(camera, vTarget, vUp);
	mat4x4 matView = Matrix_QuickInverse(matCamera);
	std::vector<Triangle>vecTrianglesToRaster;
	Object o = { enemyData[lastHitBy].mesh,{0,0,3} };
	RenderMeshDeathScreen(matView, vecTrianglesToRaster, o);

	//std::sort(vecTrianglesToRaster.begin(),vecTrianglesToRaster.end(),[](triangle&t1,triangle&t2){return (t1.p[0].z+t1.p[1].z+t1.p[2].z)/3.0f>(t2.p[0].z+t2.p[1].z+t2.p[2].z)/3.0f;});
	triRenderCount = 0;
	for (auto& triToRaster : vecTrianglesToRaster) {
		Triangle clipped[2];
		std::list<Triangle>listTriangles;
		listTriangles.push_back(triToRaster);
		int nNewTriangles = 1;

		for (int p = 0; p < 4; p++)
		{
			int nTrisToAdd = 0;
			while (nNewTriangles > 0)
			{
				// Take triangle from front of queue
				Triangle test = listTriangles.front();
				listTriangles.pop_front();
				nNewTriangles--;

				// Clip it against a plane. We only need to test each
				// subsequent plane, against subsequent new triangles
				// as all triangles after a plane clip are guaranteed
				// to lie on the inside of the plane. I like how this
				// comment is almost completely and utterly justified
				switch (p)
				{
				case 0:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 1:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, (float)ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 2:	nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				case 3:	nTrisToAdd = Triangle_ClipAgainstPlane({ (float)ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
				}

				// Clipping may yield a variable number of triangles, so
				// add these new ones to the back of the queue for subsequent
				// clipping against next planes
				for (int w = 0; w < nTrisToAdd; w++)
					listTriangles.push_back(clipped[w]);
			}
			nNewTriangles = listTriangles.size();
		}

		for (auto& t : listTriangles) {
			// Rasterize triangle
			SetDecalStructure(DecalStructure::LIST);
			SetDecalMode(DecalMode::NORMAL);
			DrawPolygonDecal(t.tex, {
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
				}, {
					{t.uv[0].u,t.uv[0].v},
					{t.uv[1].u,t.uv[1].v},
					{t.uv[2].u,t.uv[2].v},
				}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { t.col[0],t.col[1],t.col[2] });
			/*SetDecalMode(DecalMode::WIREFRAME);
			DrawPolygonDecal(nullptr,{
				{t.p[0].x, t.p[0].y},
				{t.p[1].x, t.p[1].y},
				{t.p[2].x, t.p[2].y}
			},{
				{0,0},
				{0,0},
				{0,0},
			}, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { BLACK,BLACK,BLACK });*/
			SetDecalStructure(DecalStructure::FAN);
			triRenderCount++;
		}
	}
}

void FaceBall::RenderHud(float fElapsedTime) {
	if (hudShakeTime > 0) {
		hudShakeTime -= fElapsedTime;
	}
	if (!GetKey(olc::D).bHeld && !GetKey(olc::A).bHeld) {
		hudOffsetAcc += 20 * fElapsedTime;
		while (hudOffsetAcc >= 1) {
			hudOffset /= 2;
			hudOffsetAcc--;
		}
	}
	if (hp <= 0) {
		FillRectDecal({ 0,0 }, { float(ScreenWidth()),float(ScreenHeight()) });
		std::string topText = enemyData[lastHitBy].name + " SAYS";
		std::string bottomText = "HAVE A NICE DAY !";
		if (!enemyData[lastHitBy].blinking||std::sinf(30*gameTimer)>0.4f) {
			Display3DKillerModel();
		}
		DrawStringDecal({ (float)(ScreenWidth() / 2 - GetTextSize(topText).x / 2 * 3),140.f }, topText, { 96,96,255 }, { 3,6 });
		DrawStringDecal({ float(ScreenWidth() / 2 - GetTextSize(bottomText).x / 2 * 3),float(ScreenHeight() - 140.f) }, bottomText, { 96,96,255 }, { 3,6 });
	}
	vf2d hudAdjustment = { -32,-18 };
	if (hudShakeTime > 0) {
		hudShakeAmt = std::sinf(40 * hudShakeTime);
		hudAdjustment.x += hudShakeAmt * 2;
		hudAdjustment.y += hudShakeAmt * 4;
		hudOffset += hudShakeAmt * 2;
	}
	vf2d hudLoc = { hudAdjustment.x + (hp>0?hudOffset:0),hudAdjustment.y};
	if (hp > 0) {
		SetDecalMode(DecalMode::ADDITIVE);
	}
	DrawWarpedDecal(hudmeter, { meter_ARMOR[0] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2,meter_ARMOR[1] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2,meter_ARMOR[2].lerp(meter_ARMOR[1],1 - float(armorUpgrades)/maxArmorUpgrades)  + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2, meter_ARMOR[3].lerp(meter_ARMOR[0],1- float(armorUpgrades)/maxArmorUpgrades)   + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2});
	DrawWarpedDecal(hudmeter, { meter_SPEED[0] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)}/2,  meter_SPEED[1] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2,meter_SPEED[2].lerp(meter_SPEED[1],1 - float(speedUpgrades) / maxSpeedUpgrades) + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)}/2,   meter_SPEED[3].lerp(meter_SPEED[0],1-float(speedUpgrades) / maxSpeedUpgrades) + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2 });
	DrawWarpedDecal(hudmeter, { meter_SHOTS[0] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2,meter_SHOTS[1] + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2,meter_SHOTS[2].lerp(meter_SHOTS[1],1 - float(shotsUpgrades) / maxShotsUpgrades) + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2, meter_SHOTS[3].lerp(meter_SHOTS[0],1-float(shotsUpgrades) / maxShotsUpgrades) + hudLoc + vf2d{float(hudmeter->sprite->width),float(hudmeter->sprite->height)} / 2});
	DrawDecal(hudLoc, hud, { 1.05,1.05 });
	SetDecalMode(DecalMode::NORMAL);
	DrawDecal(hudLoc + vf2d{ 704+32,56+18 }, life4, { 1,1 }, float(hp) / maxHP > 0.75f ? WHITE : VERY_DARK_GREEN);
	DrawDecal(hudLoc + vf2d{ 704 - 64+32,56+18 }, life3, { 1,1 }, float(hp) / maxHP > 0.34f && float(hp) / maxHP <= 0.75f ? WHITE : VERY_DARK_GREEN);
	DrawDecal(hudLoc + vf2d{ 704 - 128+32,56+18 }, life2, { 1,1 }, hp > 0 && float(hp) / maxHP <= 0.34f ? WHITE : VERY_DARK_GREEN);
	DrawDecal(hudLoc + vf2d{ 704 - 192+32,56+18 }, life1, { 1,1 }, hp <= 0 ? WHITE : VERY_DARK_GREEN);
	if (hp > 0) {
		DrawStringDecal(vf2d{ 112 + hudOffset+32,4+18 }+hudAdjustment, "Triangles: " + std::to_string(triRenderCount), BLACK, { 2,4 });
		std::string hudText = "Tags Left: " + std::to_string(tagsRemaining) + "   Lives: " + std::to_string(lives);
		DrawStringPropDecal(vf2d{ hudOffset + (float)(ScreenWidth() / 2 - GetTextSizeProp(hudText).x * 3 / 2)+32,(float)(ScreenHeight() - 2 - GetTextSizeProp(hudText).y * 5)+18 } + hudAdjustment, hudText, VERY_DARK_GREY, { 3,5 });
		for (int x = -1; x <= 1; x++) {
			for (int y = -1; y <= 1; y++) {
				if (x != 0 && y != 0) {
					if (lookingAtText != "") {
						DrawStringDecal(vf2d{ hudOffset + float(ScreenWidth() / 2 - GetTextSize(lookingAtText).x / 2 * 4) + 32,float(ScreenHeight() - 128 - GetTextSize(lookingAtText).y * 4) + 18 } + vf2d{ float(x),float(y) }, lookingAtText, DARK_BLUE, { 4,4 });
					}
					else {
						DrawStringDecal(vf2d{ hudOffset + float(ScreenWidth() / 2 - GetTextSize(hudDisplayText).x / 2 * 4) + 32,float(ScreenHeight() - 128 - GetTextSize(hudDisplayText).y * 4) + 18 } + vf2d{ float(x),float(y) }, hudDisplayText, DARK_BLUE, { 4,4 });
					}
				}
			}
		}
		for (int x = -1; x <= 1; x++) {
			for (int y = -1; y <= 1; y++) {
				if (x != 0 && y != 0) {
					DrawStringPropDecal(vf2d{ hudOffset + (float)(ScreenWidth() / 2 - GetTextSizeProp(std::to_string(score)).x * 4 / 2) + 32,(float)(36 - GetTextSizeProp(std::to_string(score)).y * 4) + 18 } + hudAdjustment + vf2d{float(x),float(y)}, std::to_string(score), VERY_DARK_RED, {4,4});
				}
			}
		}
		DrawStringPropDecal(vf2d{ hudOffset + (float)(ScreenWidth() / 2 - GetTextSizeProp(std::to_string(score)).x * 4 / 2) + 32,(float)(36 - GetTextSizeProp(std::to_string(score)).y * 4) + 18 } + hudAdjustment, std::to_string(score), { 192,96,96 }, { 4,4 });
		if (lookingAtText != "") {
			DrawStringDecal(vf2d{ hudOffset + float(ScreenWidth() / 2 - GetTextSize(lookingAtText).x / 2 * 4) + 32,float(ScreenHeight() - 128 - GetTextSize(lookingAtText).y * 4) + 18 }, lookingAtText, { 192,192,255 }, { 4,4 });
		}
		else {
			DrawStringDecal(vf2d{ hudOffset + float(ScreenWidth() / 2 - GetTextSize(hudDisplayText).x / 2 * 4) + 32,float(ScreenHeight() - 128 - GetTextSize(hudDisplayText).y * 4) + 18 }, hudDisplayText, { 192,192,255 }, { 4,4 });
		}
		DrawStringDecal({float(ScreenWidth()-128),24},std::to_string(wave2Enemies.size()));
		SetDecalMode(DecalMode::ADDITIVE);
		DrawDecal({ float(ScreenWidth() / 2 - crosshair->sprite->width / 2),float(ScreenHeight() / 2 - crosshair->sprite->height / 2) }, crosshair, { 1,1 }, {255,255,255,128});
		if (stopDuration > 0) {
			DrawPartialDecal({ hudOffset + float(ScreenWidth() / 10 - 16), float(ScreenHeight() / 4 - 16 * 4) }, vf2d{ 32,32 }*4, powerups_tex, { 3 * 32,0 }, { 32,32 },stopDuration>7?WHITE:stopDuration>2?std::abs(std::sin(10*stopDuration))>0.65?WHITE:BLACK: std::abs(std::sin(30 * stopDuration)) > 0.75 ? WHITE : BLACK);
		}
		if (shieldDuration > 0) {
			DrawPartialDecal({ hudOffset + float(ScreenWidth() / 10 - 16), float(ScreenHeight() / 4 + ScreenHeight() / 4 * 1 - 16 * 4) }, vf2d{ 32,32 }*4, powerups_tex, { 4 * 32,0 }, { 32,32 }, shieldDuration > 7 ? WHITE : shieldDuration > 2 ? std::abs(std::sin(10 * shieldDuration)) > 0.65 ? WHITE : BLACK : std::abs(std::sin(30 * shieldDuration)) > 0.75 ? WHITE : BLACK);
		}
		if (camoDuration > 0) {
			DrawPartialDecal({ hudOffset + float(ScreenWidth() / 10 - 16), float(ScreenHeight() / 4 + ScreenHeight() / 4 * 2 - 16 * 4) }, vf2d{ 32,32 }*4, powerups_tex, { 5 * 32,0 }, { 32,32 },camoDuration>7?WHITE:camoDuration>2?std::abs(std::sin(10*camoDuration))>0.65?WHITE:BLACK: std::abs(std::sin(30 * camoDuration)) > 0.75 ? WHITE : BLACK);
		}
		vf2d mapCenter = {float(ScreenWidth()-364),float(164)};
		SetDrawTarget(nullptr);
		DrawDecal(vf2d{mapCenter.x+hudOffset/2,mapCenter.y}-vf2d{2.5f,2.5f},dot,{5,5},GREEN);
		DrawRotatedDecal({mapCenter.x+hudOffset/2,mapCenter.y},mapDecal,-fYaw-PI/2,vf2d{int(player.GetPos().x)+0.5f,int(player.GetPos().z)+0.5f}*minimapGridSize,{1,1});
		SetDecalMode(DecalMode::NORMAL);
	}
	GradientFillRectDecal({ 0,0 }, vf2d{ (float)ScreenWidth()/2,(float)ScreenHeight()/2 }, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0) }, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0) }, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime > 0.2f ? 64 : 0) }, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0) });
	GradientFillRectDecal({ float(ScreenWidth()/2),0}, vf2d{(float)ScreenWidth() / 2,(float)ScreenHeight() / 2}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime > 0.2f ? 64 : 0) }, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)});
	GradientFillRectDecal({ 0,float(ScreenHeight()/2)}, vf2d{(float)ScreenWidth() / 2,(float)ScreenHeight() / 2}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime > 0.2f ? 64 : 0) });
	GradientFillRectDecal({ float(ScreenWidth()/2),float(ScreenHeight() / 2)}, vf2d{(float)ScreenWidth() / 2,(float)ScreenHeight() / 2}, { (uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime > 0.2f ? 64 : 0) }, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)}, {(uint8_t)screenCol.r,(uint8_t)screenCol.g,(uint8_t)screenCol.b,(uint8_t)(hudShakeTime>0.2f?120:hudShakeTime>0?64:0)});
	FillRectDecal({ 0,0 }, vf2d{ (float)ScreenWidth(),(float)ScreenHeight() }, { (uint8_t)0,(uint8_t)0,(uint8_t)0,(uint8_t)screenAlpha });
	if (screenAlpha>=254.f){
		GradientFillRectDecal({0,0},{float(ScreenWidth()),float(ScreenHeight())},topCol,botCol,botCol,topCol);
		FillRectDecal({ 0,0 }, vf2d{ (float)ScreenWidth(),(float)ScreenHeight() }, { (uint8_t)0,(uint8_t)0,(uint8_t)0,(uint8_t)screenAlpha2 });
	}
}

void FaceBall::SubtractTag() {
	tagsRemaining = std::max(0, tagsRemaining - 1);
	if (!exitWallsCleared&&tagsRemaining <= 0) {
		exitWallsCleared = true;
		if (exitCoords.y != 0) {
			map[exitCoords.y][exitCoords.x].wallN = NULL;
		}
		if (exitCoords.y != MAP_SIZE.y-1) {
			map[exitCoords.y][exitCoords.x].wallS = NULL;
		}
		if (exitCoords.x != 0) {
			map[exitCoords.y][exitCoords.x].wallW = NULL;
		}
		if (exitCoords.x != MAP_SIZE.x-1) {
			map[exitCoords.y][exitCoords.x].wallE = NULL;
		}
		if (exitCoords.x > 0) {
			map[exitCoords.y][exitCoords.x - 1].wallE = NULL;
		}
		if (exitCoords.x+1 < MAP_SIZE.x) {
			map[exitCoords.y][exitCoords.x+1].wallW = NULL;
		}
		if (exitCoords.y > 0) {
			map[exitCoords.y - 1][exitCoords.x].wallS = NULL;
		}
		if (exitCoords.y+1 < MAP_SIZE.y) {
			map[exitCoords.y+1][exitCoords.x].wallN = NULL;
		}
		mapWalls.tris.clear();
		for (int y = 0; y < MAP_SIZE.y; y++) {
			for (int x = 0; x < MAP_SIZE.x; x++) {
				if (map[y][x].wallN != NULL) {
					Decal* exitWallTex = wall_tex;
					if (vi2d{ x,y } == vi2d{ exitCoords.x, exitCoords.y + 1 }) {
						exitWallTex = exit_wall_tex;
					}
					mapWalls.tris.push_back({ {{(float)x,1,(float)y},{(float)x,0,(float)y},{(float)x + 1,1,(float)y}},{{0,0},{0,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x,0,(float)y},{(float)x + 1,0,(float)y},{(float)x + 1,1,(float)y}},{{0,1},{1,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
				if (map[y][x].wallS != NULL) {
					Decal* exitWallTex = wall_tex;
					if (vi2d{ x,y } == vi2d{ exitCoords.x, exitCoords.y - 1 }) {
						exitWallTex = exit_wall_tex;
					}
					mapWalls.tris.push_back({ {{(float)x + 1,1,(float)y + 1},{(float)x,0,(float)y + 1},{(float)x,1,(float)y + 1}},{{0,0},{1,1},{1,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x + 1,1,(float)y + 1},{(float)x + 1,0,(float)y + 1},{(float)x,0,(float)y + 1}},{{0,0},{0,1},{1,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
				if (map[y][x].wallW != NULL) {
					Decal* exitWallTex = wall_tex;
					if (vi2d{ x,y } == vi2d{ exitCoords.x + 1, exitCoords.y }) {
						exitWallTex = exit_wall_tex;
					}
					mapWalls.tris.push_back({ {{(float)x,1,(float)y},{(float)x,1,(float)y + 1}, {(float)x,0,(float)y + 1}},{{1,0},{0,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x,0,(float)y},{(float)x,1,(float)y}, {(float)x,0,(float)y + 1}}, {{1,1},{1,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
				if (map[y][x].wallE != NULL) {
					Decal* exitWallTex = wall_tex;
					if (vi2d{ x,y } == vi2d{ exitCoords.x - 1, exitCoords.y }) {
						exitWallTex = exit_wall_tex;
					}
					mapWalls.tris.push_back({ {{(float)x + 1,0,(float)y + 1},{(float)x + 1,1,(float)y + 1},{(float)x + 1,1,(float)y}},{{1,1},{1,0},{0,0}},{WHITE,WHITE,WHITE}, exitWallTex });
					mapWalls.tris.push_back({ {{(float)x + 1,0,(float)y + 1} ,{(float)x + 1,1,(float)y},{(float)x + 1,0,(float)y}},{{1,1},{0,0},{0,1}},{WHITE,WHITE,WHITE}, exitWallTex });
				}
			}
		}
		walls.mesh=mapWalls;
	}
}

void FaceBall::RunEnemyAI(Enemy& e,float fElapsedTime,int myIndex) {
	if (!e.isDead()) {
		EnemyData dat = enemyData[e.GetID()];
		e.ReloadBullet(fElapsedTime);
		switch (e.GetID()) {
			case SHOOTME:
			case SHOOTME_ARMOR: {
				e.rot += dat.rotSpd * fElapsedTime;
			}break;
			case SHOOTME2: {
				switch (e.GetPhase()) {
				case Phase::DEFAULT: {
					vf2d movementVec = { std::cosf(e.rot) * dat.movSpd * fElapsedTime,std::sinf(e.rot) * dat.movSpd * fElapsedTime };
					if (-1 == CheckEnemyCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius, myIndex) &&
						!CheckCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius)) {
						if (CheckPlayerCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius)) {
							e.Hurt(999);
							e.setExploded(true);
							wave2Enemies.push_back(e.freshCopy(true));
							HurtPlayer(e.GetID(), 1, e.isBlinking());
							hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
						}
						e.pos.x += movementVec.x;
						e.pos.z += movementVec.y;
					}
					else {
						e.SetPhase(Phase::TURNING);
						e.turnAmt = PI;
					}
				}break;
				case Phase::TURNING: {
					if (e.turnAmt > 0) {
						float rotAmt = std::min(e.turnAmt, dat.rotSpd * fElapsedTime);
						e.turnAmt -= rotAmt;
						e.rot += rotAmt;
						if (e.turnAmt <= 0) {
							e.SetPhase(Phase::DEFAULT);
						}
					}
				}break;
				}
			}break;
			case ISHOOT:
			case ISHOOT_MAP: {
				e.rot += dat.rotSpd * fElapsedTime;
				if (e.CanShoot()) {
					e.ShootBullet(myIndex);
				}
			}break;
			case ISHOOT2: {
				switch (e.GetPhase()) {
					case Phase::DEFAULT: {
						vf2d movementVec = { std::cosf(e.rot) * dat.movSpd * fElapsedTime,std::sinf(e.rot) * dat.movSpd * fElapsedTime };
						if (-1 == CheckEnemyCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius, myIndex) &&
							!CheckCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius)) {
							if (CheckPlayerCollision({ movementVec.x,0,movementVec.y }, { e.pos.x,e.pos.z }, e.radius)) {
								e.Hurt(999);
								e.setExploded(true);
								wave2Enemies.push_back(e.freshCopy(true));
								HurtPlayer(e.GetID(), 1, e.isBlinking());
								hudShakeTime = shieldDuration >= 0 ? 0.2f : 0.6f;
							}
							e.pos.x += movementVec.x;
							e.pos.z += movementVec.y;
							if (e.CanShoot()) {
								e.ShootBullet(myIndex);
							}
						}
						else {
							e.SetPhase(Phase::TURNING);
							e.turnAmt = PI;
						}
					}break;
					case Phase::TURNING: {
						if (e.turnAmt > 0) {
							float rotAmt = std::min(e.turnAmt, dat.rotSpd * fElapsedTime);
							e.turnAmt -= rotAmt;
							e.rot += rotAmt;
							if (e.turnAmt <= 0) {
								e.SetPhase(Phase::DEFAULT);
							}
						}
					}break;
				}
			}break;
		}
		if (e.roundedCoords!=vi2d{int(e.pos.x),int(e.pos.z)}){
			e.roundedCoords=vi2d{int(e.pos.x),int(e.pos.z)};
			if (hasMapUpgrade){
				RegenerateMinimap();
			}
		}
	}
}

bool FaceBall::PlayerHasMapUpgrade(){
	return hasMapUpgrade;
}

bool FaceBall::OnUserUpdate(float fElapsedTime)
{
	fElapsedTime = std::min(2/60.f, fElapsedTime);
	gameTimer += fElapsedTime;
	switch (mode) {
		case GAME: {
			hudDisplayText = "";
			if (stopDuration >= 0) {
				stopDuration -= fElapsedTime;
			}
			if (shieldDuration >= 0) {
				shieldDuration -= fElapsedTime;
			}
			if (camoDuration >= 0) {
				camoDuration -= fElapsedTime;
			}
			if (EnemiesAlive() < 5) {
				if (tagsRemaining <= 0) {
					//Look at all wave 2 enemies first, if we can't spawn one in then look at wave 3 enemies, and try to spawn one in.
					if (wave2Enemies.size()>0){
						for (int i=0;i<wave2Enemies.size();i++){
							Enemy&e=wave2Enemies[i];
							if (CheckEnemyCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*3)==-1&&
								!CheckPlayerCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*6)){
								enemies.push_back(e);
								wave2Enemies.erase(wave2Enemies.begin()+i);
								RegenerateMinimap();
								goto spawningDone;
							}
						}
					} else 
					if (wave3Enemies.size()>0){
						for (int i=0;i<wave3Enemies.size();i++){
							Enemy&e=wave3Enemies[i];
							if (CheckEnemyCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*3)==-1&&
								!CheckPlayerCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*6)){
								enemies.push_back(e);
								wave3Enemies.erase(wave3Enemies.begin()+i);
								RegenerateMinimap();
								goto spawningDone;
							}
						}
					}
					spawningDone:
					int a;
				}
				else {
					//Look at wave 2 enemies, and try to spawn one in.
					for (int i=0;i<wave2Enemies.size();i++){
						Enemy&e=wave2Enemies[i];
						if (CheckEnemyCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*3)==-1&&
							!CheckPlayerCollision({0,0,0},{e.pos.x,e.pos.z},e.radius*6)){
							enemies.push_back(e);
							wave2Enemies.erase(wave2Enemies.begin()+i);
							RegenerateMinimap();
							break;
						}
					}
				}
			}
			for (std::vector<Bullet>::iterator it = bullets.begin(); it != bullets.end();) {
				Bullet& b = *it;
				if (!b.Update(fElapsedTime)) {
					it = bullets.erase(it);
				}
				else {
					it++;
				}
			}
			int counter = 0;
			for (std::vector<Enemy>::iterator it = enemies.begin(); it != enemies.end();) {
				Enemy& e = *it;
				if (!e.Update(fElapsedTime)) {
					it = enemies.erase(it);
				}
				else {
					if (e.isLastHitTimerActive()) {
						e.reduceLastHitTimer(fElapsedTime);
					}
					if (stopDuration < 0) {
						RunEnemyAI(e, fElapsedTime, counter);
					}
					it++;
					counter++;
				}
			}
			for (std::vector<Powerup>::iterator it = powerups.begin(); it != powerups.end();) {
				Powerup& p = *it;
				if (!p.Update(fElapsedTime)) {
					it = powerups.erase(it);
				}
				else {
					it++;
				}
			}
			if (lastPowerupCollidedWith != -1) {
				vec3d vForward = Vector_Mul(vLookDir, std::min(player.GetRadius() - 0.00001f, moveSpd * fElapsedTime));
				if (CheckPowerupCollision({ vForward.x,0,vForward.z }, { player.GetPos().x,player.GetPos().z }, player.GetRadius() * 1.25) != -1) {
					powerups[lastPowerupCollidedWith].opened = true;
					hudDisplayText = powerupData[powerups[lastPowerupCollidedWith].type].name;
					if (GetKey(F).bPressed) {
						Powerup& power = powerups[lastPowerupCollidedWith];
						switch (power.type) {
							case PowerupType::ARMOR: {
								armorUpgrades=std::min(maxArmorUpgrades,armorUpgrades+1);
								maxHP = hp = armorUpgrades + baseHP;
							}break;
							case PowerupType::SPEED: {
								speedUpgrades = std::min(maxSpeedUpgrades, speedUpgrades + 1);
								moveSpd = baseMoveSpd + speedUpgrades * 0.5;
								turnSpd = baseTurnSpd + speedUpgrades * 0.25;
								shotSpd = baseShotSpd + speedUpgrades * 0.5;
							}break;
							case PowerupType::SHOTS: {
								shotsUpgrades = std::min(maxShotsUpgrades, shotsUpgrades + 1);
								shotLimit = baseShotLimit + shotsUpgrades * 1;
							}break;
							case PowerupType::STOP: {
								stopDuration = 20;
							}break;
							case PowerupType::SHIELD: {
								shieldDuration = 20;
							}break;
							case PowerupType::CAMO: {
								camoDuration = 20;
							}break;
							case PowerupType::MAP: {
								hasMapUpgrade = true;
								RegenerateMinimap();
							}break;
							case PowerupType::COIN: {
								AddScore(1000);
							}break;
						}
						powerups.erase(powerups.begin() + lastPowerupCollidedWith);
						lastPowerupCollidedWith = -1;
					}
				}
			}
			if (hp > 0) {
				vf2d checkPos = {player.GetPos().x, player.GetPos().z};
				lookingAtText = "";
				while (true) {
					if (checkPos.x<0 || checkPos.y<0 || checkPos.x>MAP_SIZE.x || checkPos.y>MAP_SIZE.y) break;
					vf2d checkVec = { std::cosf(fYaw) * 0.1f,std::sinf(fYaw) * 0.1f };
					if (CheckCollision({ checkVec.x,0,checkVec.y }, checkPos, 0.1f)) break;
					checkPos.x += checkVec.x;
					checkPos.y += checkVec.y;
					for (Enemy&e : enemies) {
						if (!e.isDead()) {
							float dist = std::sqrtf(std::powf(checkPos.x - e.pos.x, 2) + std::powf(checkPos.y - e.pos.z, 2));
							if (dist <= e.radius) {
								lookingAtText = enemyData[e.GetID()].name;
								goto afterPositionCheck;
							}
						}
					}
				}
				afterPositionCheck:
				HandleKeys(fElapsedTime);
				RenderWorld();
			}
			else {
				respawnTimer -= fElapsedTime;
				if (respawnTimer <= 0.0f) {
					lives--;
					hasMapUpgrade = false;
					if (lives > 0) {
						hp = maxHP;
						player.UpdatePos(spawnLoc);
						fYaw = (int(spawnFacingDir) - 1) * PI / 2;
					}
					else {
						//TODO Game over.
					}
				}
			}
			RenderHud(fElapsedTime);
		}break;
		case EDITOR: {
			editor.Update(fElapsedTime);
		}break;
		case LEVELCOMPLETE: {
			RenderWorld();
			RenderHud(fElapsedTime);
			if (screenAlpha < 255) {
				screenAlpha = std::min(255.f,screenAlpha+200 * fElapsedTime);
				if (screenAlpha>=254.f){
					screenAlpha2 = 255.f;
					int colInd=rand()%colorCycle.size();
					topTargetColInd=(colInd+1)%colorCycle.size();
					botTargetColInd=(topTargetColInd+1)%colorCycle.size();
					topCol=topCurrentCol=colorCycle[colInd];
					botCol=botCurrentCol=colorCycle[topTargetColInd];
					topTargetCol=colorCycle[topTargetColInd];
					botTargetCol=colorCycle[botTargetColInd];
				}
			}
			else {
				if (GetMouse(0).bPressed){
					LoadLevel(++level);
				}
				if (screenAlpha2>0){
					screenAlpha2 = std::max(0.f,screenAlpha2-200 * fElapsedTime);
				}
				DrawStringDecal({ 16,16 }, "Level " + std::to_string(level) + " complete!", WHITE, { 4,8 });
			}
			if (colTransferAmt<1){
				colTransferAmt+=fElapsedTime*0.5;
				topCol=PixelLerp(topCurrentCol,topTargetCol,colTransferAmt);
				botCol=PixelLerp(botCurrentCol,botTargetCol,colTransferAmt);
			} else {
				topCol=topCurrentCol=colorCycle[topTargetColInd];
				botCol=botCurrentCol=colorCycle[botTargetColInd];
				topTargetColInd=(topTargetColInd+1)%colorCycle.size();
				botTargetColInd=(topTargetColInd+1)%colorCycle.size();
				topTargetCol=colorCycle[topTargetColInd];
				botTargetCol=colorCycle[botTargetColInd];
				colTransferAmt=0;
			}
		}break;
	}
	if (GetKey(olc::F5).bPressed) {
		mode = (GAMEMODE)!(mode&1);
		if (mode == GAMEMODE::GAME) {
			LoadLevel(level);
		}
	}
	return true;
}

void FaceBall::OnTextEntryComplete(const std::string& sText) {
	switch (mode) {
		case GAME: {
		}break;
		case EDITOR: {
			editor.OnTextEntryComplete(sText);
		}break;
	}
}

//This function adds to the game score and increments lives appropriately.
void FaceBall::AddScore(int score) {
	this->score += score;
	if (lastAwardedScore/10000 != this->score/10000) {
		lives++;
		lastAwardedScore = this->score;
	}
}

vf2d FaceBall::GetRandomizedSpawnPosition()
{
	return knownGoodPositions[rand()%knownGoodPositions.size()];
}

void FaceBall::ResetScore() {
	score = 0;
	lastAwardedScore = 0;
}

int FaceBall::EnemiesAlive(){
	int alive=0;
	for (Enemy&e:enemies){
		if (!e.isDead()){
			alive++;
		}
	}
	return alive;
}

int main()
{
	FaceBall demo;
	if (demo.Construct(1280, 720, 2, 2))
		demo.Start();
	return 0;
}
