#include "App.h"


BaseApp *app = new App();

void App::moveCamera(const vec3 &dir){
	vec3 newPos = camPos + dir * (speed * frameTime);

	vec3 point;
	const BTri *tri;
	if (bsp.intersects(camPos, newPos, &point, &tri)){
		newPos = point + tri->plane.xyz();
	}
	bsp.pushSphere(newPos, 20);

	camPos = newPos;
}

void App::resetCamera(){
	camPos = vec3(-755, -280, -1460);
	wx = -0.13f;
	wy = -0.6f;
}
f
bool App::onKey(const uint key, const bool pressed){
	if (D3DApp::onKey(key, pressed)) return true;

	if (pressed){
		if (key >= '1' && key <= '6'){
			sampleMode = key - '0';
			return true;
		} else if (key == '0'){
			wobbleView = !wobbleView;
		} else if (key == '9'){
			useDynamicBranching = !useDynamicBranching;
		}
	}

	return false;
}

bool App::init(){
	lightDir = normalize(vec3(0.75f, 1.1f, 1));
	sampleMode = 3;
	wobbleView = true;
	useDynamicBranching = true;


	map = new Model();
	if (!map->loadT3d("../Models/Castle2/Map.t3d")) return false;

	TexturePacker texPacker;

	uint nIndices = map->getIndexCount();
	vec3 *src = (vec3 *) map->getStream(0).vertices;
	uint *inds = map->getStream(0).indices;

	for (uint i = 0; i < nIndices; i += 6){
		vec3 v0 = src[inds[i]];
		vec3 v1 = src[inds[i + 1]];
		vec3 v3 = src[inds[i + 5]];

		float w = length(v1 - v0) / 24;
		float h = length(v3 - v0) / 24;

		if (w < 8) w = 8;
		if (h < 8) h = 8;

		uint tw = (int) w;
		uint th = (int) h;

		tw = (tw + 4) & ~7;
		th = (th + 4) & ~7;

		texPacker.addRectangle(tw, th);
	}

	uint tw = 512;
	uint th = 256;

	if (!texPacker.assignCoords(&tw, &th)){
		ErrorMsg("Lightmap too small");
		return false;
	}

	tw = getUpperPowerOfTwo(tw);
	th = getUpperPowerOfTwo(th);


	uint nLmCoords = (nIndices / 6) * 4;
	vec2 *lmCoords = new vec2[nLmCoords];
	uint *lmIndices = new uint[nIndices];

	uint index = 0;
	for (uint i = 0; i < nIndices; i += 6){
		TextureRectangle *rect = texPacker.getRectangle(index);

		float x0 = float(rect->x + 0.5f) / tw;
		float y0 = float(rect->y + 0.5f) / th;
		float x1 = float(rect->x + rect->width  - 0.5f) / tw;
		float y1 = float(rect->y + rect->height - 0.5f) / th;

		lmCoords[4 * index + 0] = vec2(x0, y0);
		lmCoords[4 * index + 1] = vec2(x1, y0);
		lmCoords[4 * index + 2] = vec2(x1, y1);
		lmCoords[4 * index + 3] = vec2(x0, y1);

		lmIndices[i + 0] = 4 * index;
		lmIndices[i + 1] = 4 * index + 1;
		lmIndices[i + 2] = 4 * index + 2;
		lmIndices[i + 3] = 4 * index;
		lmIndices[i + 4] = 4 * index + 2;
		lmIndices[i + 5] = 4 * index + 3;

		vec3 v0 = src[inds[i]];
		vec3 v1 = src[inds[i + 1]];
		vec3 v2 = src[inds[i + 2]];
		vec3 v3 = src[inds[i + 5]];

		bsp.addTriangle(v0, v1, v2);
		bsp.addTriangle(v0, v2, v3);

		index++;
	}
	bsp.build();

	map->addStream(TYPE_TEXCOORD, 2, nLmCoords, (float *) lmCoords, lmIndices, true);


#ifdef COMPUTE_LIGHTMAP
	Image lightMapImage;
	if (!lightMapImage.loadDDS("LightMap.dds")){

		/*****	Lightmap generation code *****/
		SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);

		vec3 lightDirs[L_SAMPLE_COUNT];
		vec3 occlusionDirs[O_SAMPLE_COUNT];

		lightDirs[0] = lightDir;
		vec3 v;
		for (uint x = 1; x < L_SAMPLE_COUNT; x++){
			do {
				v = vec3(float(rand()), float(rand()), float(rand())) * (2.0f / RAND_MAX) - 1.0f;
			} while (dot(v, v) > 1);
			lightDirs[x] = normalize(lightDir + 0.11f * v);
		}

		for (uint x = 0; x < O_SAMPLE_COUNT; x++){
			do {
				v = vec3(float(rand()), float(rand()), float(rand())) * (2.0f / RAND_MAX) - 1.0f;
			} while (dot(v, v) > 1);
			occlusionDirs[x] = normalize(v);
		}

		
		ubyte *lMap = lightMapImage.create(FORMAT_IA8, tw, th, 1, 1);
		memset(lMap, 0x7F, 2 * tw * th);


		index = 0;
		for (uint i = 0; i < nIndices; i += 6){
			TextureRectangle *rect = texPacker.getRectangle(index);

			vec3 dirS = (src[inds[i + 1]] - src[inds[i]]);
			vec3 dirT = (src[inds[i + 5]] - src[inds[i]]);
			vec3 pos = src[inds[i]];
			vec3 normal = cross(dirS, dirT);

			for (int t = 0; t < int(rect->height); t++){
				for (int s = 0; s < int(rect->width); s++){
					vec3 samplePos = pos + saturate((float(s) / (rect->width - 1))) * dirS + saturate((float(t) / (rect->height - 1))) * dirT;
					bsp.pushSphere(samplePos, 5);

					float light = 0.0f;
					for (uint k = 0; k < L_SAMPLE_COUNT; k++){
						if (dot(lightDirs[k], normal) > 0){
							if (!bsp.intersectsCached(samplePos, samplePos + 10000 * lightDirs[k])) light += 1.0f / L_SAMPLE_COUNT;
						}
					}

					lMap[2 * ((rect->y + t) * tw + (rect->x + s))] = (ubyte) (255.0f * light);
				}
			}
			index++;
		}

		index = 0;
		for (uint i = 0; i < nIndices; i += 6){
			TextureRectangle *rect = texPacker.getRectangle(index);

			vec3 dirS = (src[inds[i + 1]] - src[inds[i]]);
			vec3 dirT = (src[inds[i + 5]] - src[inds[i]]);
			vec3 pos = src[inds[i]];
			vec3 normal = normalize(cross(dirS, dirT));

			for (int t = 0; t < int(rect->height); t++){
				for (int s = 0; s < int(rect->width); s++){
					vec3 samplePos = pos + (saturate((float(s) / (rect->width - 1))) * 0.998f + 0.001f) * dirS + (saturate((float(t) / (rect->height - 1))) * 0.998f + 0.001f) * dirT + normal;
					bsp.pushSphere(samplePos, 3);

					float ambientOcclusion = 0.0f;
					vec3 point;
					const BTri *tri;
					for (uint k = 0; k < O_SAMPLE_COUNT; k++){
						float diffuse = dot(occlusionDirs[k], normal);
						if (diffuse <= 0) continue;

						// Quite fake ambient occlusion, but good enough
						if (!bsp.intersects(samplePos, samplePos + 10000 * occlusionDirs[k], &point, &tri)){
							float occ = 0.5f + 0.5f * dot(occlusionDirs[k], lightDir);
							occ *= occ;
							occ *= occ;
							ambientOcclusion += occ * (1.0f / O_SAMPLE_COUNT);
						}
					}

					lMap[2 * ((rect->y + t) * tw + (rect->x + s)) + 1] = (ubyte) (255.0f * saturate(5.3f * ambientOcclusion) + 0.5f);
				}
			}

			index++;
		}

		lightMapImage.saveDDS("LightMap.dds");
	}
#endif

	map->cleanUp();

	return true;
}

void App::exit(){
	delete map;
}

bool App::load(){
	if (caps.PixelShaderVersion < D3DPS_VERSION(3,0) || caps.VertexShaderVersion < D3DVS_VERSION(3,0)){
		ErrorMsg("This demo requires ps3.0 and vs3.0.");
		return false;
	}

	// Shaders
	if ((lighting[ 0] = renderer->addShader("lighting.shd", "#define NO_SPECULAR\n")) == SHADER_NONE) return false;
	if ((lighting[ 1] = renderer->addShader("lighting.shd")) == SHADER_NONE) return false;
	if ((lighting[ 2] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE3\n")) == SHADER_NONE) return false;
	if ((lighting[ 3] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE4\n")) == SHADER_NONE) return false;
	if ((lighting[ 4] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE5\n")) == SHADER_NONE) return false;
	if ((lighting[ 5] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE7\n")) == SHADER_NONE) return false;
	if ((lighting[ 6] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE11\n")) == SHADER_NONE) return false;
	if ((lighting[ 7] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE3\n#define USE_DYNAMIC_BRANCHING\n")) == SHADER_NONE) return false;
	if ((lighting[ 8] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE4\n#define USE_DYNAMIC_BRANCHING\n")) == SHADER_NONE) return false;
	if ((lighting[ 9] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE5\n#define USE_DYNAMIC_BRANCHING\n")) == SHADER_NONE) return false;
	if ((lighting[10] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE7\n#define USE_DYNAMIC_BRANCHING\n")) == SHADER_NONE) return false;
	if ((lighting[11] = renderer->addShader("lighting.shd", "#define SUPERSAMPLE11\n#define USE_DYNAMIC_BRANCHING\n")) == SHADER_NONE) return false;

	if ((skyBox = renderer->addShader("skybox.shd")) == SHADER_NONE) return false;

	// Samplerstates
	if ((bilinear = renderer->addSamplerState(BILINEAR, CLAMP, CLAMP, CLAMP)) == SS_NONE) return false;
	if ((trilinearAniso = renderer->addSamplerState(TRILINEAR_ANISO, WRAP, WRAP, WRAP)) == SS_NONE) return false;

	
	// Textures
	if ((base[0] = renderer->addTexture("../Textures/stone34.dds",       true, trilinearAniso)) == TEXTURE_NONE) return false;
	if ((base[1] = renderer->addTexture("../Textures/brick01.dds",       true, trilinearAniso)) == TEXTURE_NONE) return false;
	if ((base[2] = renderer->addTexture("../Textures/StoneWall_2-2.dds", true, trilinearAniso)) == TEXTURE_NONE) return false;

	if ((bump[0] = renderer->addNormalMap("../Textures/stone34Bump.dds",       FORMAT_RGB565, true, trilinearAniso)) == TEXTURE_NONE) return false;
	if ((bump[1] = renderer->addNormalMap("../Textures/brick01Bump.dds",       FORMAT_RGB565, true, trilinearAniso, 0.3f)) == TEXTURE_NONE) return false;
	if ((bump[2] = renderer->addNormalMap("../Textures/StoneWall_2-2Bump.dds", FORMAT_RGB565, true, trilinearAniso, 0.3f)) == TEXTURE_NONE) return false;


	if ((lightMap = renderer->addTexture("LightMap.dds", true, linearClamp)) == TEXTURE_NONE) return false;


	// Cubemap
	const char *files[] = {
		"../Textures/CubeMaps/Nottingham/posx.jpg",
		"../Textures/CubeMaps/Nottingham/negx.jpg",
		"../Textures/CubeMaps/Nottingham/posy.jpg",
		"../Textures/CubeMaps/Nottingham/negy.jpg",
		"../Textures/CubeMaps/Nottingham/posz.jpg",
		"../Textures/CubeMaps/Nottingham/negz.jpg",
	};
	/*const char *files[] = {
		"../Textures/CubeMaps/Nottingham/posx.tga",
		"../Textures/CubeMaps/Nottingham/negx.tga",
		"../Textures/CubeMaps/Nottingham/posy.tga",
		"../Textures/CubeMaps/Nottingham/negy.tga",
		"../Textures/CubeMaps/Nottingham/posz.tga",
		"../Textures/CubeMaps/Nottingham/negz.tga",
	};*/
	if ((env = renderer->addCubemap(files, true, bilinear)) == TEXTURE_NONE) return false;

	// Vertex declaration
	FormatDesc format[] = { 0, TYPE_VERTEX, FORMAT_FLOAT, 3 };
	if ((sbVF = renderer->addVertexFormat(format, elementsOf(format))) == VF_NONE) return false;

	// Upload map to vertex/index buffer
	if (!map->makeDrawable(renderer, true)) return false;

	return true;
}

void App::unload(){
	
}

void App::drawEnvironment(const float4x4 &mvp){
	static float vertices[] = { -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1 };
	static ushort indices[] = { 0,1,2,  1,3,2,  1,5,3,  5,7,3,  5,4,7,  4,6,7,  4,0,6,  0,2,6,  4,5,0,  5,1,0,  2,3,6,  3,7,6 };

	renderer->reset();
	renderer->setShader(skyBox);
	renderer->setVertexFormat(sbVF);
	renderer->setTexture("Env", env, linearClamp);
	renderer->setDepthState(noDepthWrite);
	renderer->setShaderConstant4x4f("mvp", mvp);
	renderer->apply();

	dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 8, 12, indices, D3DFMT_INDEX16, vertices, sizeof(float3));
}

void App::drawFrame(){
	float nwx = wx;
	if (wobbleView) nwx += 0.02f * sinf(1.5f * time);

	mat4 projection = toD3DProjection(perspectiveMatrixX(1.5f, width, height, 10, 7000));
	mat4 modelview = rotateXY(-nwx, -wy) * translate(-camPos);

	// Force depth to 1 for environment to put it behind everything
	mat4 envP = translate(0, 0, 1) * scale(1, 1, 0) * projection;
	mat4 envMV = rotateXY(-nwx, 0.5f - wy);

	mat4 mvp = projection * modelview;


    dev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0);

	const float gloss[] = { 0.8f, 0.0f, 0.0f };
	for (int i = map->getBatchCount() - 1; i >= 0; i--){
		bool hasSpecular = (gloss[i] > 0);

		int shaderIndex = 0;
		if (hasSpecular){
			shaderIndex = sampleMode;
			if (useDynamicBranching && sampleMode > 1){
				shaderIndex += 5;
			}
		}

		renderer->reset();
		renderer->setRasterizerState(cullBack);
		renderer->setShader(lighting[shaderIndex]);
		renderer->setTexture("Base", base[i], trilinearAniso);
		renderer->setTexture("Bump", bump[i], trilinearAniso);
		renderer->setTexture("LightMap", lightMap, linearClamp);
		renderer->setShaderConstant4x4f("mvp", mvp);
		renderer->setShaderConstant3f("lightDir", lightDir);
		renderer->setShaderConstant3f("camPos", camPos);
		if (hasSpecular) renderer->setShaderConstant1f("gloss", gloss[i]);
		renderer->apply();

		map->drawBatch(renderer, i);
	}


	drawEnvironment(envP * envMV);


	renderer->setup2DMode(0, (float) width, 0, (float) height);

	char str[64];
	static uint samples[] = { 3, 4, 5, 7, 11 };
	if (sampleMode > 1){
		sprintf(str, "%d samples", samples[sampleMode - 2]);
	} else {
		strcpy(str, "No supersampling");
	}

	renderer->drawText(str, 10, height - 78.0f, 30, 36, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

	sprintf(str, "Dynamic branching: %s", useDynamicBranching? "On" : "Off");
	renderer->drawText(str, 10, height - 42.0f, 30, 36, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);
}
