#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 fileBytes((istreambuf_iterator(cachedImage)), (istreambuf_iterator())); 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(nx0, nx1, t); nx0 = Grad(_p[ix1 + _p[iy0]], fx1, fy0); nx1 = Grad(_p[ix1 + _p[iy1]], fx1, fy1); float n1 = SharedMethods::Lerp(nx0, nx1, t); return 0.507f * SharedMethods::Lerp(n0, n1, s); }