You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
9.3 KiB
298 lines
9.3 KiB
/*
|
|
OneLoneCoder.com - What Is Perlin Noise?
|
|
"Mountains, Clouds, Worms Landscapes?" - @Javidx9
|
|
|
|
Disclaimer
|
|
~~~~~~~~~~
|
|
I don't care what you use this for. It's intended to be educational, and perhaps
|
|
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
|
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
|
bad that happens as a result of your actions. However, if good stuff happens, I
|
|
would appreciate a shout out, or at least give the blog some publicity for me.
|
|
Cheers!
|
|
|
|
Background
|
|
~~~~~~~~~~
|
|
Noise is random - a bad thing when trying to procedurally generate content. Perlin
|
|
noise adds coherence at varying spatial scales which create natural looking noise
|
|
arrays. Perlin noise can be further processed into all sorts of assets, such as
|
|
mountains, maps, rooms, textures, data sets.
|
|
|
|
|
|
Author
|
|
~~~~~~
|
|
Twitter: @javidx9
|
|
Blog: www.onelonecoder.com
|
|
|
|
Video:
|
|
~~~~~~
|
|
https://youtu.be/6-0UaeJBumA
|
|
|
|
Last Updated: 29/10/2017
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <algorithm>
|
|
using namespace std;
|
|
|
|
#include "olcConsoleGameEngine.h"
|
|
|
|
class OneLoneCoder_PerlinNoiseDemo : public olcConsoleGameEngine
|
|
{
|
|
public:
|
|
OneLoneCoder_PerlinNoiseDemo()
|
|
{
|
|
m_sAppName = L"Perlin Noise";
|
|
}
|
|
|
|
private:
|
|
// 2D noise variables
|
|
int nOutputWidth = 256;
|
|
int nOutputHeight = 256;
|
|
float *fNoiseSeed2D = nullptr;
|
|
float *fPerlinNoise2D = nullptr;
|
|
|
|
// 1D noise variables
|
|
float *fNoiseSeed1D = nullptr;
|
|
float *fPerlinNoise1D = nullptr;
|
|
int nOutputSize = 256;
|
|
|
|
|
|
int nOctaveCount = 1;
|
|
float fScalingBias = 2.0f;
|
|
int nMode = 1;
|
|
|
|
|
|
virtual bool OnUserCreate()
|
|
{
|
|
nOutputWidth = ScreenWidth();
|
|
nOutputHeight = ScreenHeight();
|
|
|
|
fNoiseSeed2D = new float[nOutputWidth * nOutputHeight];
|
|
fPerlinNoise2D = new float[nOutputWidth * nOutputHeight];
|
|
for (int i = 0; i < nOutputWidth * nOutputHeight; i++) fNoiseSeed2D[i] = (float)rand() / (float)RAND_MAX;
|
|
|
|
nOutputSize = ScreenWidth();
|
|
fNoiseSeed1D = new float[nOutputSize];
|
|
fPerlinNoise1D = new float[nOutputSize];
|
|
for (int i = 0; i < nOutputSize; i++) fNoiseSeed1D[i] = (float)rand() / (float)RAND_MAX;
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool OnUserUpdate(float fElapsedTime)
|
|
{
|
|
Fill(0, 0, ScreenWidth(), ScreenHeight(), L' ');
|
|
|
|
if (m_keys[VK_SPACE].bReleased)
|
|
nOctaveCount++;
|
|
|
|
if (m_keys[L'1'].bReleased)
|
|
nMode = 1;
|
|
|
|
if (m_keys[L'2'].bReleased)
|
|
nMode = 2;
|
|
|
|
if (m_keys[L'3'].bReleased)
|
|
nMode = 3;
|
|
|
|
if (m_keys[L'Q'].bReleased)
|
|
fScalingBias += 0.2f;
|
|
|
|
if (m_keys[L'A'].bReleased)
|
|
fScalingBias -= 0.2f;
|
|
|
|
if (fScalingBias < 0.2f)
|
|
fScalingBias = 0.2f;
|
|
|
|
if (nOctaveCount == 9)
|
|
nOctaveCount = 1;
|
|
|
|
if (nMode == 1) // 1D Noise
|
|
{
|
|
if (m_keys[L'Z'].bReleased) // Noise Between 0 and +1
|
|
for (int i = 0; i < nOutputSize; i++) fNoiseSeed1D[i] = (float)rand() / (float)RAND_MAX;
|
|
|
|
if (m_keys[L'X'].bReleased) // Noise Between -1 and +1
|
|
for (int i = 0; i < nOutputSize; i++) fNoiseSeed1D[i] = 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f;
|
|
|
|
PerlinNoise1D(nOutputSize, fNoiseSeed1D, nOctaveCount, fScalingBias, fPerlinNoise1D);
|
|
|
|
for (int x = 0; x < nOutputSize; x++)
|
|
{
|
|
int y = -(fPerlinNoise1D[x] * (float)ScreenHeight() / 2.0f) + (float)ScreenHeight() / 2.0f;
|
|
if (y < ScreenHeight() / 2)
|
|
for (int f = y; f < ScreenHeight() / 2; f++)
|
|
Draw(x, f, PIXEL_SOLID, FG_GREEN);
|
|
else
|
|
for (int f = ScreenHeight() / 2; f <= y; f++)
|
|
Draw(x, f, PIXEL_SOLID, FG_RED);
|
|
}
|
|
}
|
|
|
|
if (nMode == 2) // 2D Noise
|
|
{
|
|
if (m_keys[L'Z'].bReleased) // Noise Between 0 and +1
|
|
for (int i = 0; i < nOutputWidth * nOutputHeight; i++) fNoiseSeed2D[i] = (float)rand() / (float)RAND_MAX;
|
|
|
|
|
|
PerlinNoise2D(nOutputWidth, nOutputHeight, fNoiseSeed2D, nOctaveCount, fScalingBias, fPerlinNoise2D);
|
|
|
|
for (int x = 0; x < nOutputWidth; x++)
|
|
{
|
|
for (int y = 0; y < nOutputHeight; y++)
|
|
{
|
|
short bg_col, fg_col;
|
|
wchar_t sym;
|
|
int pixel_bw = (int)(fPerlinNoise2D[y * nOutputWidth + x] * 12.0f);
|
|
switch (pixel_bw)
|
|
{
|
|
case 0: bg_col = BG_BLACK; fg_col = FG_BLACK; sym = PIXEL_SOLID; break;
|
|
|
|
case 1: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_QUARTER; break;
|
|
case 2: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_HALF; break;
|
|
case 3: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_THREEQUARTERS; break;
|
|
case 4: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_SOLID; break;
|
|
|
|
case 5: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_QUARTER; break;
|
|
case 6: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_HALF; break;
|
|
case 7: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_THREEQUARTERS; break;
|
|
case 8: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_SOLID; break;
|
|
|
|
case 9: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_QUARTER; break;
|
|
case 10: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_HALF; break;
|
|
case 11: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_THREEQUARTERS; break;
|
|
case 12: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_SOLID; break;
|
|
}
|
|
|
|
Draw(x, y, sym, fg_col | bg_col);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nMode == 3) // 2D Noise - colourised
|
|
{
|
|
if (m_keys[L'Z'].bReleased) // Noise Between 0 and +1
|
|
for (int i = 0; i < nOutputWidth * nOutputHeight; i++) fNoiseSeed2D[i] = (float)rand() / (float)RAND_MAX;
|
|
|
|
|
|
PerlinNoise2D(nOutputWidth, nOutputHeight, fNoiseSeed2D, nOctaveCount, fScalingBias, fPerlinNoise2D);
|
|
|
|
for (int x = 0; x < nOutputWidth; x++)
|
|
{
|
|
for (int y = 0; y < nOutputHeight; y++)
|
|
{
|
|
short bg_col, fg_col;
|
|
wchar_t sym;
|
|
int pixel_bw = (int)(fPerlinNoise2D[y * nOutputWidth + x] * 16.0f);
|
|
switch (pixel_bw)
|
|
{
|
|
case 0: bg_col = BG_DARK_BLUE; fg_col = FG_DARK_BLUE; sym = PIXEL_SOLID; break;
|
|
|
|
case 1: bg_col = BG_DARK_BLUE; fg_col = FG_BLUE; sym = PIXEL_QUARTER; break;
|
|
case 2: bg_col = BG_DARK_BLUE; fg_col = FG_BLUE; sym = PIXEL_HALF; break;
|
|
case 3: bg_col = BG_DARK_BLUE; fg_col = FG_BLUE; sym = PIXEL_THREEQUARTERS; break;
|
|
case 4: bg_col = BG_DARK_BLUE; fg_col = FG_BLUE; sym = PIXEL_SOLID; break;
|
|
|
|
case 5: bg_col = BG_BLUE; fg_col = FG_GREEN; sym = PIXEL_QUARTER; break;
|
|
case 6: bg_col = BG_BLUE; fg_col = FG_GREEN; sym = PIXEL_HALF; break;
|
|
case 7: bg_col = BG_BLUE; fg_col = FG_GREEN; sym = PIXEL_THREEQUARTERS; break;
|
|
case 8: bg_col = BG_BLUE; fg_col = FG_GREEN; sym = PIXEL_SOLID; break;
|
|
|
|
case 9: bg_col = BG_GREEN; fg_col = FG_DARK_GREY; sym = PIXEL_QUARTER; break;
|
|
case 10: bg_col = BG_GREEN; fg_col = FG_DARK_GREY; sym = PIXEL_HALF; break;
|
|
case 11: bg_col = BG_GREEN; fg_col = FG_DARK_GREY; sym = PIXEL_THREEQUARTERS; break;
|
|
case 12: bg_col = BG_GREEN; fg_col = FG_DARK_GREY; sym = PIXEL_SOLID; break;
|
|
|
|
case 13: bg_col = BG_DARK_GREY; fg_col = FG_WHITE; sym = PIXEL_QUARTER; break;
|
|
case 14: bg_col = BG_DARK_GREY; fg_col = FG_WHITE; sym = PIXEL_HALF; break;
|
|
case 15: bg_col = BG_DARK_GREY; fg_col = FG_WHITE; sym = PIXEL_THREEQUARTERS; break;
|
|
case 16: bg_col = BG_DARK_GREY; fg_col = FG_WHITE; sym = PIXEL_SOLID; break;
|
|
}
|
|
|
|
Draw(x, y, sym, fg_col | bg_col);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PerlinNoise1D(int nCount, float *fSeed, int nOctaves, float fBias, float *fOutput)
|
|
{
|
|
// Used 1D Perlin Noise
|
|
for (int x = 0; x < nCount; x++)
|
|
{
|
|
float fNoise = 0.0f;
|
|
float fScaleAcc = 0.0f;
|
|
float fScale = 1.0f;
|
|
|
|
for (int o = 0; o < nOctaves; o++)
|
|
{
|
|
int nPitch = nCount >> o;
|
|
int nSample1 = (x / nPitch) * nPitch;
|
|
int nSample2 = (nSample1 + nPitch) % nCount;
|
|
|
|
float fBlend = (float)(x - nSample1) / (float)nPitch;
|
|
|
|
float fSample = (1.0f - fBlend) * fSeed[nSample1] + fBlend * fSeed[nSample2];
|
|
|
|
fScaleAcc += fScale;
|
|
fNoise += fSample * fScale;
|
|
fScale = fScale / fBias;
|
|
}
|
|
|
|
// Scale to seed range
|
|
fOutput[x] = fNoise / fScaleAcc;
|
|
}
|
|
}
|
|
|
|
void PerlinNoise2D(int nWidth, int nHeight, float *fSeed, int nOctaves, float fBias, float *fOutput)
|
|
{
|
|
// Used 1D Perlin Noise
|
|
for (int x = 0; x < nWidth; x++)
|
|
for (int y = 0; y < nHeight; y++)
|
|
{
|
|
float fNoise = 0.0f;
|
|
float fScaleAcc = 0.0f;
|
|
float fScale = 1.0f;
|
|
|
|
for (int o = 0; o < nOctaves; o++)
|
|
{
|
|
int nPitch = nWidth >> o;
|
|
int nSampleX1 = (x / nPitch) * nPitch;
|
|
int nSampleY1 = (y / nPitch) * nPitch;
|
|
|
|
int nSampleX2 = (nSampleX1 + nPitch) % nWidth;
|
|
int nSampleY2 = (nSampleY1 + nPitch) % nWidth;
|
|
|
|
float fBlendX = (float)(x - nSampleX1) / (float)nPitch;
|
|
float fBlendY = (float)(y - nSampleY1) / (float)nPitch;
|
|
|
|
float fSampleT = (1.0f - fBlendX) * fSeed[nSampleY1 * nWidth + nSampleX1] + fBlendX * fSeed[nSampleY1 * nWidth + nSampleX2];
|
|
float fSampleB = (1.0f - fBlendX) * fSeed[nSampleY2 * nWidth + nSampleX1] + fBlendX * fSeed[nSampleY2 * nWidth + nSampleX2];
|
|
|
|
fScaleAcc += fScale;
|
|
fNoise += (fBlendY * (fSampleB - fSampleT) + fSampleT) * fScale;
|
|
fScale = fScale / fBias;
|
|
}
|
|
|
|
// Scale to seed range
|
|
fOutput[y * nWidth + x] = fNoise / fScaleAcc;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
int main()
|
|
{
|
|
OneLoneCoder_PerlinNoiseDemo game;
|
|
game.ConstructConsole(256, 256, 3, 3);
|
|
game.Start();
|
|
return 0;
|
|
} |