591 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			591 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| 
 | |
| ////////////////////////////////////////////////////////////
 | |
| // Headers
 | |
| ////////////////////////////////////////////////////////////
 | |
| #define STB_PERLIN_IMPLEMENTATION
 | |
| #include "stb_perlin.h"
 | |
| #include <SFML/Graphics.hpp>
 | |
| #include <vector>
 | |
| #include <deque>
 | |
| #include <sstream>
 | |
| #include <algorithm>
 | |
| #include <cstring>
 | |
| #include <cmath>
 | |
| 
 | |
| 
 | |
| 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<WorkItem> workQueue;
 | |
|     std::vector<sf::Thread*> 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<sf::Vertex> 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<sf::Vertex>& 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<float>(windowWidth) / static_cast<float>(resolutionX);
 | |
|     const float scalingFactorY = static_cast<float>(windowHeight) / static_cast<float>(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<sf::Vertex> 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;
 | |
|     }
 | |
| }
 |