From d032050ccfce1becef0b0e13e3f7b71b2d6de4e7 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Tue, 24 Oct 2017 18:58:55 +0200 Subject: [PATCH] Added example demonstrating sf::VertexBuffer, sf::Shader and sf::Thread usage. --- examples/CMakeLists.txt | 1 + examples/island/CMakeLists.txt | 11 + examples/island/Island.cpp | 590 ++++++++++++++++++++++++ examples/island/resources/sansation.ttf | Bin 0 -> 28912 bytes examples/island/resources/terrain.frag | 11 + examples/island/resources/terrain.vert | 8 + examples/island/stb_perlin.h | 316 +++++++++++++ 7 files changed, 937 insertions(+) create mode 100644 examples/island/CMakeLists.txt create mode 100644 examples/island/Island.cpp create mode 100644 examples/island/resources/sansation.ttf create mode 100644 examples/island/resources/terrain.frag create mode 100644 examples/island/resources/terrain.vert create mode 100644 examples/island/stb_perlin.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 13542862..a13f585a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,7 @@ else(SFML_OS_IOS) add_subdirectory(joystick) add_subdirectory(opengl) add_subdirectory(shader) + add_subdirectory(island) if(SFML_OS_WINDOWS) add_subdirectory(win32) elseif(SFML_OS_LINUX OR SFML_OS_FREEBSD) diff --git a/examples/island/CMakeLists.txt b/examples/island/CMakeLists.txt new file mode 100644 index 00000000..38428e31 --- /dev/null +++ b/examples/island/CMakeLists.txt @@ -0,0 +1,11 @@ + +set(SRCROOT ${PROJECT_SOURCE_DIR}/examples/island) + +# all source files +set(SRC ${SRCROOT}/Island.cpp) + +# define the island target +sfml_add_example(island GUI_APP + SOURCES ${SRC} + DEPENDS sfml-graphics sfml-window sfml-system + RESOURCES_DIR resources) \ No newline at end of file diff --git a/examples/island/Island.cpp b/examples/island/Island.cpp new file mode 100644 index 00000000..50a8f1ea --- /dev/null +++ b/examples/island/Island.cpp @@ -0,0 +1,590 @@ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#define STB_PERLIN_IMPLEMENTATION +#include "stb_perlin.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace +{ + // Width and height of the application window + const unsigned int windowWidth = 800; + const unsigned int windowHeight = 600; + + // Resolution of the generated terrain + const unsigned int resolutionX = 800; + const unsigned int resolutionY = 600; + + // Thread pool parameters + const unsigned int threadCount = 4; + const unsigned int blockCount = 32; + + struct WorkItem + { + sf::Vertex* targetBuffer; + unsigned int index; + }; + + std::deque workQueue; + std::vector threads; + int pendingWorkCount = 0; + bool workPending = true; + bool bufferUploadPending = false; + sf::Mutex workQueueMutex; + + struct Setting + { + const char* name; + float* value; + }; + + // Terrain noise parameters + const int perlinOctaves = 3; + + float perlinFrequency = 7.0f; + float perlinFrequencyBase = 4.0f; + + // Terrain generation parameters + float heightBase = 0.0f; + float edgeFactor = 0.9f; + float edgeDropoffExponent = 1.5f; + + float snowcapHeight = 0.6f; + + // Terrain lighting parameters + float heightFactor = windowHeight / 2.0f; + float heightFlatten = 3.0f; + float lightFactor = 0.7f; +} + + +// Forward declarations of the functions we define further down +void threadFunction(); +void generateTerrain(sf::Vertex* vertexBuffer); + + +//////////////////////////////////////////////////////////// +/// Entry point of application +/// +/// \return Application exit code +/// +//////////////////////////////////////////////////////////// +int main() +{ + // Create the window of the application + sf::RenderWindow window(sf::VideoMode(windowWidth, windowHeight), "SFML Island", + sf::Style::Titlebar | sf::Style::Close); + window.setVerticalSyncEnabled(true); + + sf::Font font; + if (!font.loadFromFile("resources/sansation.ttf")) + return EXIT_FAILURE; + + // Create all of our graphics resources + sf::Text hudText; + sf::Text statusText; + sf::Shader terrainShader; + sf::RenderStates terrainStates(&terrainShader); + sf::VertexBuffer terrain(sf::Triangles, sf::VertexBuffer::Static); + + // Set up our text drawables + statusText.setFont(font); + statusText.setCharacterSize(28); + statusText.setFillColor(sf::Color::White); + statusText.setOutlineColor(sf::Color::Black); + statusText.setOutlineThickness(2.0f); + + hudText.setFont(font); + hudText.setCharacterSize(14); + hudText.setFillColor(sf::Color::White); + hudText.setOutlineColor(sf::Color::Black); + hudText.setOutlineThickness(2.0f); + hudText.setPosition(5.0f, 5.0f); + + // Staging buffer for our terrain data that we will upload to our VertexBuffer + std::vector terrainStagingBuffer; + + // Check whether the prerequisites are suppprted + bool prerequisitesSupported = sf::VertexBuffer::isAvailable() && sf::Shader::isAvailable(); + + // Set up our graphics resources and set the status text accordingly + if (!prerequisitesSupported) + { + statusText.setString("Shaders and/or Vertex Buffers Unsupported"); + } + else if (!terrainShader.loadFromFile("resources/terrain.vert", "resources/terrain.frag")) + { + prerequisitesSupported = false; + + statusText.setString("Failed to load shader program"); + } + else + { + // Start up our thread pool + for (unsigned int i = 0; i < threadCount; i++) + { + threads.push_back(new sf::Thread(threadFunction)); + threads.back()->launch(); + } + + // Create our VertexBuffer with enough space to hold all the terrain geometry + terrain.create(resolutionX * resolutionY * 6); + + // Resize the staging buffer to be able to hold all the terrain geometry + terrainStagingBuffer.resize(resolutionX * resolutionY * 6); + + // Generate the initial terrain + generateTerrain(&terrainStagingBuffer[0]); + + statusText.setString("Generating Terrain..."); + } + + // Center the status text + statusText.setPosition((windowWidth - statusText.getLocalBounds().width) / 2.f, (windowHeight - statusText.getLocalBounds().height) / 2.f); + + // Set up an array of pointers to our settings for arrow navigation + Setting settings[] = + { + {"perlinFrequency", &perlinFrequency}, + {"perlinFrequencyBase", &perlinFrequencyBase}, + {"heightBase", &heightBase}, + {"edgeFactor", &edgeFactor}, + {"edgeDropoffExponent", &edgeDropoffExponent}, + {"snowcapHeight", &snowcapHeight}, + {"heightFactor", &heightFactor}, + {"heightFlatten", &heightFlatten}, + {"lightFactor", &lightFactor} + }; + + const int settingCount = 9; + int currentSetting = 0; + + std::ostringstream osstr; + sf::Clock clock; + + while (window.isOpen()) + { + // Handle events + sf::Event event; + while (window.pollEvent(event)) + { + // Window closed or escape key pressed: exit + if ((event.type == sf::Event::Closed) || + ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))) + { + window.close(); + break; + } + + // Arrow key pressed: + if (prerequisitesSupported && (event.type == sf::Event::KeyPressed)) + { + switch (event.key.code) + { + case sf::Keyboard::Return: generateTerrain(&terrainStagingBuffer[0]); break; + case sf::Keyboard::Down: currentSetting = (currentSetting + 1) % settingCount; break; + case sf::Keyboard::Up: currentSetting = (currentSetting + settingCount - 1) % settingCount; break; + case sf::Keyboard::Left: *(settings[currentSetting].value) -= 0.1f; break; + case sf::Keyboard::Right: *(settings[currentSetting].value) += 0.1f; break; + default: break; + } + } + } + + // Clear, draw graphics objects and display + window.clear(); + + window.draw(statusText); + + if (prerequisitesSupported) + { + { + sf::Lock lock(workQueueMutex); + + // Don't bother updating/drawing the VertexBuffer while terrain is being regenerated + if (!pendingWorkCount) + { + // If there is new data pending to be uploaded to the VertexBuffer, do it now + if (bufferUploadPending) + { + terrain.update(&terrainStagingBuffer[0]); + bufferUploadPending = false; + } + + terrainShader.setUniform("lightFactor", lightFactor); + window.draw(terrain, terrainStates); + } + } + + // Update and draw the HUD text + osstr.str(""); + osstr << "Frame: " << clock.restart().asMilliseconds() << "ms\n" + << "perlinOctaves: " << perlinOctaves << "\n\n" + << "Use the arrow keys to change the values.\nUse the return key to regenerate the terrain.\n\n"; + + for (int i = 0; i < settingCount; ++i) + osstr << ((i == currentSetting) ? ">> " : " ") << settings[i].name << ": " << *(settings[i].value) << "\n"; + + hudText.setString(osstr.str()); + + window.draw(hudText); + } + + // Display things on screen + window.display(); + } + + // Shut down our thread pool + { + sf::Lock lock(workQueueMutex); + workPending = false; + } + + while (!threads.empty()) + { + threads.back()->wait(); + delete threads.back(); + threads.pop_back(); + } + + return EXIT_SUCCESS; +} + + +//////////////////////////////////////////////////////////// +/// Get the terrain elevation at the given coordinates. +/// +//////////////////////////////////////////////////////////// +float getElevation(float x, float y) +{ + x = x / resolutionX - 0.5f; + y = y / resolutionY - 0.5f; + + float elevation = 0.0f; + + for (int i = 0; i < perlinOctaves; i++) + { + elevation += stb_perlin_noise3( + x * perlinFrequency * std::pow(perlinFrequencyBase, i), + y * perlinFrequency * std::pow(perlinFrequencyBase, i), + 0, 0, 0, 0 + ) * std::pow(perlinFrequencyBase, -i); + } + + elevation = (elevation + 1.f) / 2.f; + + float distance = 2.0f * std::sqrt(x * x + y * y); + elevation = (elevation + heightBase) * (1.0f - edgeFactor * std::pow(distance, edgeDropoffExponent)); + elevation = std::min(std::max(elevation, 0.0f), 1.0f); + + return elevation; +} + + +//////////////////////////////////////////////////////////// +/// Get the terrain moisture at the given coordinates. +/// +//////////////////////////////////////////////////////////// +float getMoisture(float x, float y) +{ + x = x / resolutionX - 0.5f; + y = y / resolutionY - 0.5f; + + float moisture = stb_perlin_noise3( + x * 4.f + 0.5f, + y * 4.f + 0.5f, + 0, 0, 0, 0 + ); + + return (moisture + 1.f) / 2.f; +} + + +//////////////////////////////////////////////////////////// +/// Get the lowlands terrain color for the given moisture. +/// +//////////////////////////////////////////////////////////// +sf::Color getLowlandsTerrainColor(float moisture) +{ + sf::Color color = + moisture < 0.27f ? sf::Color(240, 240, 180) : + moisture < 0.3f ? sf::Color(240 - 240 * (moisture - 0.27f) / 0.03f, 240 - 40 * (moisture - 0.27f) / 0.03f, 180 - 180 * (moisture - 0.27f) / 0.03f) : + moisture < 0.4f ? sf::Color(0, 200, 0) : + moisture < 0.48f ? sf::Color(0, 200 - 40 * (moisture - 0.4f) / 0.08f, 0) : + moisture < 0.6f ? sf::Color(0, 160, 0) : + moisture < 0.7f ? sf::Color(34 * (moisture - 0.6f) / 0.1f, 160 - 60 * (moisture - 0.6f) / 0.1f, 34 * (moisture - 0.6f) / 0.1f) : + sf::Color(34, 100, 34); + + return color; +} + + +//////////////////////////////////////////////////////////// +/// Get the highlands terrain color for the given elevation +/// and moisture. +/// +//////////////////////////////////////////////////////////// +sf::Color getHighlandsTerrainColor(float elevation, float moisture) +{ + sf::Color lowlandsColor = getLowlandsTerrainColor(moisture); + + sf::Color color = + moisture < 0.6f ? sf::Color(112, 128, 144) : + sf::Color(112 + 110 * (moisture - 0.6f) / 0.4f, 128 + 56 * (moisture - 0.6f) / 0.4f, 144 - 9 * (moisture - 0.6f) / 0.4f); + + float factor = std::min((elevation - 0.4f) / 0.1f, 1.f); + + color.r = lowlandsColor.r * (1.f - factor) + color.r * factor; + color.g = lowlandsColor.g * (1.f - factor) + color.g * factor; + color.b = lowlandsColor.b * (1.f - factor) + color.b * factor; + + return color; +} + + +//////////////////////////////////////////////////////////// +/// Get the snowcap terrain color for the given elevation +/// and moisture. +/// +//////////////////////////////////////////////////////////// +sf::Color getSnowcapTerrainColor(float elevation, float moisture) +{ + sf::Color highlandsColor = getHighlandsTerrainColor(elevation, moisture); + + sf::Color color = sf::Color::White; + + float factor = std::min((elevation - snowcapHeight) / 0.05f, 1.f); + + color.r = highlandsColor.r * (1.f - factor) + color.r * factor; + color.g = highlandsColor.g * (1.f - factor) + color.g * factor; + color.b = highlandsColor.b * (1.f - factor) + color.b * factor; + + return color; +} + + +//////////////////////////////////////////////////////////// +/// Get the terrain color for the given elevation and +/// moisture. +/// +//////////////////////////////////////////////////////////// +sf::Color getTerrainColor(float elevation, float moisture) +{ + sf::Color color = + elevation < 0.11f ? sf::Color(0, 0, elevation / 0.11f * 74.f + 181.0f) : + elevation < 0.14f ? sf::Color(std::pow((elevation - 0.11f) / 0.03f, 0.3f) * 48.f, std::pow((elevation - 0.11f) / 0.03f, 0.3f) * 48.f, 255) : + elevation < 0.16f ? sf::Color((elevation - 0.14f) * 128.f / 0.02f + 48.f, (elevation - 0.14f) * 128.f / 0.02f + 48.f, 127.0f + (0.16f - elevation) * 128.f / 0.02f) : + elevation < 0.17f ? sf::Color(240, 230, 140) : + elevation < 0.4f ? getLowlandsTerrainColor(moisture) : + elevation < snowcapHeight ? getHighlandsTerrainColor(elevation, moisture) : + getSnowcapTerrainColor(elevation, moisture); + + return color; +} + + +//////////////////////////////////////////////////////////// +/// Compute a compressed representation of the surface +/// normal based on the given coordinates, and the elevation +/// of the 4 adjacent neighbours. +/// +//////////////////////////////////////////////////////////// +sf::Vector2f computeNormal(int x, int y, float left, float right, float bottom, float top) +{ + sf::Vector3f deltaX(1, 0, (std::pow(right, heightFlatten) - std::pow(left, heightFlatten)) * heightFactor); + sf::Vector3f deltaY(0, 1, (std::pow(top, heightFlatten) - std::pow(bottom, heightFlatten)) * heightFactor); + + sf::Vector3f crossProduct( + deltaX.y * deltaY.z - deltaX.z * deltaY.y, + deltaX.z * deltaY.x - deltaX.x * deltaY.z, + deltaX.x * deltaY.y - deltaX.y * deltaY.x + ); + + // Scale cross product to make z component 1.0f so we can drop it + crossProduct /= crossProduct.z; + + // Return "compressed" normal + return sf::Vector2f(crossProduct.x, crossProduct.y); +} + + +//////////////////////////////////////////////////////////// +/// Process a terrain generation work item. Use the vector +/// of vertices as scratch memory and upload the data to +/// the vertex buffer when done. +/// +//////////////////////////////////////////////////////////// +void processWorkItem(std::vector& vertices, const WorkItem& workItem) +{ + unsigned int rowBlockSize = (resolutionY / blockCount) + 1; + unsigned int rowStart = rowBlockSize * workItem.index; + + if (rowStart >= resolutionY) + return; + + unsigned int rowEnd = std::min(rowStart + rowBlockSize, resolutionY); + unsigned int rowCount = rowEnd - rowStart; + + const float scalingFactorX = static_cast(windowWidth) / static_cast(resolutionX); + const float scalingFactorY = static_cast(windowHeight) / static_cast(resolutionY); + + for (unsigned int y = rowStart; y < rowEnd; y++) + { + for (int x = 0; x < resolutionX; x++) + { + int arrayIndexBase = ((y - rowStart) * resolutionX + x) * 6; + + // Top left corner (first triangle) + if (x > 0) + { + vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - 6 + 5]; + } + else if (y > rowStart) + { + vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - resolutionX * 6 + 1]; + } + else + { + vertices[arrayIndexBase + 0].position = sf::Vector2f(x * scalingFactorX, y * scalingFactorY); + vertices[arrayIndexBase + 0].color = getTerrainColor(getElevation(x, y), getMoisture(x, y)); + vertices[arrayIndexBase + 0].texCoords = computeNormal(x, y, getElevation(x - 1, y), getElevation(x + 1, y), getElevation(x, y + 1), getElevation(x, y - 1)); + } + + // Bottom left corner (first triangle) + if (x > 0) + { + vertices[arrayIndexBase + 1] = vertices[arrayIndexBase - 6 + 2]; + } + else + { + vertices[arrayIndexBase + 1].position = sf::Vector2f(x * scalingFactorX, (y + 1) * scalingFactorY); + vertices[arrayIndexBase + 1].color = getTerrainColor(getElevation(x, y + 1), getMoisture(x, y + 1)); + vertices[arrayIndexBase + 1].texCoords = computeNormal(x, y + 1, getElevation(x - 1, y + 1), getElevation(x + 1, y + 1), getElevation(x, y + 2), getElevation(x, y)); + } + + // Bottom right corner (first triangle) + vertices[arrayIndexBase + 2].position = sf::Vector2f((x + 1) * scalingFactorX, (y + 1) * scalingFactorY); + vertices[arrayIndexBase + 2].color = getTerrainColor(getElevation(x + 1, y + 1), getMoisture(x + 1, y + 1)); + vertices[arrayIndexBase + 2].texCoords = computeNormal(x + 1, y + 1, getElevation(x, y + 1), getElevation(x + 2, y + 1), getElevation(x + 1, y + 2), getElevation(x + 1, y)); + + // Top left corner (second triangle) + vertices[arrayIndexBase + 3] = vertices[arrayIndexBase + 0]; + + // Bottom right corner (second triangle) + vertices[arrayIndexBase + 4] = vertices[arrayIndexBase + 2]; + + // Top right corner (second triangle) + if (y > rowStart) + { + vertices[arrayIndexBase + 5] = vertices[arrayIndexBase - resolutionX * 6 + 2]; + } + else + { + vertices[arrayIndexBase + 5].position = sf::Vector2f((x + 1) * scalingFactorX, y * scalingFactorY); + vertices[arrayIndexBase + 5].color = getTerrainColor(getElevation(x + 1, y), getMoisture(x + 1, y)); + vertices[arrayIndexBase + 5].texCoords = computeNormal(x + 1, y, getElevation(x, y), getElevation(x + 2, y), getElevation(x + 1, y + 1), getElevation(x + 1, y - 1)); + } + } + } + + // Copy the resulting geometry from our thread-local buffer into the target buffer + std::memcpy(workItem.targetBuffer + (resolutionX * rowStart * 6), &vertices[0], sizeof(sf::Vertex) * resolutionX * rowCount * 6); +} + + +//////////////////////////////////////////////////////////// +/// Worker thread entry point. We use a thread pool to avoid +/// the heavy cost of constantly recreating and starting +/// new threads whenever we need to regenerate the terrain. +/// +//////////////////////////////////////////////////////////// +void threadFunction() +{ + unsigned int rowBlockSize = (resolutionY / blockCount) + 1; + + std::vector vertices(resolutionX * rowBlockSize * 6); + + WorkItem workItem = {0, 0}; + + // Loop until the application exits + for (;;) + { + workItem.targetBuffer = 0; + + // Check if there are new work items in the queue + { + sf::Lock lock(workQueueMutex); + + if (!workPending) + return; + + if (!workQueue.empty()) + { + workItem = workQueue.front(); + workQueue.pop_front(); + } + } + + // If we didn't receive a new work item, keep looping + if (!workItem.targetBuffer) + { + sf::sleep(sf::milliseconds(10)); + + continue; + } + + processWorkItem(vertices, workItem); + + { + sf::Lock lock(workQueueMutex); + + --pendingWorkCount; + } + } +} + + +//////////////////////////////////////////////////////////// +/// Terrain generation entry point. This queues up the +/// generation work items which the worker threads dequeue +/// and process. +/// +//////////////////////////////////////////////////////////// +void generateTerrain(sf::Vertex* buffer) +{ + bufferUploadPending = true; + + // Make sure the work queue is empty before queuing new work + for (;;) + { + { + sf::Lock lock(workQueueMutex); + + if (workQueue.empty()) + break; + } + + sf::sleep(sf::milliseconds(10)); + } + + // Queue all the new work items + { + sf::Lock lock(workQueueMutex); + + for (unsigned int i = 0; i < blockCount; i++) + { + WorkItem workItem = {buffer, i}; + workQueue.push_back(workItem); + } + + pendingWorkCount = blockCount; + } +} diff --git a/examples/island/resources/sansation.ttf b/examples/island/resources/sansation.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d85fbc81d91e22c647a3338a37f24a9d684dcf8a GIT binary patch literal 28912 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 Y(-osHj2F<;P%p3rF{>lYhrW~jFV!_TuK)l5 literal 0 HcmV?d00001 diff --git a/examples/island/resources/terrain.frag b/examples/island/resources/terrain.frag new file mode 100644 index 00000000..ae181871 --- /dev/null +++ b/examples/island/resources/terrain.frag @@ -0,0 +1,11 @@ +varying vec3 normal; +uniform float lightFactor; + +void main() +{ + vec3 lightPosition = vec3(-1.0, 1.0, 1.0); + vec3 eyePosition = vec3(0.0, 0.0, 1.0); + vec3 halfVector = normalize(lightPosition + eyePosition); + float intensity = lightFactor + (1.0 - lightFactor) * dot(normalize(normal), normalize(halfVector)); + gl_FragColor = gl_Color * vec4(intensity, intensity, intensity, 1.0); +} diff --git a/examples/island/resources/terrain.vert b/examples/island/resources/terrain.vert new file mode 100644 index 00000000..a06996df --- /dev/null +++ b/examples/island/resources/terrain.vert @@ -0,0 +1,8 @@ +varying vec3 normal; + +void main() +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_FrontColor = gl_Color; + normal = vec3(gl_MultiTexCoord0.xy, 1.0); +} diff --git a/examples/island/stb_perlin.h b/examples/island/stb_perlin.h new file mode 100644 index 00000000..5d762220 --- /dev/null +++ b/examples/island/stb_perlin.h @@ -0,0 +1,316 @@ +// stb_perlin.h - v0.3 - perlin noise +// public domain single-file C implementation by Sean Barrett +// +// LICENSE +// +// See end of file. +// +// +// to create the implementation, +// #define STB_PERLIN_IMPLEMENTATION +// in *one* C/CPP file that includes this file. +// +// +// Documentation: +// +// float stb_perlin_noise3( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0) +// +// This function computes a random value at the coordinate (x,y,z). +// Adjacent random values are continuous but the noise fluctuates +// its randomness with period 1, i.e. takes on wholly unrelated values +// at integer points. Specifically, this implements Ken Perlin's +// revised noise function from 2002. +// +// The "wrap" parameters can be used to create wraparound noise that +// wraps at powers of two. The numbers MUST be powers of two. Specify +// 0 to mean "don't care". (The noise always wraps every 256 due +// details of the implementation, even if you ask for larger or no +// wrapping.) +// +// Fractal Noise: +// +// Three common fractal noise functions are included, which produce +// a wide variety of nice effects depending on the parameters +// provided. Note that each function will call stb_perlin_noise3 +// 'octaves' times, so this parameter will affect runtime. +// +// float stb_perlin_ridge_noise3(float x, float y, float z, +// float lacunarity, float gain, float offset, int octaves, +// int x_wrap, int y_wrap, int z_wrap); +// +// float stb_perlin_fbm_noise3(float x, float y, float z, +// float lacunarity, float gain, int octaves, +// int x_wrap, int y_wrap, int z_wrap); +// +// float stb_perlin_turbulence_noise3(float x, float y, float z, +// float lacunarity, float gain,int octaves, +// int x_wrap, int y_wrap, int z_wrap); +// +// Typical values to start playing with: +// octaves = 6 -- number of "octaves" of noise3() to sum +// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) +// gain = 0.5 -- relative weighting applied to each successive octave +// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure +// +// +// Contributors: +// Jack Mott - additional noise functions +// + + +#ifdef __cplusplus +extern "C" { +#endif +extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap); +extern float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float gain, float offset, int octaves,int x_wrap, int y_wrap, int z_wrap); +extern float stb_perlin_fbm_noise3(float x, float y, float z,float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap); +extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap); +#ifdef __cplusplus +} +#endif + +#ifdef STB_PERLIN_IMPLEMENTATION + +// not same permutation table as Perlin's reference to avoid copyright issues; +// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/ +// @OPTIMIZE: should this be unsigned char instead of int for cache? +static unsigned char stb__perlin_randtab[512] = +{ + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, + + // and a second copy so we don't need an extra mask or static initializer + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, +}; + +static float stb__perlin_lerp(float a, float b, float t) +{ + return a + (b-a) * t; +} + +static int stb__perlin_fastfloor(float a) +{ + int ai = (int) a; + return (a < ai) ? ai-1 : ai; +} + +// different grad function from Perlin's, but easy to modify to match reference +static float stb__perlin_grad(int hash, float x, float y, float z) +{ + static float basis[12][4] = + { + { 1, 1, 0 }, + { -1, 1, 0 }, + { 1,-1, 0 }, + { -1,-1, 0 }, + { 1, 0, 1 }, + { -1, 0, 1 }, + { 1, 0,-1 }, + { -1, 0,-1 }, + { 0, 1, 1 }, + { 0,-1, 1 }, + { 0, 1,-1 }, + { 0,-1,-1 }, + }; + + // perlin's gradient has 12 cases so some get used 1/16th of the time + // and some 2/16ths. We reduce bias by changing those fractions + // to 5/64ths and 6/64ths, and the same 4 cases get the extra weight. + static unsigned char indices[64] = + { + 0,1,2,3,4,5,6,7,8,9,10,11, + 0,9,1,11, + 0,1,2,3,4,5,6,7,8,9,10,11, + 0,1,2,3,4,5,6,7,8,9,10,11, + 0,1,2,3,4,5,6,7,8,9,10,11, + 0,1,2,3,4,5,6,7,8,9,10,11, + }; + + // if you use reference permutation table, change 63 below to 15 to match reference + // (this is why the ordering of the table above is funky) + float *grad = basis[indices[hash & 63]]; + return grad[0]*x + grad[1]*y + grad[2]*z; +} + +float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + unsigned int x_mask = (x_wrap-1) & 255; + unsigned int y_mask = (y_wrap-1) & 255; + unsigned int z_mask = (z_wrap-1) & 255; + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x0 = px & x_mask, x1 = (px+1) & x_mask; + int y0 = py & y_mask, y1 = (py+1) & y_mask; + int z0 = pz & z_mask, z1 = (pz+1) & z_mask; + int r0,r1, r00,r01,r10,r11; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0]; + r1 = stb__perlin_randtab[x1]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} + +float stb_perlin_ridge_noise3(float x, float y, float z,float lacunarity, float gain, float offset, int octaves,int x_wrap, int y_wrap, int z_wrap) +{ + int i; + float frequency = 1.0f; + float prev = 1.0f; + float amplitude = 0.5f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = (float)(stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)); + r = r<0 ? -r : r; // fabs() + r = offset - r; + r = r*r; + sum += r*amplitude*prev; + prev = r; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +float stb_perlin_fbm_noise3(float x, float y, float z,float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + sum += stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves,int x_wrap, int y_wrap, int z_wrap) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = stb_perlin_noise3(x*frequency,y*frequency,z*frequency,x_wrap,y_wrap,z_wrap)*amplitude; + r = r<0 ? -r : r; // fabs() + sum += r; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +#endif // STB_PERLIN_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/