#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) { // Get the seed string and hash it _seedString = seed; _seedHash = (unsigned int)std::hash{}(_seedString); // Set the random seed number 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 < (int)_gridRows; z++) { for (int x = 0; x < (int)_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 < (int)_gridRows; z++) { for (int x = 0; x < (int)_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)); // Process all the connected normals 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); // Check if the index is not around the edges 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 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 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 textureInterface; terrainTextures[0].As(&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 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() )); } // Method for populating the terrain with random clutter described in the structs void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector& nodesForPop, int xStep, int zStep, float heightLower, float heightUpper, float slopeLower, float slopeUpper) { // Step through the z and x with the given spacing for (int z = 0; z < (int)_gridRows; z += zStep) { for (int x = 0; x < (int)_gridCols; x += xStep) { int currentIndex = ((z * (int)_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 the height value is within the given range if (heightValue >= heightLower && heightValue <= heightUpper) { float avgSlope = totalSlope / 4.0f; // If the slope value is within the given range if (avgSlope >= slopeLower && avgSlope <= slopeUpper) { // The Height level and slope values are in range, create a mesh node with the provided details int nodeIndex = 0; if (nodesForPop.size() > 1) { nodeIndex = rand() % nodesForPop.size(); } float scale = (rand() % 100 * 0.01f) * nodesForPop[nodeIndex].scaleFactor; float yRotation = (float)(rand() % 360); float xPos = x * _cellSizeX + _terrainStartX; float yPos = heightValue; float zPos = (-z + 1) * _cellSizeZ + _terrainStartZ; // Made a unique name to stop conflicts wstring nodeName = L"_" + to_wstring(z) + L"_" + to_wstring(x); shared_ptr newNode = make_shared(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() { // Set up the color gradients for each type of texture 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 }; // Create a vector of these gradients, multiple grass because i want it to cover more area, i need to come up with a better way if i have time vector colorSteps = {darkDirt, lightDirt, grassLand, grassLand, grassLand, snowTops}; float waterOffset = _waterHeight - 50.0f; if (waterOffset < 0.0f) { waterOffset = 0.0f; } // Set up the gradient 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; // Calculate the average height and slope values 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; // Get the RGB value from the gradient at the given height value 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 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() )); } // Method for getting the height value at a given point, can also check if we are colliding with water or not 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 * 4) + (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))); // Get the normal value for the vert we landed on float result = _terrainVerts[currentIndex].Position.y + (normal.x * dx + normal.z * dz) / -normal.y; if (waterCollide) { if (result <= _waterHeight) { result = _waterHeight; } } return result; } // Method for checking if we are within the X Boundary bool TerrainNode::CheckXBoundary(float x) { if (!(x > _terrainStartX && x < _terrainEndX)) { return false; } return true; } // Method for checking if we are withing the Z Boundary bool TerrainNode::CheckZBoundary(float z) { if (!(z < _terrainStartZ && z > _terrainEndZ)) { return false; } return true; }