#include "TerrainNode.h"

#define WORLD_HEIGHT 1024

TerrainNode::TerrainNode(wstring name, wstring seed, float waterHeight, int widthX, int widthZ, int cellSizeX, int cellSizeZ) : SceneNode(name)
{	
	_seedString = seed;
	_seedHash = std::hash<wstring>{}(_seedString);
	
	srand(_seedHash);

	_widthX = widthX;
	_widthZ = widthZ;

	_gridCols = _widthX + 1;
	_gridRows = _widthZ + 1;

	_cellSizeX = cellSizeX;
	_cellSizeZ = cellSizeZ;

	_waterHeight = waterHeight;

	_polygonsCount = (_gridCols * _gridRows) * 2;
	_vertexCount = _polygonsCount * 2;
	_indicesCount = _polygonsCount * 3;
}

void TerrainNode::SetAmbientLight(XMFLOAT4 ambientLight)
{
	_ambientLight = ambientLight;
}

void TerrainNode::SetWaterColor(XMFLOAT4 waterColor)
{
	_waterColor = waterColor;
}

void TerrainNode::SetDirectionalLight(FXMVECTOR lightVector, XMFLOAT4 lightColor)
{
	_directionalLightColor = lightColor;
	XMStoreFloat4(&_directionalLightVector, lightVector);
}

bool TerrainNode::Initialise()
{
	_device = DirectXFramework::GetDXFramework()->GetDevice();
	_deviceContext = DirectXFramework::GetDXFramework()->GetDeviceContext();

	if (_device.Get() == nullptr || _deviceContext.Get() == nullptr || _initError)
	{
		return false;
	}

	GenerateTerrainData();
	GenerateTerrainNormals();
	LoadTerrainTextures();
	GenerateBlendMap();
	BuildExtraMaps();

	GenerateBuffers();

	BuildShaders();
	BuildVertexLayout();
	BuildConstantBuffer();
	BuildRendererStates();
	return true;
}

void TerrainNode::GenerateTerrainData()
{	
	float totalWidth = (float)(_gridCols * _cellSizeX);
	float totalDepth = (float)(_gridRows * _cellSizeZ);

	float centerWidth = totalWidth * -0.5f;
	float centerDepth = totalDepth * 0.5f;

	_terrainStartX = centerWidth;
	_terrainStartZ = centerDepth;
	_terrainEndX = centerWidth + totalWidth - 1;
	_terrainEndZ = centerDepth - totalDepth + 1;

	float bu = 1.0f / (float)(_gridCols - 1u);
	float bv = 1.0f / (float)(_gridRows - 1u);

	int currentVertexCount = 0;
	for (int z = 0; z < _gridRows; z++)
	{
		for (int x = 0; x < _gridCols; x++)
		{
			int currentIndex = (z * _gridCols) + x;
			int offsetIndexX = 1;
			if (x == _widthX)
			{
				offsetIndexX = 0;
			}
			int offsetIndexZ = _gridCols;
			if (z == _widthZ)
			{
				offsetIndexZ = 0;
			}

			// Generate pairs of random UV's
			float rUA = SharedMethods::GenerateRandomUV(0.0f, 0.25f);
			float rVA = SharedMethods::GenerateRandomUV(0.0f, 0.25f); //rUA; // 0.0f;
			float rUB = SharedMethods::GenerateRandomUV(0.75f, 1.0f);
			float rVB = SharedMethods::GenerateRandomUV(0.75f, 1.0f); //rUB;

			// Rotate the UV's around a bit to add some more variance
			int tumbleAmount = rand() % 10;

			float prevUV = 0.0f;
			for (int ti = 0; ti < tumbleAmount; ti++)
			{
				prevUV = rVB;
				rVB = rUB;
				rUB = rVA;
				rVA = rUA;
				rUA = prevUV;
			}

			// TL
			TerrainVertex vertex{};
			vertex.Position = XMFLOAT3(x * _cellSizeX + centerWidth, GetHeightValueAt(currentIndex), (-z + 1) * _cellSizeZ + centerDepth);
			vertex.Normal = XMFLOAT3(0.0f, 0.0f, 0.0f);
			vertex.TexCoord = XMFLOAT2(rUA, rVA);
			vertex.BlendMapTexCoord = XMFLOAT2((bu * x), (bv * z));
			_terrainVerts.push_back(vertex);

			// TR
			vertex = TerrainVertex();
			vertex.Position = XMFLOAT3((x + 1) * _cellSizeX + centerWidth, GetHeightValueAt(currentIndex + offsetIndexX), (-z + 1) * _cellSizeZ + centerDepth);
			vertex.Normal = XMFLOAT3(0.0f, 0.0f, 0.0f);
			vertex.TexCoord = XMFLOAT2(rUB, rVA);
			vertex.BlendMapTexCoord = XMFLOAT2((bu * (x + 1)), (bv * z));
			_terrainVerts.push_back(vertex);

			// BL
			vertex = TerrainVertex();
			vertex.Position = XMFLOAT3(x * _cellSizeX + centerWidth, GetHeightValueAt(currentIndex + offsetIndexZ), -z * _cellSizeZ + centerDepth);
			vertex.Normal = XMFLOAT3(0.0f, 0.0f, 0.0f);
			vertex.TexCoord = XMFLOAT2(rUA, rVB);
			vertex.BlendMapTexCoord = XMFLOAT2((bu * x), (bv * (z + 1)));
			_terrainVerts.push_back(vertex);

			// BR
			vertex = TerrainVertex();
			vertex.Position = XMFLOAT3((x + 1) * _cellSizeX + centerWidth, GetHeightValueAt(currentIndex + offsetIndexZ + offsetIndexX), -z * _cellSizeZ + centerDepth);
			vertex.Normal = XMFLOAT3(0.0f, 0.0f, 0.0f);
			vertex.TexCoord = XMFLOAT2(rUB, rVB);
			vertex.BlendMapTexCoord = XMFLOAT2((bu * (x + 1)), (bv * (z + 1)));
			_terrainVerts.push_back(vertex);

			_indices.push_back(currentVertexCount);
			_indices.push_back(currentVertexCount + 1);
			_indices.push_back(currentVertexCount + 2);

			_indices.push_back(currentVertexCount + 2);
			_indices.push_back(currentVertexCount + 1);
			_indices.push_back(currentVertexCount + 3);
			currentVertexCount += 4;
		}
	}
}

void TerrainNode::GenerateTerrainNormals()
{
	int currentNormalIndex = 0;
	for (int z = 0; z < _gridRows; z++)
	{
		for (int x = 0; x < _gridCols; x++)
		{
			XMVECTOR v1 = XMVectorSet(_terrainVerts[currentNormalIndex + 1].Position.x - _terrainVerts[currentNormalIndex].Position.x,
				_terrainVerts[currentNormalIndex + 1].Position.y - _terrainVerts[currentNormalIndex].Position.y,
				_terrainVerts[currentNormalIndex + 1].Position.z - _terrainVerts[currentNormalIndex].Position.z,
				0.0f);
			XMVECTOR v2 = XMVectorSet(_terrainVerts[currentNormalIndex + 2].Position.x - _terrainVerts[currentNormalIndex].Position.x,
				_terrainVerts[currentNormalIndex + 2].Position.y - _terrainVerts[currentNormalIndex].Position.y,
				_terrainVerts[currentNormalIndex + 2].Position.z - _terrainVerts[currentNormalIndex].Position.z,
				0.0f);

			XMVECTOR polygonNormal = XMVector3Cross(v1, v2);

			XMStoreFloat3(&_terrainVerts[currentNormalIndex].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex].Normal), polygonNormal));
			XMStoreFloat3(&_terrainVerts[currentNormalIndex + 1].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex + 1].Normal), polygonNormal));
			XMStoreFloat3(&_terrainVerts[currentNormalIndex + 2].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex + 2].Normal), polygonNormal));
			XMStoreFloat3(&_terrainVerts[currentNormalIndex + 3].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[currentNormalIndex + 3].Normal), polygonNormal));

			AddNormalToVertex(z - 1, x - 1, 3, polygonNormal);
			AddNormalToVertex(z - 1, x, 2, polygonNormal);
			AddNormalToVertex(z - 1, x, 3, polygonNormal);
			AddNormalToVertex(z - 1, x + 1, 2, polygonNormal);

			AddNormalToVertex(z, x - 1, 1, polygonNormal);
			AddNormalToVertex(z, x - 1, 3, polygonNormal);
			AddNormalToVertex(z, x + 1, 0, polygonNormal);
			AddNormalToVertex(z, x + 1, 2, polygonNormal);
			AddNormalToVertex(z + 1, x - 1, 1, polygonNormal);
			AddNormalToVertex(z + 1, x, 0, polygonNormal);
			AddNormalToVertex(z + 1, x, 1, polygonNormal);
			AddNormalToVertex(z + 1, x + 1, 0, polygonNormal);

			currentNormalIndex += 4;
		}
	}

	for (int i = 0; i < _terrainVerts.size(); i++)
	{
		XMVECTOR currentNormal = XMLoadFloat3(&_terrainVerts[i].Normal);
		//XMVECTOR normalised = XMVector3Normalize(currentNormal);
		XMStoreFloat3(&_terrainVerts[i].Normal, XMVector3Normalize(currentNormal));
	}
}

void TerrainNode::AddNormalToVertex(int row, int col, int vertexIndex, XMVECTOR normal)
{
	int rowIndexOffset = row * _gridCols;
	int colIndexOffset = col;

	int finalIndex = vertexIndex + ((rowIndexOffset + colIndexOffset) * 4);

	if (row >= 0 && row < (int)_gridRows && col >= 0 && col < (int)_gridCols)
	{
		XMStoreFloat3(&_terrainVerts[finalIndex].Normal, XMVectorAdd(XMLoadFloat3(&_terrainVerts[finalIndex].Normal), normal));
	}
}

float TerrainNode::GetHeightValueAt(int index)
{
	float result = _heightValues[index] * WORLD_HEIGHT; // +(rand() % 10);			

	return result;
}

void TerrainNode::GenerateBuffers()
{
	// Setup the structure that specifies how big the vertex 
		// buffer should be
	D3D11_BUFFER_DESC vertexBufferDescriptor;
	vertexBufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;
	vertexBufferDescriptor.ByteWidth = sizeof(TerrainVertex) * _vertexCount;
	vertexBufferDescriptor.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDescriptor.CPUAccessFlags = 0;
	vertexBufferDescriptor.MiscFlags = 0;
	vertexBufferDescriptor.StructureByteStride = 0;

	// Now set up a structure that tells DirectX where to get the
	// data for the vertices from
	D3D11_SUBRESOURCE_DATA vertexInitialisationData;
	vertexInitialisationData.pSysMem = &_terrainVerts[0];

	// and create the vertex buffer
	ThrowIfFailed(_device->CreateBuffer(&vertexBufferDescriptor, &vertexInitialisationData, _vertexBuffer.GetAddressOf()));

	// Setup the structure that specifies how big the index 
	// buffer should be
	D3D11_BUFFER_DESC indexBufferDescriptor;
	indexBufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;
	indexBufferDescriptor.ByteWidth = sizeof(UINT) * _indicesCount;
	indexBufferDescriptor.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDescriptor.CPUAccessFlags = 0;
	indexBufferDescriptor.MiscFlags = 0;
	indexBufferDescriptor.StructureByteStride = 0;

	// Now set up a structure that tells DirectX where to get the
	// data for the indices from
	D3D11_SUBRESOURCE_DATA indexInitialisationData;
	indexInitialisationData.pSysMem = &_indices[0];

	// and create the index buffer
	ThrowIfFailed(_device->CreateBuffer(&indexBufferDescriptor, &indexInitialisationData, _indexBuffer.GetAddressOf()));
}

void TerrainNode::Render()
{
	XMMATRIX projectionTransformation = DirectXFramework::GetDXFramework()->GetProjectionTransformation();
	XMMATRIX viewTransformation = DirectXFramework::GetDXFramework()->GetCamera()->GetViewMatrix();

	XMMATRIX completeTransformation = XMLoadFloat4x4(&_combinedWorldTransformation) * viewTransformation * projectionTransformation;

	// Draw the first cube
	TCBUFFER cBuffer;
	cBuffer.completeTransformation = completeTransformation;
	cBuffer.worldTransformation = XMLoadFloat4x4(&_worldTransformation);
	cBuffer.ambientColor = _ambientLight; // XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
	//cBuffer.AmbientColor = XMFLOAT4(SharedMethods::RGBValueToIntensity(0xfd), 0.0f, 1.0f, 1.0f); //XMFLOAT4(1, 1, 1, 1); //_ambientLight;
	cBuffer.lightVector = XMVector4Normalize(XMLoadFloat4(&_directionalLightVector));
	//cBuffer.lightColor = XMFLOAT4(0.5f, 0.5f, 0.5f, 0.5f);
	//cBuffer.LightColor = XMFLOAT4(SharedMethods::RGBValueToIntensity(0x89), 0.0f, 5.0f, 5.0f);	
	cBuffer.lightColor = _directionalLightColor;		
	XMStoreFloat4(&cBuffer.cameraPosition, DirectXFramework::GetDXFramework()->GetCamera()->GetCameraPosition());

	cBuffer.waterHeight = _waterHeight;
	cBuffer.waterShininess = 10.0f;
	cBuffer.waterColor = _waterColor;

	_deviceContext->PSSetShaderResources(0, 1, _blendMapResourceView.GetAddressOf());
	_deviceContext->PSSetShaderResources(1, 1, _texturesResourceView.GetAddressOf());
	_deviceContext->PSSetShaderResources(2, 1, _waterNormalMap.GetAddressOf());
	_deviceContext->PSSetShaderResources(3, 1, _snowTest.GetAddressOf());

	_deviceContext->VSSetShader(_vertexShader.Get(), 0, 0);
	_deviceContext->PSSetShader(_pixelShader.Get(), 0, 0);
	_deviceContext->IASetInputLayout(_layout.Get());

	UINT stride = sizeof(TerrainVertex);
	UINT offset = 0;
	_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
	_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
	cBuffer.diffuseCoefficient = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
	cBuffer.specularCoefficient = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
	cBuffer.shininess = 5.0f;
	cBuffer.opacity = 1.0f;	

	// Update the constant buffer 
	_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer.GetAddressOf());
	_deviceContext->PSSetConstantBuffers(0, 1, _constantBuffer.GetAddressOf());
	_deviceContext->UpdateSubresource(_constantBuffer.Get(), 0, 0, &cBuffer, 0, 0);
	_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	//_deviceContext->RSSetState(_wireframeRasteriserState.Get());
	_deviceContext->DrawIndexed(_indicesCount, 0, 0);
}

void TerrainNode::Shutdown(void)
{
}

void TerrainNode::BuildShaders()
{
	DWORD shaderCompileFlags = 0;
#if defined( _DEBUG )
	shaderCompileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

	ComPtr<ID3DBlob> compilationMessages = nullptr;

	//Compile vertex shader
	HRESULT hr = D3DCompileFromFile(L"TerrainShaders.hlsl",
		nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE,
		"VShader", "vs_5_0",
		shaderCompileFlags, 0,
		_vertexShaderByteCode.GetAddressOf(),
		compilationMessages.GetAddressOf());

	if (compilationMessages.Get() != nullptr)
	{
		// If there were any compilation messages, display them
		MessageBoxA(0, (char*)compilationMessages->GetBufferPointer(), 0, 0);
	}
	// Even if there are no compiler messages, check to make sure there were no other errors.
	ThrowIfFailed(hr);
	ThrowIfFailed(_device->CreateVertexShader(_vertexShaderByteCode->GetBufferPointer(), _vertexShaderByteCode->GetBufferSize(), NULL, _vertexShader.GetAddressOf()));

	// Compile pixel shader
	hr = D3DCompileFromFile(L"TerrainShaders.hlsl",
		nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE,
		"PShader", "ps_5_0",
		shaderCompileFlags, 0,
		_pixelShaderByteCode.GetAddressOf(),
		compilationMessages.GetAddressOf());

	if (compilationMessages.Get() != nullptr)
	{
		// If there were any compilation messages, display them
		MessageBoxA(0, (char*)compilationMessages->GetBufferPointer(), 0, 0);
	}
	ThrowIfFailed(hr);
	ThrowIfFailed(_device->CreatePixelShader(_pixelShaderByteCode->GetBufferPointer(), _pixelShaderByteCode->GetBufferSize(), NULL, _pixelShader.GetAddressOf()));
}


void TerrainNode::BuildVertexLayout()
{
	// Create the vertex input layout. This tells DirectX the format
	// of each of the vertices we are sending to it.

	D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT , D3D11_INPUT_PER_VERTEX_DATA, 0 }
	};
	ThrowIfFailed(_device->CreateInputLayout(vertexDesc, ARRAYSIZE(vertexDesc), _vertexShaderByteCode->GetBufferPointer(), _vertexShaderByteCode->GetBufferSize(), _layout.GetAddressOf()));
}

void TerrainNode::BuildConstantBuffer()
{
	D3D11_BUFFER_DESC bufferDesc;
	ZeroMemory(&bufferDesc, sizeof(bufferDesc));
	bufferDesc.Usage = D3D11_USAGE_DEFAULT;
	bufferDesc.ByteWidth = sizeof(TCBUFFER);
	bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	ThrowIfFailed(_device->CreateBuffer(&bufferDesc, NULL, _constantBuffer.GetAddressOf()));
}

void TerrainNode::BuildRendererStates()
{
	// Set default and wireframe rasteriser states
	D3D11_RASTERIZER_DESC rasteriserDesc;
	rasteriserDesc.FillMode = D3D11_FILL_SOLID;
	rasteriserDesc.CullMode = D3D11_CULL_BACK;
	rasteriserDesc.FrontCounterClockwise = false;
	rasteriserDesc.DepthBias = 0;
	rasteriserDesc.SlopeScaledDepthBias = 0.0f;
	rasteriserDesc.DepthBiasClamp = 0.0f;
	rasteriserDesc.DepthClipEnable = true;
	rasteriserDesc.ScissorEnable = false;
	rasteriserDesc.MultisampleEnable = false;
	rasteriserDesc.AntialiasedLineEnable = false;
	ThrowIfFailed(_device->CreateRasterizerState(&rasteriserDesc, _defaultRasteriserState.GetAddressOf()));
	rasteriserDesc.FillMode = D3D11_FILL_WIREFRAME;
	ThrowIfFailed(_device->CreateRasterizerState(&rasteriserDesc, _wireframeRasteriserState.GetAddressOf()));
}

void TerrainNode::LoadTerrainTextures()
{
	// Change the paths below as appropriate for your use
	wstring terrainTextureNames[5] = { L"Textures\\grass.dds", L"Textures\\darkdirt.dds", L"Textures\\stone.dds", L"Textures\\lightdirt.dds", L"Textures\\snow.dds" };

	// Load the textures from the files
	ComPtr<ID3D11Resource> terrainTextures[5];
	for (int i = 0; i < 5; i++)
	{
		ThrowIfFailed(CreateDDSTextureFromFileEx(_device.Get(),
			_deviceContext.Get(),
			terrainTextureNames[i].c_str(),
			0,
			D3D11_USAGE_IMMUTABLE,
			D3D11_BIND_SHADER_RESOURCE,
			0,
			0,
			false,
			terrainTextures[i].GetAddressOf(),
			nullptr
		));
	}
	// Now create the Texture2D arrary.  We assume all textures in the
	// array have the same format and dimensions

	D3D11_TEXTURE2D_DESC textureDescription;
	ComPtr<ID3D11Texture2D> textureInterface;
	terrainTextures[0].As<ID3D11Texture2D>(&textureInterface);
	textureInterface->GetDesc(&textureDescription);

	D3D11_TEXTURE2D_DESC textureArrayDescription;
	textureArrayDescription.Width = textureDescription.Width;
	textureArrayDescription.Height = textureDescription.Height;
	textureArrayDescription.MipLevels = textureDescription.MipLevels;
	textureArrayDescription.ArraySize = 5;
	textureArrayDescription.Format = textureDescription.Format;
	textureArrayDescription.SampleDesc.Count = 1;
	textureArrayDescription.SampleDesc.Quality = 0;
	textureArrayDescription.Usage = D3D11_USAGE_DEFAULT;
	textureArrayDescription.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	textureArrayDescription.CPUAccessFlags = 0;
	textureArrayDescription.MiscFlags = 0;

	ComPtr<ID3D11Texture2D> textureArray = 0;
	ThrowIfFailed(_device->CreateTexture2D(&textureArrayDescription, 0, textureArray.GetAddressOf()));

	// Copy individual texture elements into texture array.

	for (UINT i = 0; i < 5; i++)
	{
		// For each mipmap level...
		for (UINT mipLevel = 0; mipLevel < textureDescription.MipLevels; mipLevel++)
		{
			_deviceContext->CopySubresourceRegion(textureArray.Get(),
				D3D11CalcSubresource(mipLevel, i, textureDescription.MipLevels),
				NULL,
				NULL,
				NULL,
				terrainTextures[i].Get(),
				mipLevel,
				nullptr
			);
		}
	}

	// Create a resource view to the texture array.
	D3D11_SHADER_RESOURCE_VIEW_DESC viewDescription;
	viewDescription.Format = textureArrayDescription.Format;
	viewDescription.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
	viewDescription.Texture2DArray.MostDetailedMip = 0;
	viewDescription.Texture2DArray.MipLevels = textureArrayDescription.MipLevels;
	viewDescription.Texture2DArray.FirstArraySlice = 0;
	viewDescription.Texture2DArray.ArraySize = 5;

	ThrowIfFailed(_device->CreateShaderResourceView(textureArray.Get(), &viewDescription, _texturesResourceView.GetAddressOf()));

	// Cheeky load in the snow hack to solve that weird issue
	wstring snowTestName = L"Textures\\snow.dds";
	ThrowIfFailed(CreateDDSTextureFromFile(_device.Get(),
		snowTestName.c_str(),
		nullptr,
		_snowTest.GetAddressOf()
	));
}

void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<TerrainPopNode>& nodesForPop, int xStep, int zStep, float heightLower, float heightUpper, float slopeLower, float slopeUpper)
{
	for (int z = 0; z < _gridRows; z += zStep)
	{
		for (int x = 0; x < _gridCols; x += xStep)
		{
			int currentIndex = ((z * _gridCols) + x) * 4;

			float totalHeights = 0.0f;
			float totalSlope = 0.0f;
			for (int si = 0; si < 4; si++)
			{
				totalHeights += _terrainVerts[currentIndex + si].Position.y;
				totalSlope += _terrainVerts[currentIndex + si].Normal.y;
			}

			float heightValue = totalHeights / 4.0f;

			if (heightValue >= heightLower && heightValue <= heightUpper)
			{
				float avgSlope = totalSlope / 4.0f;

				if (avgSlope >= slopeLower && avgSlope <= slopeUpper)
				{
					int nodeIndex = 0;
					if (nodesForPop.size() > 1)
					{
						nodeIndex = rand() % nodesForPop.size();
					}

					float scale = (rand() % 100 * 0.01f) * nodesForPop[nodeIndex].scaleFactor;
					float yRotation = rand() % 360;
					
					float xPos = x * _cellSizeX + _terrainStartX;
					float yPos = heightValue;
					float zPos = (-z + 1) * _cellSizeZ + _terrainStartZ;

					wstring nodeName = L"_" + to_wstring(z) + L"_" + to_wstring(x);
					shared_ptr<MeshNode> newNode = make_shared<MeshNode>(nodesForPop[nodeIndex].nodeBaseName + nodeName, nodesForPop[nodeIndex].modelName);
					newNode->SetWorldTransform(XMMatrixRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), yRotation * XM_PI / 180.0f) * XMMatrixScaling(scale, scale, scale) * XMMatrixTranslation(xPos, yPos, zPos));
					currentSceneGraph->Add(newNode);
				}
			}
		}
	}
}

void TerrainNode::GenerateBlendMap()
{
	RGBA snowTops = RGBA{ 0u, 0u, 0u, 255u };
	RGBA grassLand = RGBA{ 0u, 0u, 0u, 0u };
	RGBA lightDirt = RGBA{ 50u, 0u, 255u, 0u };
	RGBA darkDirt = RGBA{ 255u, 50u, 50u, 0u };

	vector<RGBA> colorSteps = {darkDirt, lightDirt, grassLand, grassLand, grassLand, snowTops};

	float waterOffset = _waterHeight - 50.0f;
	if (waterOffset < 0.0f)
	{
		waterOffset = 0.0f;
	}

	ColorGradient terrainBlendGradient = ColorGradient(waterOffset, 820.0f, colorSteps);

	// Note that _numberOfRows and _numberOfColumns need to be setup
	// to the number of rows and columns in your grid in order for this
	// to work.
	DWORD* blendMap = new DWORD[_gridCols * _gridRows];
	DWORD* blendMapPtr = blendMap;
	BYTE r;
	BYTE g;
	BYTE b;
	BYTE a;

	DWORD index = 0;
	for (DWORD i = 0; i < _gridRows; i++)
	{
		for (DWORD j = 0; j < _gridCols; j++)
		{
			r = 0u;
			g = 0u;
			b = 0u;
			a = 0u;
			// Calculate the appropriate blend colour for the 
			// current location in the blend map.  This has been
			// left as an exercise for you.  You need to calculate
			// appropriate values for the r, g, b and a values (each
			// between 0 and 255). The code below combines these
			// into a DWORD (32-bit value) and stores it in the blend map.

			int currentIndex = ((i * _gridCols) + j) * 4;

			float totalHeights = 0.0f;
			float totalSlope = 0.0f;

			for (int si = 0; si < 4; si++)
			{
				totalHeights += _terrainVerts[currentIndex + si].Position.y;				
				totalSlope += _terrainVerts[currentIndex + si].Normal.y;
			}
			
			float avgHeight = totalHeights / 4.0f;
			float avgSlope = totalSlope / 4.0f;

			RGBA currentBlend = terrainBlendGradient.GetRGBAValue(avgHeight);
			r = (BYTE)currentBlend.red;
			g = (BYTE)currentBlend.green;
			b = (BYTE)currentBlend.blue;
			a = (BYTE)currentBlend.alpha;			
			// Override the G channel if we are cliff height and slope angle
			if (avgHeight > 500.0f)
			{
				if (avgSlope < 0.6f)
				{
					g = (BYTE)SharedMethods::Clamp<int>(avgHeight - 450.0f, 0, 150);
				}
			}				

			DWORD mapValue = (a << 24) + (b << 16) + (g << 8) + r;
			*blendMapPtr++ = mapValue;
		}
	}
	D3D11_TEXTURE2D_DESC blendMapDescription;
	blendMapDescription.Width = _gridCols;
	blendMapDescription.Height = _gridRows;
	blendMapDescription.MipLevels = 1;
	blendMapDescription.ArraySize = 1;
	blendMapDescription.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	blendMapDescription.SampleDesc.Count = 1;
	blendMapDescription.SampleDesc.Quality = 0;
	blendMapDescription.Usage = D3D11_USAGE_DEFAULT;
	blendMapDescription.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	blendMapDescription.CPUAccessFlags = 0;
	blendMapDescription.MiscFlags = 0;

	D3D11_SUBRESOURCE_DATA blendMapInitialisationData;
	blendMapInitialisationData.pSysMem = blendMap;
	blendMapInitialisationData.SysMemPitch = 4 * _gridCols;

	ComPtr<ID3D11Texture2D> blendMapTexture;
	ThrowIfFailed(_device->CreateTexture2D(&blendMapDescription, &blendMapInitialisationData, blendMapTexture.GetAddressOf()));

	// Create a resource view to the texture array.
	D3D11_SHADER_RESOURCE_VIEW_DESC viewDescription;
	viewDescription.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	viewDescription.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	viewDescription.Texture2D.MostDetailedMip = 0;
	viewDescription.Texture2D.MipLevels = 1;

	ThrowIfFailed(_device->CreateShaderResourceView(blendMapTexture.Get(), &viewDescription, _blendMapResourceView.GetAddressOf()));
	delete[] blendMap;
}

void TerrainNode::BuildExtraMaps()
{
	// Note that in order to use CreateWICTextureFromFile, we 
	// need to ensure we make a call to CoInitializeEx in our 
	// Initialise method (and make the corresponding call to 
	// CoUninitialize in the Shutdown method).  Otherwise, 
	// the following call will throw an exception
	wstring waterNormalMapName = L"Textures\\waterNormals.bmp";
	ThrowIfFailed(CreateWICTextureFromFile(_device.Get(),
		_deviceContext.Get(),
		waterNormalMapName.c_str(),
		nullptr,
		_waterNormalMap.GetAddressOf()
	));
}

float TerrainNode::GetHeightAtPoint(float x, float z, bool waterCollide)
{
	int cellX = (int)((x - _terrainStartX) / _cellSizeX);
	int cellZ = (int)((_terrainStartZ - z) / _cellSizeZ);

	int currentIndex = ((cellZ * _gridCols) + cellX) * 4;

	float dx = x - _terrainVerts[currentIndex].Position.x;
	float dz = z - _terrainVerts[currentIndex].Position.z;

	int currentNormalIndex = ((cellZ * _gridCols) + cellX) * 6;
	if (dx < dz)
	{
		currentNormalIndex += 3;
	}
	
	XMVECTOR v1 = XMVectorSet(_terrainVerts[_indices[currentNormalIndex + 1]].Position.x - _terrainVerts[_indices[currentNormalIndex]].Position.x,
		_terrainVerts[_indices[currentNormalIndex + 1]].Position.y - _terrainVerts[_indices[currentNormalIndex]].Position.y,
		_terrainVerts[_indices[currentNormalIndex + 1]].Position.z - _terrainVerts[_indices[currentNormalIndex]].Position.z,
		0.0f);
	XMVECTOR v2 = XMVectorSet(_terrainVerts[_indices[currentNormalIndex + 2]].Position.x - _terrainVerts[_indices[currentNormalIndex]].Position.x,
		_terrainVerts[_indices[currentNormalIndex + 2]].Position.y - _terrainVerts[_indices[currentNormalIndex]].Position.y,
		_terrainVerts[_indices[currentNormalIndex + 2]].Position.z - _terrainVerts[_indices[currentNormalIndex]].Position.z,
		0.0f);
		
	XMFLOAT4 normal;
	XMStoreFloat4(&normal, XMVector3Normalize(XMVector3Cross(v1, v2)));
	
	float result = _terrainVerts[currentIndex].Position.y + (normal.x * dx + normal.z * dz) / -normal.y;
	if (waterCollide)
	{
		if (result <= _waterHeight)
		{
			result = _waterHeight;
		}
	}

	return result;
}

bool TerrainNode::CheckXBoundary(float x)
{	
	if (!(x > _terrainStartX && x < _terrainEndX))
	{
		return false;
	}

	return true;
}

bool TerrainNode::CheckZBoundary(float z)
{
	if (!(z < _terrainStartZ && z > _terrainEndZ))
	{
		return false;
	}

	return true;
}