Simplify island example using vectors and extracting repetitive code

This commit is contained in:
kimci86 2024-05-04 22:12:54 +02:00 committed by Lukas Dürrenberger
parent 6bcc3414fc
commit 267bbe35b8

View File

@ -24,12 +24,10 @@
namespace namespace
{ {
// Width and height of the application window // Width and height of the application window
const unsigned int windowWidth = 800; const sf::Vector2u windowSize(800, 600);
const unsigned int windowHeight = 600;
// Resolution of the generated terrain // Resolution of the generated terrain
const unsigned int resolutionX = 800; const sf::Vector2u resolution(800, 600);
const unsigned int resolutionY = 600;
// Thread pool parameters // Thread pool parameters
const unsigned int threadCount = 4; const unsigned int threadCount = 4;
@ -68,7 +66,7 @@ float edgeDropoffExponent = 1.5f;
float snowcapHeight = 0.6f; float snowcapHeight = 0.6f;
// Terrain lighting parameters // Terrain lighting parameters
float heightFactor = windowHeight / 2.0f; float heightFactor = static_cast<float>(windowSize.y) / 2.0f;
float heightFlatten = 3.0f; float heightFlatten = 3.0f;
float lightFactor = 0.7f; float lightFactor = 0.7f;
} // namespace } // namespace
@ -88,7 +86,7 @@ void generateTerrain(sf::Vertex* vertexBuffer);
int main() int main()
{ {
// Create the window of the application // Create the window of the application
sf::RenderWindow window(sf::VideoMode({windowWidth, windowHeight}), "SFML Island", sf::Style::Titlebar | sf::Style::Close); sf::RenderWindow window(sf::VideoMode(windowSize), "SFML Island", sf::Style::Titlebar | sf::Style::Close);
window.setVerticalSyncEnabled(true); window.setVerticalSyncEnabled(true);
const sf::Font font("resources/tuffy.ttf"); const sf::Font font("resources/tuffy.ttf");
@ -133,14 +131,14 @@ int main()
} }
// Create our VertexBuffer with enough space to hold all the terrain geometry // Create our VertexBuffer with enough space to hold all the terrain geometry
if (!terrain.create(resolutionX * resolutionY * 6)) if (!terrain.create(resolution.x * resolution.y * 6))
{ {
std::cerr << "Failed to create vertex buffer" << std::endl; std::cerr << "Failed to create vertex buffer" << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// Resize the staging buffer to be able to hold all the terrain geometry // Resize the staging buffer to be able to hold all the terrain geometry
terrainStagingBuffer.resize(resolutionX * resolutionY * 6); terrainStagingBuffer.resize(resolution.x * resolution.y * 6);
// Generate the initial terrain // Generate the initial terrain
generateTerrain(terrainStagingBuffer.data()); generateTerrain(terrainStagingBuffer.data());
@ -152,7 +150,7 @@ int main()
} }
// Center the status text // Center the status text
statusText.setPosition((sf::Vector2f(windowWidth, windowHeight) - statusText.getLocalBounds().size) / 2.f); statusText.setPosition((sf::Vector2f(windowSize) - statusText.getLocalBounds().size) / 2.f);
// Set up an array of pointers to our settings for arrow navigation // Set up an array of pointers to our settings for arrow navigation
constexpr std::array<Setting, 9> settings = { constexpr std::array<Setting, 9> settings = {
@ -279,58 +277,45 @@ int main()
/// Get the terrain elevation at the given coordinates. /// Get the terrain elevation at the given coordinates.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
float getElevation(float x, float y) float getElevation(sf::Vector2u position)
{ {
x = x / resolutionX - 0.5f; const sf::Vector2f normalized = sf::Vector2f(position).componentWiseDiv(sf::Vector2f(resolution)) -
y = y / resolutionY - 0.5f; sf::Vector2f(0.5f, 0.5f);
float elevation = 0.0f; float elevation = 0.0f;
for (int i = 0; i < perlinOctaves; ++i) for (int i = 0; i < perlinOctaves; ++i)
{ {
elevation += stb_perlin_noise3(x * perlinFrequency * static_cast<float>(std::pow(perlinFrequencyBase, i)), const sf::Vector2f scaled = normalized * perlinFrequency * static_cast<float>(std::pow(perlinFrequencyBase, i));
y * perlinFrequency * static_cast<float>(std::pow(perlinFrequencyBase, i)), elevation += stb_perlin_noise3(scaled.x, scaled.y, 0, 0, 0, 0) *
0,
0,
0,
0) *
static_cast<float>(std::pow(perlinFrequencyBase, -i)); static_cast<float>(std::pow(perlinFrequencyBase, -i));
} }
elevation = (elevation + 1.f) / 2.f; elevation = (elevation + 1.f) / 2.f;
const float distance = 2.0f * std::sqrt(x * x + y * y); const float distance = 2.0f * normalized.length();
elevation = (elevation + heightBase) * (1.0f - edgeFactor * std::pow(distance, edgeDropoffExponent)); elevation = (elevation + heightBase) * (1.0f - edgeFactor * std::pow(distance, edgeDropoffExponent));
elevation = std::clamp(elevation, 0.0f, 1.0f); elevation = std::clamp(elevation, 0.0f, 1.0f);
return elevation; return elevation;
} }
float getElevation(unsigned int x, unsigned int y)
{
return getElevation(static_cast<float>(x), static_cast<float>(y));
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// Get the terrain moisture at the given coordinates. /// Get the terrain moisture at the given coordinates.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
float getMoisture(float x, float y) float getMoisture(sf::Vector2u position)
{ {
x = x / resolutionX - 0.5f; const sf::Vector2f normalized = sf::Vector2f(position).componentWiseDiv(sf::Vector2f(resolution)) -
y = y / resolutionY - 0.5f; sf::Vector2f(0.5f, 0.5f);
const sf::Vector2f transformed = normalized * 4.f + sf::Vector2f(0.5f, 0.5f);
const float moisture = stb_perlin_noise3(x * 4.f + 0.5f, y * 4.f + 0.5f, 0, 0, 0, 0); const float moisture = stb_perlin_noise3(transformed.x, transformed.y, 0, 0, 0, 0);
return (moisture + 1.f) / 2.f; return (moisture + 1.f) / 2.f;
} }
float getMoisture(unsigned int x, unsigned int y)
{
return getMoisture(static_cast<float>(x), static_cast<float>(y));
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// Get the lowlands terrain color for the given moisture. /// Get the lowlands terrain color for the given moisture.
@ -372,18 +357,16 @@ sf::Color getHighlandsTerrainColor(float elevation, float moisture)
{ {
const sf::Color lowlandsColor = getLowlandsTerrainColor(moisture); const sf::Color lowlandsColor = getLowlandsTerrainColor(moisture);
sf::Color color = moisture < 0.6f ? sf::Color(112, 128, 144) const sf::Color color = moisture < 0.6f ? sf::Color(112, 128, 144)
: colorFromFloats(112 + (110 * (moisture - 0.6f) / 0.4f), : colorFromFloats(112 + (110 * (moisture - 0.6f) / 0.4f),
128 + (56 * (moisture - 0.6f) / 0.4f), 128 + (56 * (moisture - 0.6f) / 0.4f),
144 - (9 * (moisture - 0.6f) / 0.4f)); 144 - (9 * (moisture - 0.6f) / 0.4f));
const float factor = std::min((elevation - 0.4f) / 0.1f, 1.f); const float factor = std::min((elevation - 0.4f) / 0.1f, 1.f);
color.r = static_cast<std::uint8_t>(lowlandsColor.r * (1.f - factor) + color.r * factor); return colorFromFloats(lowlandsColor.r * (1.f - factor) + color.r * factor,
color.g = static_cast<std::uint8_t>(lowlandsColor.g * (1.f - factor) + color.g * factor); lowlandsColor.g * (1.f - factor) + color.g * factor,
color.b = static_cast<std::uint8_t>(lowlandsColor.b * (1.f - factor) + color.b * factor); lowlandsColor.b * (1.f - factor) + color.b * factor);
return color;
} }
@ -396,15 +379,13 @@ sf::Color getSnowcapTerrainColor(float elevation, float moisture)
{ {
const sf::Color highlandsColor = getHighlandsTerrainColor(elevation, moisture); const sf::Color highlandsColor = getHighlandsTerrainColor(elevation, moisture);
sf::Color color = sf::Color::White; const sf::Color color = sf::Color::White;
const float factor = std::min((elevation - snowcapHeight) / 0.05f, 1.f); const float factor = std::min((elevation - snowcapHeight) / 0.05f, 1.f);
color.r = static_cast<std::uint8_t>(highlandsColor.r * (1.f - factor) + color.r * factor); return colorFromFloats(highlandsColor.r * (1.f - factor) + color.r * factor,
color.g = static_cast<std::uint8_t>(highlandsColor.g * (1.f - factor) + color.g * factor); highlandsColor.g * (1.f - factor) + color.g * factor,
color.b = static_cast<std::uint8_t>(highlandsColor.b * (1.f - factor) + color.b * factor); highlandsColor.b * (1.f - factor) + color.b * factor);
return color;
} }
@ -446,9 +427,7 @@ sf::Vector2f computeNormal(float left, float right, float bottom, float top)
const sf::Vector3f deltaX(1, 0, (std::pow(right, heightFlatten) - std::pow(left, heightFlatten)) * heightFactor); const sf::Vector3f deltaX(1, 0, (std::pow(right, heightFlatten) - std::pow(left, heightFlatten)) * heightFactor);
const sf::Vector3f deltaY(0, 1, (std::pow(top, heightFlatten) - std::pow(bottom, heightFlatten)) * heightFactor); const 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, sf::Vector3f crossProduct = deltaX.cross(deltaY);
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 // Scale cross product to make z component 1.0f so we can drop it
crossProduct /= crossProduct.z; crossProduct /= crossProduct.z;
@ -458,6 +437,26 @@ sf::Vector2f computeNormal(float left, float right, float bottom, float top)
} }
////////////////////////////////////////////////////////////
/// Compute the vertex representing the terrain at the given
/// coordinates.
///
////////////////////////////////////////////////////////////
const auto scalingFactors = sf::Vector2f(windowSize).componentWiseDiv(sf::Vector2f(resolution));
sf::Vertex computeVertex(sf::Vector2u position)
{
sf::Vertex vertex;
vertex.position = sf::Vector2f(position).componentWiseMul(scalingFactors);
vertex.color = getTerrainColor(getElevation(position), getMoisture(position));
vertex.texCoords = computeNormal(getElevation(position - sf::Vector2u(1, 0)),
getElevation(position + sf::Vector2u(1, 0)),
getElevation(position + sf::Vector2u(0, 1)),
getElevation(position - sf::Vector2u(0, 1)));
return vertex;
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// Process a terrain generation work item. Use the vector /// Process a terrain generation work item. Use the vector
/// of vertices as scratch memory and upload the data to /// of vertices as scratch memory and upload the data to
@ -466,23 +465,20 @@ sf::Vector2f computeNormal(float left, float right, float bottom, float top)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem) void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem)
{ {
const unsigned int rowBlockSize = (resolutionY / blockCount) + 1; const unsigned int rowBlockSize = (resolution.y / blockCount) + 1;
const unsigned int rowStart = rowBlockSize * workItem.index; const unsigned int rowStart = rowBlockSize * workItem.index;
if (rowStart >= resolutionY) if (rowStart >= resolution.y)
return; return;
const unsigned int rowEnd = std::min(rowStart + rowBlockSize, resolutionY); const unsigned int rowEnd = std::min(rowStart + rowBlockSize, resolution.y);
const unsigned int rowCount = rowEnd - rowStart; const unsigned int rowCount = rowEnd - rowStart;
const float scalingFactorX = float{windowWidth} / float{resolutionX};
const float scalingFactorY = float{windowHeight} / float{resolutionY};
for (unsigned int y = rowStart; y < rowEnd; ++y) for (unsigned int y = rowStart; y < rowEnd; ++y)
{ {
for (unsigned int x = 0; x < resolutionX; ++x) for (unsigned int x = 0; x < resolution.x; ++x)
{ {
const unsigned int arrayIndexBase = ((y - rowStart) * resolutionX + x) * 6; const unsigned int arrayIndexBase = ((y - rowStart) * resolution.x + x) * 6;
// Top left corner (first triangle) // Top left corner (first triangle)
if (x > 0) if (x > 0)
@ -491,17 +487,11 @@ void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem
} }
else if (y > rowStart) else if (y > rowStart)
{ {
vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - resolutionX * 6 + 1]; vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - resolution.x * 6 + 1];
} }
else else
{ {
vertices[arrayIndexBase + 0].position = sf::Vector2f(static_cast<float>(x) * scalingFactorX, vertices[arrayIndexBase + 0] = computeVertex({x, y});
static_cast<float>(y) * scalingFactorY);
vertices[arrayIndexBase + 0].color = getTerrainColor(getElevation(x, y), getMoisture(x, y));
vertices[arrayIndexBase + 0].texCoords = computeNormal(getElevation(x - 1, y),
getElevation(x + 1, y),
getElevation(x, y + 1),
getElevation(x, y - 1));
} }
// Bottom left corner (first triangle) // Bottom left corner (first triangle)
@ -511,23 +501,11 @@ void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem
} }
else else
{ {
vertices[arrayIndexBase + 1].position = sf::Vector2f(static_cast<float>(x) * scalingFactorX, vertices[arrayIndexBase + 1] = computeVertex({x, y + 1});
static_cast<float>(y + 1) * scalingFactorY);
vertices[arrayIndexBase + 1].color = getTerrainColor(getElevation(x, y + 1), getMoisture(x, y + 1));
vertices[arrayIndexBase + 1].texCoords = computeNormal(getElevation(x - 1, y + 1),
getElevation(x + 1, y + 1),
getElevation(x, y + 2),
getElevation(x, y));
} }
// Bottom right corner (first triangle) // Bottom right corner (first triangle)
vertices[arrayIndexBase + 2].position = sf::Vector2f(static_cast<float>(x + 1) * scalingFactorX, vertices[arrayIndexBase + 2] = computeVertex({x + 1, y + 1});
static_cast<float>(y + 1) * scalingFactorY);
vertices[arrayIndexBase + 2].color = getTerrainColor(getElevation(x + 1, y + 1), getMoisture(x + 1, y + 1));
vertices[arrayIndexBase + 2].texCoords = computeNormal(getElevation(x, y + 1),
getElevation(x + 2, y + 1),
getElevation(x + 1, y + 2),
getElevation(x + 1, y));
// Top left corner (second triangle) // Top left corner (second triangle)
vertices[arrayIndexBase + 3] = vertices[arrayIndexBase + 0]; vertices[arrayIndexBase + 3] = vertices[arrayIndexBase + 0];
@ -538,25 +516,19 @@ void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem
// Top right corner (second triangle) // Top right corner (second triangle)
if (y > rowStart) if (y > rowStart)
{ {
vertices[arrayIndexBase + 5] = vertices[arrayIndexBase - resolutionX * 6 + 2]; vertices[arrayIndexBase + 5] = vertices[arrayIndexBase - resolution.x * 6 + 2];
} }
else else
{ {
vertices[arrayIndexBase + 5].position = sf::Vector2f(static_cast<float>(x + 1) * scalingFactorX, vertices[arrayIndexBase + 5] = computeVertex({x + 1, y});
static_cast<float>(y) * scalingFactorY);
vertices[arrayIndexBase + 5].color = getTerrainColor(getElevation(x + 1, y), getMoisture(x + 1, y));
vertices[arrayIndexBase + 5].texCoords = computeNormal(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 // Copy the resulting geometry from our thread-local buffer into the target buffer
std::memcpy(workItem.targetBuffer + (resolutionX * rowStart * 6), std::memcpy(workItem.targetBuffer + (resolution.x * rowStart * 6),
vertices.data(), vertices.data(),
sizeof(sf::Vertex) * resolutionX * rowCount * 6); sizeof(sf::Vertex) * resolution.x * rowCount * 6);
} }
@ -568,9 +540,9 @@ void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void threadFunction() void threadFunction()
{ {
const unsigned int rowBlockSize = (resolutionY / blockCount) + 1; const unsigned int rowBlockSize = (resolution.y / blockCount) + 1;
std::vector<sf::Vertex> vertices(resolutionX * rowBlockSize * 6); std::vector<sf::Vertex> vertices(resolution.x * rowBlockSize * 6);
WorkItem workItem = {nullptr, 0}; WorkItem workItem = {nullptr, 0};