Files
directx-plane-game/Graphics2/PerlinTerrainNode.cpp
iDunnoDev 616f68bf8b Added Comments
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
2022-06-13 16:41:25 +01:00

212 lines
5.9 KiB
C++

#include "PerlinTerrainNode.h"
PerlinTerrainNode::PerlinTerrainNode(wstring name, wstring seed, float chunkSize, int widthX, int widthZ, float waterHeight, int cellSizeX, int cellSizeZ) : TerrainNode(name, seed, waterHeight, widthX, widthZ, cellSizeX, cellSizeZ)
{
// Set the chink size
_chunkSize = chunkSize;
// Check if a cache image exists and load it if so
if (!ReadCache())
{
// No Cache image existed so generate the perlin noise map
GeneratePerlinValues();
if (!GeneratePerlinHeights())
{
_initError = true;
}
else
{
// The generation was successful, save it to the cache
CacheOutput();
}
}
}
void PerlinTerrainNode::GeneratePerlinValues()
{
// Setup the RNG
std::seed_seq seedGenerator(_seedString.begin(), _seedString.end());
std::mt19937 generator;
generator.seed(seedGenerator);
std::uniform_int_distribution<> dist(0, 511);
char bufferChar;
int newPosition;
// Clear the permutation array so we can reinsert a new copy
_p.clear();
copy(_staticP.begin(), _staticP.end(), back_inserter(_p));
// Loop through and alternate the numbers so they are in a random position, this will not create any new numbers so we should always have what we started with
// just in a different order
for (int i = 0; i < 512; i++)
{
newPosition = dist(generator);
bufferChar = _p[i];
_p[i] = _p[newPosition];
_p[newPosition] = bufferChar;
}
}
bool PerlinTerrainNode::ReadCache()
{
ifstream cachedImage;
string stringSeed = string(_seedString.begin(), _seedString.end());
string chunkSizeString = to_string(_chunkSize);
replace(chunkSizeString.begin(), chunkSizeString.end(), '.', '_');
cachedImage.open("TerrainCache\\cache_" + stringSeed + "_" + chunkSizeString + ".raw", std::ios_base::binary);
// Couldnt open the cache, either doesnt exist or some issue opening the file, a new one should be generated
if (!cachedImage)
{
return false;
}
// Load the files bytes into a vector we can foreach loop
vector<char> fileBytes((istreambuf_iterator<char>(cachedImage)), (istreambuf_iterator<char>()));
for (BYTE currentByte : fileBytes)
{
// Change the number back into a float value, some precision is lost because of this but i cant notice it really
float heightValue = currentByte / 255.0f;
_heightValues.push_back(heightValue);
}
cachedImage.close();
return true;
}
void PerlinTerrainNode::CacheOutput()
{
// Create a unique string name for the seed and cluster size given
string stringSeed = string(_seedString.begin(), _seedString.end());
string chunkSizeString = to_string(_chunkSize);
replace(chunkSizeString.begin(), chunkSizeString.end(), '.', '_');
ofstream output("TerrainCache\\cache_" + stringSeed + "_" + chunkSizeString + ".raw", std::ios_base::binary);
// Loop through the height values array
int current = 0;
for (float value : _heightValues)
{
// Force the value to be within 255, we lose some precision from this conversion but to generate an image it has to be this way
BYTE currentByte = (int)(255 * value) & 0xff;
// Write the value to the file buffer
output << currentByte;
// Constantly flush the buffer so that we dont have it waiting in memory for too long
if (current >= 100)
{
output.flush();
current = 0;
}
else
{
current++;
}
}
// Close the file
output.close();
}
// Method for filling the height values array for the terrain generation
bool PerlinTerrainNode::GeneratePerlinHeights()
{
for (int z = 0; z < (int)_gridRows; z++)
{
float mapZ = ((float)z / (float)_gridRows);
for (int x = 0; x < (int)_gridCols; x++)
{
float mapX = ((float)x / (float)_gridCols);
//float currentHeightValue = GetPerlinValueAt(mapX, mapZ, 8, 0.85f) - 0.25f;
float currentHeightValue = 0.0f;
// Octaves adds "noise" to the final outcome, 12 is really the max before it tanks the system for little effect
int octaves = 12;
float currentOctaveValue = 1.0f;
float stepOff = 1.0f;
float stepTotal = 0.0f;
// n covers the persistance, frequency and
for (int n = 1; n <= octaves; n++)
{
// Generate a noise at the given position
currentHeightValue += stepOff * GetNoiseValueAt(((float)x * currentOctaveValue) / ((float)_gridCols / _chunkSize), ((float)z * currentOctaveValue) / ((float)_gridRows / _chunkSize));
currentOctaveValue *= 2;
stepTotal += stepOff;
stepOff *= 0.5f;
}
//currentHeightValue = currentHeightValue / stepTotal; // *(currentHeightValue * 2.0f);
//currentHeightValue = currentHeightValue - floor(currentHeightValue);
// Normalise the result to a number between 0 and 1, since most like to generate numbers between -1 and 1, we just have to add 1 and half the value
_heightValues.push_back((currentHeightValue + 1.0f) * 0.5f);
}
}
return true;
}
float PerlinTerrainNode::Fade(float t)
{
return t * t * t * (t * (t * 6 - 15) + 10);
}
float PerlinTerrainNode::Grad(int hash, float x, float y)
{
// To be honest this is something i dont really understand, it shifts bits around to see which direction we need to multiply in
int h = hash & 7;
float u = y;
float v = x;
if (h < 4)
{
u = x;
v = y;
}
float outA = u;
if ((h & 1))
{
outA = -u;
}
float outB = 2.0f * v;
if ((h & 2))
{
outB = -2.0f * v;
}
return outA + outB;
}
// Method to get the perlin noise value at x/y
float PerlinTerrainNode::GetNoiseValueAt(float x, float y)
{
int ix0 = (int)floor(x);
int iy0 = (int)floor(y);
float fx0 = x - (float)ix0;
float fy0 = y - (float)iy0;
float fx1 = fx0 - 1.0f;
float fy1 = fy0 - 1.0f;
int ix1 = (ix0 + 1) & 0xff;
int iy1 = (iy0 + 1) & 0xff;
ix0 = ix0 & 0xff;
iy0 = iy0 & 0xff;
float t = Fade(fy0);
float s = Fade(fx0);
float nx0 = Grad(_p[ix0 + _p[iy0]], fx0, fy0);
float nx1 = Grad(_p[ix0 + _p[iy1]], fx0, fy1);
float n0 = SharedMethods::Lerp<float>(nx0, nx1, t);
nx0 = Grad(_p[ix1 + _p[iy0]], fx1, fy0);
nx1 = Grad(_p[ix1 + _p[iy1]], fx1, fy1);
float n1 = SharedMethods::Lerp<float>(nx0, nx1, t);
return 0.507f * SharedMethods::Lerp(n0, n1, s);
}