
/* * * * * * * * * * * * * Author's note * * * * * * * * * * * *\
*   _       _   _       _   _       _   _       _     _ _ _ _   *
*  |_|     |_| |_|     |_| |_|_   _|_| |_|     |_|  _|_|_|_|_|  *
*  |_|_ _ _|_| |_|     |_| |_|_|_|_|_| |_|     |_| |_|_ _ _     *
*  |_|_|_|_|_| |_|     |_| |_| |_| |_| |_|     |_|   |_|_|_|_   *
*  |_|     |_| |_|_ _ _|_| |_|     |_| |_|_ _ _|_|  _ _ _ _|_|  *
*  |_|     |_|   |_|_|_|   |_|     |_|   |_|_|_|   |_|_|_|_|    *
*                                                               *
*                     http://www.humus.name                     *
*                                                                *
* This file is a part of the work done by Humus. You are free to   *
* use the code in any way you like, modified, unmodified or copied   *
* into your own work. However, I expect you to respect these points:  *
*  - If you use this file and its contents unmodified, or use a major *
*    part of this file, please credit the author and leave this note. *
*  - For use in anything commercial, please request my approval.     *
*  - Share your work and ideas too as much as you can.             *
*                                                                *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "App.h"

#include "../Framework3/Util/Hash.h"

BaseApp *app = new App();

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

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

	camPos = newPos;
}

void App::resetCamera()
{
	camPos = vec3(1992, 30, -432);
	wx = 0.078f;
	wy = 0.62f;
}

bool App::onKey(const uint key, const bool pressed)
{
	if (D3D10App::onKey(key, pressed)) return true;

	if (pressed)
	{
		if (key == KEY_F5)
		{
			m_UseGPAA->setChecked(!m_UseGPAA->isChecked());
		}
	}

	return false;
}

void App::onSize(const int w, const int h)
{
	D3D10App::onSize(w, h);

	if (renderer)
	{
		// Make sure render targets are the size of the window
		renderer->resizeRenderTarget(m_BaseRT,   w, h, 1, 1, 1);
		renderer->resizeRenderTarget(m_NormalRT, w, h, 1, 1, 1);
		renderer->resizeRenderTarget(m_DepthRT,  w, h, 1, 1, 1);
	}
}

bool App::init()
{
	// No framework created depth buffer
	depthBits = 0;

	// Load map
	m_Map = new Model();
	if (!m_Map->loadObj("../Models/Corridor2/MapFixed.obj")) return false;
	m_Map->scale(0, float3(1, 1, -1));

	// Create BSP for collision detection
	uint nIndices = m_Map->getIndexCount();
	float3 *src = (float3 *) m_Map->getStream(0).vertices;
	uint *inds = m_Map->getStream(0).indices;

	for (uint i = 0; i < nIndices; i += 3)
	{
		const float3 &v0 = src[inds[i]];
		const float3 &v1 = src[inds[i + 1]];
		const float3 &v2 = src[inds[i + 2]];

		m_BSP.addTriangle(v0, v1, v2);
	}
	m_BSP.build();

	m_Map->computeTangentSpace(true);

	// Create light sphere for deferred shading
	m_Sphere = new Model();
	m_Sphere->createSphere(3);

	// Initialize all lights
	m_Lights[ 0].position = float3( 576, 96,    0); m_Lights[ 0].radius = 640.0f;
	m_Lights[ 1].position = float3( 0,   96,  576); m_Lights[ 1].radius = 640.0f;
	m_Lights[ 2].position = float3(-576, 96,    0); m_Lights[ 2].radius = 640.0f;
	m_Lights[ 3].position = float3( 0,   96, -576); m_Lights[ 3].radius = 640.0f;
	m_Lights[ 4].position = float3(1792, 96,  320); m_Lights[ 4].radius = 550.0f;
	m_Lights[ 5].position = float3(1792, 96, -320); m_Lights[ 5].radius = 550.0f;
	m_Lights[ 6].position = float3(-192, 96, 1792); m_Lights[ 6].radius = 550.0f;
	m_Lights[ 7].position = float3(-832, 96, 1792); m_Lights[ 7].radius = 550.0f;
	m_Lights[ 8].position = float3(1280, 32,  192); m_Lights[ 8].radius = 450.0f;
	m_Lights[ 9].position = float3(1280, 32, -192); m_Lights[ 9].radius = 450.0f;
	m_Lights[10].position = float3(-320, 32, 1280); m_Lights[10].radius = 450.0f;
	m_Lights[11].position = float3(-704, 32, 1280); m_Lights[11].radius = 450.0f;
	m_Lights[12].position = float3( 960, 32,  640); m_Lights[12].radius = 450.0f;
	m_Lights[13].position = float3( 960, 32, -640); m_Lights[13].radius = 450.0f;
	m_Lights[14].position = float3( 640, 32, -960); m_Lights[14].radius = 450.0f;
	m_Lights[15].position = float3(-640, 32, -960); m_Lights[15].radius = 450.0f;
	m_Lights[16].position = float3(-960, 32,  640); m_Lights[16].radius = 450.0f;
	m_Lights[17].position = float3(-960, 32, -640); m_Lights[17].radius = 450.0f;
	m_Lights[18].position = float3( 640, 32,  960); m_Lights[18].radius = 450.0f;

	// Init GUI components
	int tab = configDialog->addTab("GPAA");
	configDialog->addWidget(tab, m_UseGPAA = new CheckBox(0, 0, 150, 36, "Use GPAA", true));

	return true;
}

void App::exit()
{
	delete m_Sphere;
	delete m_Map;
}

bool App::initAPI()
{
	// Override the user's MSAA settings
	return D3D10App::initAPI(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, DXGI_FORMAT_UNKNOWN, 1, NO_SETTING_CHANGE | SAMPLE_BACKBUFFER);
}

void App::exitAPI()
{
	D3D10App::exitAPI();
}

bool App::load()
{
	// Shaders
	if ((m_FillBuffers = renderer->addShader("FillBuffers.shd")) == SHADER_NONE) return false;
	if ((m_Ambient     = renderer->addShader("Ambient.shd"    )) == SHADER_NONE) return false;
	if ((m_Lighting    = renderer->addShader("Lighting.shd"   )) == SHADER_NONE) return false;
	if ((m_AntiAlias   = renderer->addShader("GPAA.shd"       )) == SHADER_NONE) return false;

	// Samplerstates
	if ((m_BaseFilter = renderer->addSamplerState(TRILINEAR_ANISO, WRAP, WRAP, WRAP)) == SS_NONE) return false;
	if ((m_PointClamp = renderer->addSamplerState(NEAREST, CLAMP, CLAMP, CLAMP)) == SS_NONE) return false;

	// Main render targets
	if ((m_BaseRT   = renderer->addRenderTarget(width, height, 1, 1, 1, FORMAT_RGBA8,  1, SS_NONE, SRGB)) == TEXTURE_NONE) return false;
	if ((m_NormalRT = renderer->addRenderTarget(width, height, 1, 1, 1, FORMAT_RGBA8S, 1, SS_NONE)) == TEXTURE_NONE) return false;
	if ((m_DepthRT  = renderer->addRenderDepth (width, height, 1,       FORMAT_D16,    1, SS_NONE, SAMPLE_DEPTH)) == TEXTURE_NONE) return false;

	// Textures
	if ((m_BaseTex[0] = renderer->addTexture  ("../Textures/wood.dds",                    true, m_BaseFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((m_BumpTex[0] = renderer->addNormalMap("../Textures/woodBump.dds", FORMAT_RGBA8S, true, m_BaseFilter      )) == TEXTURE_NONE) return false;

	if ((m_BaseTex[1] = renderer->addTexture  ("../Textures/Tx_imp_wall_01_small.dds",              true, m_BaseFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((m_BumpTex[1] = renderer->addNormalMap("../Textures/Tx_imp_wall_01Bump.dds", FORMAT_RGBA8S, true, m_BaseFilter      )) == TEXTURE_NONE) return false;

	if ((m_BaseTex[2] = renderer->addTexture  ("../Textures/floor_wood_3.dds",                    true, m_BaseFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((m_BumpTex[2] = renderer->addNormalMap("../Textures/floor_wood_3Bump.dds", FORMAT_RGBA8S, true, m_BaseFilter      )) == TEXTURE_NONE) return false;

	if ((m_BaseTex[3] = renderer->addTexture  ("../Textures/light2.dds",                    true, m_BaseFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((m_BumpTex[3] = renderer->addNormalMap("../Textures/light2Bump.dds", FORMAT_RGBA8S, true, m_BaseFilter      )) == TEXTURE_NONE) return false;

	if ((m_BaseTex[4] = renderer->addTexture  ("../Textures/floor_wood_4.dds",                    true, m_BaseFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((m_BumpTex[4] = renderer->addNormalMap("../Textures/floor_wood_4Bump.dds", FORMAT_RGBA8S, true, m_BaseFilter      )) == TEXTURE_NONE) return false;


	// Blendstates
	if ((m_BlendAdd = renderer->addBlendState(ONE, ONE)) == BS_NONE) return false;

	// Depth states - use reversed depth (1 to 0) to improve precision
	if ((m_DepthTest = renderer->addDepthState(true, true, GEQUAL)) == DS_NONE) return false;

	// Rassterizer states
	if ((m_CullBack_DepthBias = renderer->addRasterizerState(CULL_BACK, SOLID, false, false, -1.0f, -1.0f)) == DS_NONE) return false;

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




	// Vertex format for edges
	FormatDesc formatDesc[] =
	{
		{ 0, TYPE_VERTEX, FORMAT_FLOAT, 3 },
		{ 0, TYPE_VERTEX, FORMAT_FLOAT, 3 },
	};
	if ((m_EdgesVF = renderer->addVertexFormat(formatDesc, elementsOf(formatDesc), m_AntiAlias)) == VF_NONE) return false;

	
	// Finds all edges to use for GPAA. This code makes sure each edge is added only once
	// and that internal edges (such as the diagonal in a quad) are not used.
	uint index_count = m_Map->getIndexCount();
	uint *indices = m_Map->getStream(0).indices;
	float3 *src_vertices = (float3 *) m_Map->getStream(0).vertices;

	Hash hash(2, index_count >> 3, index_count);

	Array<Edge> edges;
	for (uint i = 0; i < index_count; i += 3)
	{
		const uint i0 = indices[i];
		const uint i1 = indices[i + 1];
		const uint i2 = indices[i + 2];

		const float3 &v0 = src_vertices[i0];
		const float3 &v1 = src_vertices[i1];
		const float3 &v2 = src_vertices[i2];

		vec3 normal = normalize(cross(v1 - v0, v2 - v0));

		uint index;
		uint last_n = 2;
		for (int n = 0; n < 3; n++)
		{
			Edge edge(indices[i + last_n], indices[i + n]);
			if (hash.insert(&edge.index0, &index))
			{
				if (dot(normal, edges[index].normal) > 0.99f)
				{
					edges[index].remove = true;
				}
			}
			else
			{
				edge.normal = normal;
				edge.remove = false;
				edges.add(edge);
			}
			last_n = n;
		}

	}

	// Create the edge buffer for post-process antialiasing.
	uint edge_count = edges.getCount();
	Vertex *vertices = new Vertex[edge_count * 2];
	Vertex *dest = vertices;

	for (uint i = 0; i < edge_count; i++)
	{
		if (!edges[i].remove)
		{
			dest->v0 = src_vertices[edges[i].index0];
			dest->v1 = src_vertices[edges[i].index1];
			dest++;
			dest->v0 = src_vertices[edges[i].index1];
			dest->v1 = src_vertices[edges[i].index0];
			dest++;
		}
	}

	m_EdgesVertexCount = dest - vertices;

	if ((m_EdgesVB = renderer->addVertexBuffer(m_EdgesVertexCount * sizeof(Vertex), STATIC, vertices)) == VB_NONE) return false;

	delete [] vertices;

	return true;
}

void App::unload()
{

}

void App::drawFrame()
{
	const float near_plane = 20.0f;
	const float far_plane = 4000.0f;

	// Reversed depth
	float4x4 projection = toD3DProjection(perspectiveMatrixY(1.2f, width, height, far_plane, near_plane));
	float4x4 view = rotateXY(-wx, -wy);
	view.translate(-camPos);
	float4x4 viewProj = projection * view;
	// Pre-scale-bias the matrix so we can use the screen position directly
	float4x4 viewProjInv = (!viewProj) * (translate(-1.0f, 1.0f, 0.0f) * scale(2.0f, -2.0f, 1.0f));


	TextureID bufferRTs[] = { m_BaseRT, m_NormalRT };
	renderer->changeRenderTargets(bufferRTs, elementsOf(bufferRTs), m_DepthRT);

		renderer->clear(false, true, false, NULL, 0.0f);

		/*
			Main scene pass.
			This is where the buffers are filled for the later deferred passes.
		*/
		renderer->reset();
		renderer->setRasterizerState(m_CullBack_DepthBias);
		renderer->setShader(m_FillBuffers);
		renderer->setShaderConstant4x4f("ViewProj", viewProj);
		renderer->setSamplerState("Filter", m_BaseFilter);
		renderer->setDepthState(m_DepthTest);
		renderer->apply();

		const uint batch_count = m_Map->getBatchCount();
		for (uint i = 0; i < batch_count; i++)
		{
			renderer->setTexture("Base", m_BaseTex[i]);
			renderer->setTexture("Bump", m_BumpTex[i]);
			renderer->applyTextures();

			m_Map->drawBatch(renderer, i);
		}

	renderer->changeToMainFramebuffer();

	/*
		Deferred ambient pass.
	*/
	renderer->reset();
	renderer->setRasterizerState(cullNone);
	renderer->setDepthState(noDepthTest);
	renderer->setShader(m_Ambient);
	renderer->setTexture("Base", m_BaseRT);
	renderer->setSamplerState("Filter", m_PointClamp);
	renderer->apply();


	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	device->Draw(3, 0);


	/*
		Deferred lighting pass.
	*/
	renderer->reset();
	renderer->setDepthState(noDepthTest);
	renderer->setShader(m_Lighting);
	renderer->setRasterizerState(cullFront);
	renderer->setBlendState(m_BlendAdd);
	renderer->setShaderConstant4x4f("ViewProj", viewProj);
	renderer->setShaderConstant4x4f("ViewProjInv", viewProjInv * scale(1.0f / width, 1.0f / height, 1.0f));
	renderer->setShaderConstant3f("CamPos", camPos);
	renderer->setTexture("Base", m_BaseRT);
	renderer->setTexture("Normal", m_NormalRT);
	renderer->setTexture("Depth", m_DepthRT);
	renderer->apply();

	float2 zw = projection.rows[2].zw();
	for (uint i = 0; i < LIGHT_COUNT; i++)
	{
		float3 lightPos = m_Lights[i].position;
		float radius = m_Lights[i].radius;
		float invRadius = 1.0f / radius;

		// Compute z-bounds
		float4 lPos = view * float4(lightPos, 1.0f);
		float z1 = lPos.z + radius;

		if (z1 > near_plane)
		{
			float z0 = max(lPos.z - radius, near_plane);

			float2 zBounds;
			zBounds.y = saturate(zw.x + zw.y / z0);
			zBounds.x = saturate(zw.x + zw.y / z1);

			renderer->setShaderConstant3f("LightPos", lightPos);
			renderer->setShaderConstant1f("Radius", radius);
			renderer->setShaderConstant1f("InvRadius", invRadius);
			renderer->setShaderConstant2f("ZBounds", zBounds);
			renderer->applyConstants();

			m_Sphere->draw(renderer);
		}
	}


	if (m_UseGPAA->isChecked())
	{
		renderer->changeRenderTarget(FB_COLOR, m_DepthRT);

		// Copy backbuffer to a texture
		Direct3D10Renderer *d3d10_renderer = (Direct3D10Renderer *) renderer;
		device->CopyResource(d3d10_renderer->getResource(m_BaseRT), d3d10_renderer->getResource(backBufferTexture));

		/*
			GPAA antialiasing pass
		*/
		renderer->reset();
		renderer->setDepthState(m_DepthTest);
		renderer->setShader(m_AntiAlias);
		renderer->setRasterizerState(cullNone);
		renderer->setShaderConstant4x4f("ViewProj", viewProj);
		renderer->setShaderConstant4f("ScaleBias", float4(0.5f * width, -0.5f * height, 0.5f * width, 0.5f * height));
		renderer->setShaderConstant2f("PixelSize", float2(1.0f / width, 1.0f / height));
		renderer->setTexture("BackBuffer", m_BaseRT);
		renderer->setSamplerState("Filter", linearClamp);
		renderer->setVertexFormat(m_EdgesVF);
		renderer->setVertexBuffer(0, m_EdgesVB);
		renderer->apply();

		renderer->drawArrays(PRIM_LINES, 0, m_EdgesVertexCount);
	}
}
