feat(editor): sand dune generator for desert terrain

- Dune Generator: creates rolling sand dune patterns with primary
  and secondary sine waves plus hash-based variation
- Configurable wavelength, amplitude, wind direction, and seed
- Directional waves create realistic parallel dune ridges
- Secondary wave adds natural irregularity at 2.3x wavelength
- Perpendicular variation breaks up uniform dune lines
- Pair with Desert biome paint for instant Tanaris-style zones
This commit is contained in:
Kelsi 2026-05-05 08:47:44 -07:00
parent 8255cda9a8
commit 79db38219f
3 changed files with 49 additions and 0 deletions

View file

@ -709,6 +709,22 @@ void EditorUI::renderBrushPanel(EditorApp& app) {
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "Quantizes heights into flat shelves");
}
if (ImGui::CollapsingHeader("Dune Generator")) {
static float duneWave = 30.0f, duneAmp = 8.0f, duneDir = 45.0f;
static int duneSeed = 10;
ImGui::SliderFloat("Wavelength##dune", &duneWave, 10.0f, 100.0f);
ImGui::SliderFloat("Amplitude##dune", &duneAmp, 2.0f, 30.0f);
ImGui::SliderFloat("Direction##dune", &duneDir, 0.0f, 360.0f, "%.0f deg");
ImGui::InputInt("Seed##dune", &duneSeed);
if (ImGui::Button("Create Dunes", ImVec2(-1, 0))) {
app.getTerrainEditor().createDunes(duneWave, duneAmp, duneDir,
static_cast<uint32_t>(duneSeed));
app.showToast("Dunes created");
}
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1),
"Rolling sand dune pattern. Paint desert texture after.");
}
if (ImGui::CollapsingHeader("Canyon Generator")) {
static float canyonWidth = 15.0f, canyonDepth = 20.0f;
static int canyonSeed = 1;

View file

@ -900,6 +900,36 @@ void TerrainEditor::applyVoronoiNoise(int cellCount, float amplitude, uint32_t s
dirty_ = true;
}
void TerrainEditor::createDunes(float wavelength, float amplitude, float direction, uint32_t seed) {
if (!terrain_) return;
float dirRad = direction * 3.14159f / 180.0f;
float dx = std::cos(dirRad), dy = std::sin(dirRad);
// Secondary wave for variation
auto hash = [](int x, uint32_t s) -> float {
uint32_t h = static_cast<uint32_t>(x * 374761393 + s * 668265263);
h = (h ^ (h >> 13)) * 1274126177;
return (static_cast<float>(h & 0xFFFF) / 65535.0f - 0.5f) * 2.0f;
};
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap()) continue;
for (int v = 0; v < 145; v++) {
glm::vec3 pos = chunkVertexWorldPos(ci, v);
float proj = pos.x * dx + pos.y * dy;
float wave = std::sin(proj / wavelength * 6.2832f) * amplitude;
float secondary = std::sin(proj / (wavelength * 2.3f) * 6.2832f + seed * 0.1f) * amplitude * 0.3f;
float perp = pos.x * dy - pos.y * dx;
float variation = hash(static_cast<int>(perp * 0.1f), seed) * amplitude * 0.15f;
chunk.heightMap.heights[v] += wave + secondary + variation;
}
dirtyChunks_.push_back(ci);
}
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
dirty_ = true;
}
void TerrainEditor::rotateTerrain90() {
if (!terrain_) return;
// Snapshot all outer vertex heights into a 129x129 grid

View file

@ -129,6 +129,9 @@ public:
// Rotate terrain 90 degrees clockwise
void rotateTerrain90();
// Create rolling sand dune pattern
void createDunes(float wavelength, float amplitude, float direction, uint32_t seed);
// Import/export heightmap (raw 16-bit grayscale, 129x129)
bool importHeightmap(const std::string& path, float heightScale);
bool exportHeightmap(const std::string& path, float heightScale);