1
0

Added cImprovedNoise implementation.

This commit is contained in:
Mattes D 2014-11-17 16:50:28 +01:00
parent ae15c2f78e
commit f9cfc36643
4 changed files with 376 additions and 46 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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<NOISE_DATATYPE[]> 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<float>(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<float>(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<NOISE_DATATYPE[]> 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<float>(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<float>(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<unsigned char>(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<unsigned char>(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<unsigned char>(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<NOISE_DATATYPE>(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<NOISE_DATATYPE>(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<NOISE_DATATYPE>(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<NOISE_DATATYPE>(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<NOISE_DATATYPE>(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:

View File

@ -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);
}