From 79db38219f3d71236a49f75a54833e4bcc531fe6 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 08:47:44 -0700 Subject: [PATCH] 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 --- tools/editor/editor_ui.cpp | 16 ++++++++++++++++ tools/editor/terrain_editor.cpp | 30 ++++++++++++++++++++++++++++++ tools/editor/terrain_editor.hpp | 3 +++ 3 files changed, 49 insertions(+) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index d20c99ac..d54378bc 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -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(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; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 94baedb9..3a692440 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -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(x * 374761393 + s * 668265263); + h = (h ^ (h >> 13)) * 1274126177; + return (static_cast(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(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 diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 436ec910..5411b8bb 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -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);