From f9cfc36643b7cc0a2b8f46455d452beb6e444e0d Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 17 Nov 2014 16:50:28 +0100 Subject: [PATCH] Added cImprovedNoise implementation. --- src/Generating/Noise3DGenerator.cpp | 37 +--- src/Generating/Noise3DGenerator.h | 6 +- src/Noise.cpp | 274 +++++++++++++++++++++++++++- src/Noise.h | 105 ++++++++++- 4 files changed, 376 insertions(+), 46 deletions(-) diff --git a/src/Generating/Noise3DGenerator.cpp b/src/Generating/Noise3DGenerator.cpp index f2af75999..654e9d01f 100644 --- a/src/Generating/Noise3DGenerator.cpp +++ b/src/Generating/Noise3DGenerator.cpp @@ -61,35 +61,6 @@ public: -/** Linearly interpolates between two values. -Assumes that a_Ratio is in range [0, 1]. */ -inline static NOISE_DATATYPE Lerp(NOISE_DATATYPE a_Val1, NOISE_DATATYPE a_Val2, NOISE_DATATYPE a_Ratio) -{ - return a_Val1 + (a_Val2 - a_Val1) * a_Ratio; -} - - - - - -/** Linearly interpolates between two values, clamping the ratio to [0, 1] first. */ -inline static NOISE_DATATYPE ClampedLerp(NOISE_DATATYPE a_Val1, NOISE_DATATYPE a_Val2, NOISE_DATATYPE a_Ratio) -{ - if (a_Ratio < 0) - { - return a_Val1; - } - if (a_Ratio > 1) - { - return a_Val2; - } - return Lerp(a_Val1, a_Val2, a_Ratio); -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cNoise3DGenerator: @@ -249,10 +220,10 @@ void cNoise3DGenerator::GenerateNoiseArray(int a_ChunkX, int a_ChunkZ, NOISE_DAT NOISE_DATATYPE NoiseW[DIM_X * DIM_Y * DIM_Z]; // Workspace that the noise calculation can use and trash // Our noise array has different layout, XZY, instead of regular chunk's XYZ, that's why the coords are "renamed" - NOISE_DATATYPE StartX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width)) / m_FrequencyX; - NOISE_DATATYPE EndX = ((NOISE_DATATYPE)((a_ChunkX + 1) * cChunkDef::Width) - 1) / m_FrequencyX; - NOISE_DATATYPE StartZ = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width)) / m_FrequencyZ; - NOISE_DATATYPE EndZ = ((NOISE_DATATYPE)((a_ChunkZ + 1) * cChunkDef::Width) - 1) / m_FrequencyZ; + NOISE_DATATYPE StartX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE EndX = ((NOISE_DATATYPE)((a_ChunkX + 1) * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE StartZ = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width)) / m_FrequencyZ; + NOISE_DATATYPE EndZ = ((NOISE_DATATYPE)((a_ChunkZ + 1) * cChunkDef::Width)) / m_FrequencyZ; NOISE_DATATYPE StartY = 0; NOISE_DATATYPE EndY = ((NOISE_DATATYPE)256) / m_FrequencyY; diff --git a/src/Generating/Noise3DGenerator.h b/src/Generating/Noise3DGenerator.h index ba541fbcc..92cfc32b7 100644 --- a/src/Generating/Noise3DGenerator.h +++ b/src/Generating/Noise3DGenerator.h @@ -34,9 +34,9 @@ public: protected: // Linear interpolation step sizes, must be divisors of cChunkDef::Width and cChunkDef::Height, respectively: - static const int UPSCALE_X = 8; - static const int UPSCALE_Y = 4; - static const int UPSCALE_Z = 8; + static const int UPSCALE_X = 4; + static const int UPSCALE_Y = 8; + static const int UPSCALE_Z = 4; // Linear interpolation buffer dimensions, calculated from the step sizes: static const int DIM_X = 1 + cChunkDef::Width / UPSCALE_X; diff --git a/src/Noise.cpp b/src/Noise.cpp index 8fcfe2920..56d39395d 100644 --- a/src/Noise.cpp +++ b/src/Noise.cpp @@ -2,6 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Noise.h" +#include "OSSupport\Timer.h" #define FAST_FLOOR(x) (((x) < 0) ? (((int)x) - 1) : ((int)x)) @@ -9,10 +10,110 @@ +#if 0 +/** cImprovedPerlin noise test suite: +- Generate a rather large 2D and 3D noise array and output it to a file +- Compare performance of cCubicNoise and cImprovedNoise, both in single-value and 3D-array usages */ +static class cImprovedPerlinNoiseTest +{ +public: + cImprovedPerlinNoiseTest(void) + { + printf("Performing Improved Perlin Noise tests...\n"); + TestImage(); + TestSpeed(); + TestSpeedArr(); + printf("Improved Perlin Noise tests complete.\n"); + } + + + /** Tests the noise by generating 2D and 3D images and dumping them to files. */ + void TestImage(void) + { + static const int SIZE_X = 256; + static const int SIZE_Y = 256; + static const int SIZE_Z = 16; + + cImprovedNoise noise(1); + std::unique_ptr arr(new NOISE_DATATYPE[SIZE_X * SIZE_Y * SIZE_Z]); + noise.Generate3D(arr.get(), SIZE_X, SIZE_Y, SIZE_Z, 0, 14, 0, 14, 0, 14); + Debug3DNoise(arr.get(), SIZE_X, SIZE_Y, SIZE_Z, "ImprovedPerlinNoiseTest3D", 128); + noise.Generate2D(arr.get(), SIZE_X, SIZE_Y, 0, 14, 15, 28); + Debug2DNoise(arr.get(), SIZE_X, SIZE_Y, "ImprovedPerlinNoiseTest2D", 128); + } + + + /** Tests the speeds of cImprovedPerlin and cCubicNoise when generating individual values. */ + void TestSpeed(void) + { + cImprovedNoise improvedNoise(1); + cNoise noise(1); + cTimer timer; + + // Measure the improvedNoise: + NOISE_DATATYPE sum = 0; + long long start = timer.GetNowTime(); + for (int i = 0; i < 100000000; i++) + { + sum += improvedNoise.GetValueAt(i, 0, -i); + } + long long finish = timer.GetNowTime(); + printf("cImprovedNoise took %.2f seconds; total is %f.\n", static_cast(finish - start) / 1000.0f, sum); + + // Measure the cubicNoise: + sum = 0; + start = timer.GetNowTime(); + for (int i = 0; i < 100000000; i++) + { + sum += noise.IntNoise3D(i, 0, -i); + } + finish = timer.GetNowTime(); + printf("cCubicNoise took %.2f seconds; total is %f.\n", static_cast(finish - start) / 1000.0f, sum); + } + + + /** Tests the speeds of cImprovedPerlin and cCubicNoise when generating arrays. */ + void TestSpeedArr(void) + { + static const int SIZE_X = 256; + static const int SIZE_Y = 256; + static const int SIZE_Z = 16; + + std::unique_ptr arr(new NOISE_DATATYPE[SIZE_X * SIZE_Y * SIZE_Z]); + cTimer timer; + cImprovedNoise improvedNoise(1); + cCubicNoise cubicNoise(1); + + // Measure the improvedNoise: + long long start = timer.GetNowTime(); + for (int i = 0; i < 40; i++) + { + improvedNoise.Generate3D(arr.get(), SIZE_X, SIZE_Y, SIZE_Z, 0, 14, 0, 14, 0, 14); + } + long long finish = timer.GetNowTime(); + printf("cImprovedNoise(arr) took %.2f seconds.\n", static_cast(finish - start) / 1000.0f); + + // Measure the cubicNoise: + start = timer.GetNowTime(); + for (int i = 0; i < 40; i++) + { + cubicNoise.Generate3D(arr.get(), SIZE_X, SIZE_Y, SIZE_Z, 0, 14, 0, 14, 0, 14); + } + finish = timer.GetNowTime(); + printf("cCubicNoise(arr) took %.2f seconds.\n", static_cast(finish - start) / 1000.0f); + } +} g_Test; + +#endif + + + + + //////////////////////////////////////////////////////////////////////////////// // Globals: -void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int a_SizeZ, const AString & a_FileNameBase) +void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int a_SizeZ, const AString & a_FileNameBase, NOISE_DATATYPE a_Coeff) { const int BUF_SIZE = 512; ASSERT(a_SizeX <= BUF_SIZE); // Just stretch it, if needed @@ -29,7 +130,7 @@ void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int unsigned char buf[BUF_SIZE]; for (int x = 0; x < a_SizeX; x++) { - buf[x] = (unsigned char)(std::min(255, std::max(0, (int)(128 + 32 * a_Noise[idx++])))); + buf[x] = static_cast(Clamp((int)(128 + a_Coeff * a_Noise[idx++]), 0, 255)); } f1.Write(buf, a_SizeX); } // for y @@ -50,7 +151,7 @@ void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int unsigned char buf[BUF_SIZE]; for (int x = 0; x < a_SizeX; x++) { - buf[x] = (unsigned char)(std::min(255, std::max(0, (int)(128 + 32 * a_Noise[idx++])))); + buf[x] = static_cast(Clamp((int)(128 + a_Coeff * a_Noise[idx++]), 0, 255)); } f2.Write(buf, a_SizeX); } // for z @@ -65,7 +166,7 @@ void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int -void Debug2DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, const AString & a_FileNameBase) +void Debug2DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, const AString & a_FileNameBase, NOISE_DATATYPE a_Coeff) { const int BUF_SIZE = 512; ASSERT(a_SizeX <= BUF_SIZE); // Just stretch it, if needed @@ -79,7 +180,7 @@ void Debug2DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, cons unsigned char buf[BUF_SIZE]; for (int x = 0; x < a_SizeX; x++) { - buf[x] = (unsigned char)(std::min(255, std::max(0, (int)(128 + 32 * a_Noise[idx++])))); + buf[x] = static_cast(Clamp((int)(128 + a_Coeff * a_Noise[idx++]), 0, 255)); } f1.Write(buf, a_SizeX); } // for y @@ -791,6 +892,169 @@ void cCubicNoise::CalcFloorFrac( +//////////////////////////////////////////////////////////////////////////////// +// cImprovedNoise: + +cImprovedNoise::cImprovedNoise(int a_Seed) +{ + // Initialize the permutations with identity: + for (int i = 0; i < 256; i++) + { + m_Perm[i] = i; + } + + // Randomize the permutation table - swap each element with a random other element: + cNoise noise(a_Seed); + for (int i = 0; i < 256; i++) + { + int rnd = (noise.IntNoise1DInt(i) / 7) % 256; + std::swap(m_Perm[i], m_Perm[rnd]); + } + + // Copy the lower 256 entries into upper 256 entries: + for (int i = 0; i < 256; i++) + { + m_Perm[i + 256] = m_Perm[i]; + } +} + + + + + +void cImprovedNoise::Generate2D( + NOISE_DATATYPE * a_Array, + int a_SizeX, int a_SizeY, + NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, + NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY +) const +{ + size_t idx = 0; + for (int y = 0; y < a_SizeY; y++) + { + NOISE_DATATYPE ratioY = static_cast(y) / (a_SizeY - 1); + NOISE_DATATYPE noiseY = Lerp(a_StartY, a_EndY, ratioY); + int noiseYInt = FAST_FLOOR(noiseY); + int yCoord = noiseYInt & 255; + NOISE_DATATYPE noiseYFrac = noiseY - noiseYInt; + NOISE_DATATYPE fadeY = Fade(noiseYFrac); + for (int x = 0; x < a_SizeX; x++) + { + NOISE_DATATYPE ratioX = static_cast(x) / (a_SizeX - 1); + NOISE_DATATYPE noiseX = Lerp(a_StartX, a_EndX, ratioX); + int noiseXInt = FAST_FLOOR(noiseX); + int xCoord = noiseXInt & 255; + NOISE_DATATYPE noiseXFrac = noiseX - noiseXInt; + NOISE_DATATYPE fadeX = Fade(noiseXFrac); + + // Hash the coordinates: + int A = m_Perm[xCoord] + yCoord; + int AA = m_Perm[A]; + int AB = m_Perm[A + 1]; + int B = m_Perm[xCoord + 1] + yCoord; + int BA = m_Perm[B]; + int BB = m_Perm[B + 1]; + + // Lerp the gradients: + a_Array[idx++] = Lerp( + Lerp(Grad(m_Perm[AA], noiseXFrac, noiseYFrac, 0), Grad(m_Perm[BA], noiseXFrac - 1, noiseYFrac, 0), fadeX), + Lerp(Grad(m_Perm[AB], noiseXFrac, noiseYFrac - 1, 0), Grad(m_Perm[BB], noiseXFrac - 1, noiseYFrac - 1, 0), fadeX), + fadeY + ); + } // for x + } // for y +} + + + + + +void cImprovedNoise::Generate3D( + NOISE_DATATYPE * a_Array, + int a_SizeX, int a_SizeY, int a_SizeZ, + NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, + NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, + NOISE_DATATYPE a_StartZ, NOISE_DATATYPE a_EndZ +) const +{ + size_t idx = 0; + for (int z = 0; z < a_SizeZ; z++) + { + NOISE_DATATYPE ratioZ = static_cast(z) / (a_SizeZ - 1); + NOISE_DATATYPE noiseZ = Lerp(a_StartZ, a_EndZ, ratioZ); + int noiseZInt = FAST_FLOOR(noiseZ); + int zCoord = noiseZInt & 255; + NOISE_DATATYPE noiseZFrac = noiseZ - noiseZInt; + NOISE_DATATYPE fadeZ = Fade(noiseZFrac); + for (int y = 0; y < a_SizeY; y++) + { + NOISE_DATATYPE ratioY = static_cast(y) / (a_SizeY - 1); + NOISE_DATATYPE noiseY = Lerp(a_StartY, a_EndY, ratioY); + int noiseYInt = FAST_FLOOR(noiseY); + int yCoord = noiseYInt & 255; + NOISE_DATATYPE noiseYFrac = noiseY - noiseYInt; + NOISE_DATATYPE fadeY = Fade(noiseYFrac); + for (int x = 0; x < a_SizeX; x++) + { + NOISE_DATATYPE ratioX = static_cast(x) / (a_SizeX - 1); + NOISE_DATATYPE noiseX = Lerp(a_StartX, a_EndX, ratioX); + int noiseXInt = FAST_FLOOR(noiseX); + int xCoord = noiseXInt & 255; + NOISE_DATATYPE noiseXFrac = noiseX - noiseXInt; + NOISE_DATATYPE fadeX = Fade(noiseXFrac); + + // Hash the coordinates: + int A = m_Perm[xCoord] + yCoord; + int AA = m_Perm[A] + zCoord; + int AB = m_Perm[A + 1] + zCoord; + int B = m_Perm[xCoord + 1] + yCoord; + int BA = m_Perm[B] + zCoord; + int BB = m_Perm[B + 1] + zCoord; + + // Lerp the gradients: + // TODO: This may be optimized by swapping the coords and recalculating most lerps only "once every x" + a_Array[idx++] = Lerp( + Lerp( + Lerp(Grad(m_Perm[AA], noiseXFrac, noiseYFrac, noiseZFrac), Grad(m_Perm[BA], noiseXFrac - 1, noiseYFrac, noiseZFrac), fadeX), + Lerp(Grad(m_Perm[AB], noiseXFrac, noiseYFrac - 1, noiseZFrac), Grad(m_Perm[BB], noiseXFrac - 1, noiseYFrac - 1, noiseZFrac), fadeX), + fadeY + ), + Lerp( + Lerp(Grad(m_Perm[AA + 1], noiseXFrac, noiseYFrac, noiseZFrac - 1), Grad(m_Perm[BA + 1], noiseXFrac - 1, noiseYFrac, noiseZFrac - 1), fadeX), + Lerp(Grad(m_Perm[AB + 1], noiseXFrac, noiseYFrac - 1, noiseZFrac - 1), Grad(m_Perm[BB + 1], noiseXFrac - 1, noiseYFrac - 1, noiseZFrac - 1), fadeX), + fadeY + ), + fadeZ + ); + } // for x + } // for y + } // for z +} + + + + + +NOISE_DATATYPE cImprovedNoise::GetValueAt(int a_X, int a_Y, int a_Z) +{ + // Hash the coordinates: + a_X = a_X & 255; + a_Y = a_Y & 255; + a_Z = a_Z & 255; + int A = m_Perm[a_X] + a_Y; + int AA = m_Perm[A] + a_Z; + int AB = m_Perm[A + 1] + a_Z; + int B = m_Perm[a_X + 1] + a_Y; + int BA = m_Perm[B] + a_Z; + int BB = m_Perm[B + 1] + a_Z; + + return Grad(m_Perm[AA], 0, 0, 0); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cPerlinNoise: diff --git a/src/Noise.h b/src/Noise.h index b7a90d5b7..3c5caded7 100644 --- a/src/Noise.h +++ b/src/Noise.h @@ -140,6 +140,63 @@ protected: +/** Improved noise, as described by Ken Perlin: http://mrl.nyu.edu/~perlin/paper445.pdf +Implementation adapted from Perlin's Java implementation: http://mrl.nyu.edu/~perlin/noise/ */ +class cImprovedNoise +{ +public: + /** Constructs a new instance of the noise obbject. + Note that this operation is quite expensive (the permutation array being constructed). */ + cImprovedNoise(int a_Seed); + + + /** Fills a 2D array with the values of the noise. */ + void Generate2D( + NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y] + int a_SizeX, int a_SizeY, ///< Count of the array, in each direction + NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction + NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY ///< Noise-space coords of the array in the Y direction + ) const; + + + /** Fills a 3D array with the values of the noise. */ + void Generate3D( + NOISE_DATATYPE * a_Array, ///< Array to generate into [x + a_SizeX * y + a_SizeX * a_SizeY * z] + int a_SizeX, int a_SizeY, int a_SizeZ, ///< Count of the array, in each direction + NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction + NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction + NOISE_DATATYPE a_StartZ, NOISE_DATATYPE a_EndZ ///< Noise-space coords of the array in the Z direction + ) const; + + /** Returns the value at the specified integral coords. Used for raw speed measurement. */ + NOISE_DATATYPE GetValueAt(int a_X, int a_Y, int a_Z); + +protected: + + /** The permutation table used by the noise function. Initialized using seed. */ + int m_Perm[512]; + + + /** Calculates the fade curve, 6 * t^5 - 15 * t^4 + 10 * t^3. */ + inline static NOISE_DATATYPE Fade(NOISE_DATATYPE a_T) + { + return a_T * a_T * a_T * (a_T * (a_T * 6 - 15) + 10); + } + + /** Returns the gradient value based on the hash. */ + inline static NOISE_DATATYPE Grad(int a_Hash, NOISE_DATATYPE a_X, NOISE_DATATYPE a_Y, NOISE_DATATYPE a_Z) + { + int hash = a_Hash % 16; + NOISE_DATATYPE u = (hash < 8) ? a_X : a_Y; + NOISE_DATATYPE v = (hash < 4) ? a_Y : (((hash == 12) || (hash == 14)) ? a_X : a_Z); + return (((hash & 1) == 0) ? u : -u) + (((hash & 2) == 0) ? v : -v); + } +}; + + + + + class cPerlinNoise { public: @@ -155,7 +212,7 @@ public: NOISE_DATATYPE * a_Array, ///< Array to generate into int a_SizeX, ///< Count of the array NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array - NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash + NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash ) const; @@ -164,7 +221,7 @@ public: int a_SizeX, int a_SizeY, ///< Count of the array, in each direction NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction - NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash + NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash ) const; @@ -174,7 +231,7 @@ public: NOISE_DATATYPE a_StartX, NOISE_DATATYPE a_EndX, ///< Noise-space coords of the array in the X direction NOISE_DATATYPE a_StartY, NOISE_DATATYPE a_EndY, ///< Noise-space coords of the array in the Y direction NOISE_DATATYPE a_StartZ, NOISE_DATATYPE a_EndZ, ///< Noise-space coords of the array in the Z direction - NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash + NOISE_DATATYPE * a_Workspace = nullptr ///< Workspace that this function can use and trash ) const; protected: @@ -376,8 +433,46 @@ NOISE_DATATYPE cNoise::LinearInterpolate(NOISE_DATATYPE a_A, NOISE_DATATYPE a_B, //////////////////////////////////////////////////////////////////////////////// // Global functions: -extern void Debug2DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, const AString & a_FileNameBase); -extern void Debug3DNoise(const NOISE_DATATYPE * a_Noise, int a_SizeX, int a_SizeY, int a_SizeZ, const AString & a_FileNameBase); +/** Exports the noise array into a file. +a_Coeff specifies the value that each array value is multiplied by before being converted into a byte. */ +extern void Debug2DNoise(const NOISE_DATATYPE * a_Array, int a_SizeX, int a_SizeY, const AString & a_FileNameBase, NOISE_DATATYPE a_Coeff = 32); + +/** Exports the noise array into a set of files, ordered by XY and XZ. +a_Coeff specifies the value that each array value is multiplied by before being converted into a byte. */ +extern void Debug3DNoise(const NOISE_DATATYPE * a_Array, int a_SizeX, int a_SizeY, int a_SizeZ, const AString & a_FileNameBase, NOISE_DATATYPE a_Coeff = 32); + + + + +/** Linearly interpolates between two values. +Assumes that a_Ratio is in range [0, 1]. */ +inline NOISE_DATATYPE Lerp(NOISE_DATATYPE a_Val1, NOISE_DATATYPE a_Val2, NOISE_DATATYPE a_Ratio) +{ + return a_Val1 + (a_Val2 - a_Val1) * a_Ratio; +} + + + + + +/** Linearly interpolates between two values, clamping the ratio to [0, 1] first. */ +inline NOISE_DATATYPE ClampedLerp(NOISE_DATATYPE a_Val1, NOISE_DATATYPE a_Val2, NOISE_DATATYPE a_Ratio) +{ + if (a_Ratio < 0) + { + return a_Val1; + } + if (a_Ratio > 1) + { + return a_Val2; + } + return Lerp(a_Val1, a_Val2, a_Ratio); +} + + + + +