diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index bad3acb4..6c5a9ee5 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -336,9 +336,16 @@ void EditorUI::renderBrushPanel(EditorApp& app) { if (ImGui::Button("Apply Noise", ImVec2(-1, 0))) { app.getTerrainEditor().applyNoise(noiseFreq, noiseAmp, noiseOctaves, static_cast(noiseSeed)); + app.showToast("Noise applied"); + } + static int smoothPasses = 2; + ImGui::SliderInt("Smooth Passes", &smoothPasses, 1, 10); + if (ImGui::Button("Smooth Entire Tile", ImVec2(-1, 0))) { + app.getTerrainEditor().smoothEntireTile(smoothPasses); + app.showToast("Tile smoothed"); } ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), - "Adds procedural hills/valleys to entire tile"); + "Generate terrain, then smooth for natural look"); } ImGui::Separator(); @@ -466,6 +473,10 @@ void EditorUI::renderObjectPanel(EditorApp& app) { bool randRot = placer.getRandomRotation(); if (ImGui::Checkbox("Random Rotation", &randRot)) placer.setRandomRotation(randRot); + ImGui::SameLine(); + bool snap = placer.getSnapToGround(); + if (ImGui::Checkbox("Snap Ground", &snap)) + placer.setSnapToGround(snap); float scale = placer.getPlacementScale(); if (ImGui::SliderFloat("Scale", &scale, 0.1f, 10.0f, "%.2f")) placer.setPlacementScale(scale); diff --git a/tools/editor/object_placer.hpp b/tools/editor/object_placer.hpp index af46b761..7c04c6a0 100644 --- a/tools/editor/object_placer.hpp +++ b/tools/editor/object_placer.hpp @@ -58,6 +58,8 @@ public: void setPlacementScale(float s) { placementScale_ = s; } bool getRandomRotation() const { return randomRotation_; } void setRandomRotation(bool v) { randomRotation_ = v; } + bool getSnapToGround() const { return snapToGround_; } + void setSnapToGround(bool v) { snapToGround_ = v; } // Undo last placement bool canUndoPlace() const { return !undoStack_.empty(); } @@ -81,6 +83,7 @@ private: float placementRotY_ = 0.0f; float placementScale_ = 1.0f; bool randomRotation_ = false; + bool snapToGround_ = true; }; } // namespace editor diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 0c910b13..05b17092 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -588,6 +588,70 @@ void TerrainEditor::removeWater(const glm::vec3& center, float radius) { } } +void TerrainEditor::smoothEntireTile(int iterations) { + if (!terrain_) return; + + for (int iter = 0; iter < iterations; iter++) { + // Snapshot all heights + std::array, 256> snap; + for (int ci = 0; ci < 256; ci++) + for (int v = 0; v < 145; v++) + snap[ci][v] = terrain_->chunks[ci].heightMap.heights[v]; + + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap()) continue; + int cx = ci % 16, cy = ci / 16; + + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col > 8) continue; // smooth outer vertices only + + float sum = snap[ci][v]; + int count = 1; + + // Same-chunk neighbors + if (col > 0) { sum += snap[ci][row * 17 + col - 1]; count++; } + if (col < 8) { sum += snap[ci][row * 17 + col + 1]; count++; } + if (row > 0) { sum += snap[ci][(row - 1) * 17 + col]; count++; } + if (row < 8) { sum += snap[ci][(row + 1) * 17 + col]; count++; } + + // Cross-chunk neighbors at edges + if (col == 0 && cx > 0) { sum += snap[cy * 16 + cx - 1][row * 17 + 8]; count++; } + if (col == 8 && cx < 15) { sum += snap[cy * 16 + cx + 1][row * 17 + 0]; count++; } + if (row == 0 && cy > 0) { sum += snap[(cy - 1) * 16 + cx][8 * 17 + col]; count++; } + if (row == 8 && cy < 15) { sum += snap[(cy + 1) * 16 + cx][0 * 17 + col]; count++; } + + chunk.heightMap.heights[v] = sum / static_cast(count); + } + + // Update inner vertices from smoothed outer vertices + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col <= 8) continue; + int innerCol = col - 9; + // Average of 4 surrounding outer vertices + int tl = row * 17 + innerCol; + int tr = row * 17 + innerCol + 1; + int bl = (row + 1) * 17 + innerCol; + int br = (row + 1) * 17 + innerCol + 1; + if (tl < 145 && tr < 145 && bl < 145 && br < 145) + chunk.heightMap.heights[v] = (chunk.heightMap.heights[tl] + + chunk.heightMap.heights[tr] + chunk.heightMap.heights[bl] + + chunk.heightMap.heights[br]) * 0.25f; + } + } + + // Stitch all edges + for (int ci = 0; ci < 256; ci++) + stitchEdges(ci); + } + + for (int ci = 0; ci < 256; ci++) + dirtyChunks_.push_back(ci); + dirty_ = true; +} + void TerrainEditor::applyErode(float dt) { float factor = std::min(1.0f, brush_.settings().strength * dt * 0.3f); diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 48cec874..b3b5dfbb 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -54,6 +54,9 @@ public: // Noise generator: applies procedural height noise to the terrain void applyNoise(float frequency, float amplitude, int octaves, uint32_t seed); + // Global smooth pass across entire tile (N iterations) + void smoothEntireTile(int iterations); + // Import/export heightmap (raw 16-bit grayscale, 129x129) bool importHeightmap(const std::string& path, float heightScale); bool exportHeightmap(const std::string& path, float heightScale);