diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 13542862b..a13f585a1 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 000000000..38428e317 --- /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 000000000..50a8f1ead --- /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 000000000..d85fbc81d Binary files /dev/null and b/examples/island/resources/sansation.ttf differ diff --git a/examples/island/resources/terrain.frag b/examples/island/resources/terrain.frag new file mode 100644 index 000000000..ae1818713 --- /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 000000000..a06996df5 --- /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 000000000..5d7622209 --- /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. +------------------------------------------------------------------------------ +*/