
Added ability to hold shift and skip the terrain generation when loading Added ability for the perlin terrain to save a raw image of the terrain to use as a cache
747 lines
26 KiB
C++
747 lines
26 KiB
C++
#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<wstring>{}(_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<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()
|
|
));
|
|
}
|
|
|
|
// Method for populating the terrain with random clutter described in the structs
|
|
void TerrainNode::PopulateTerrain(SceneGraphPointer currentSceneGraph, vector<TerrainPopNode>& 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<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()
|
|
{
|
|
// 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<RGBA> 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>((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()
|
|
));
|
|
}
|
|
|
|
// 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;
|
|
} |