diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index ffdcf6f4..8537303b 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -469,6 +469,9 @@ void EditorApp::refreshDirtyChunks() { auto dirty = terrainEditor_.consumeDirtyChunks(); if (dirty.empty()) return; + // Recalculate normals for modified chunks (better lighting) + terrainEditor_.recalcNormals(dirty); + // Regenerate full mesh and reload terrain auto mesh = terrainEditor_.regenerateMesh(); viewport_.clearTerrain(); diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index 71360251..163f7678 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -58,7 +58,13 @@ public: core::Window* getWindow() { return window_.get(); } EditorMode getMode() const { return mode_; } - void setMode(EditorMode m) { mode_ = m; } + void setMode(EditorMode m) { + if (m != mode_) { + viewport_.clearGhostPreview(); + viewport_.setBrushIndicator({}, 0, false); + } + mode_ = m; + } void markObjectsDirty() { objectsDirty_ = true; } void startGizmoMode(TransformMode mode); diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 5903508c..52295a29 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -68,6 +68,11 @@ void EditorUI::renderMenuBar(EditorApp& app) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New Terrain...", "Ctrl+N")) showNewDialog_ = true; if (ImGui::MenuItem("Load ADT...", "Ctrl+O")) showLoadDialog_ = true; + if (ImGui::MenuItem("Clear All", nullptr, false, app.hasTerrainLoaded())) { + app.getTerrainEditor().history().clear(); + app.getObjectPlacer().clearSelection(); + app.getNpcSpawner().clearSelection(); + } ImGui::Separator(); if (ImGui::MenuItem("Quick Save", "Ctrl+S", false, app.hasTerrainLoaded())) app.quickSave(); diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 0d8f74a6..00aee8d0 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -478,6 +478,58 @@ void TerrainEditor::undo() { } } +void TerrainEditor::recalcNormals(const std::vector& chunkIndices) { + if (!terrain_) return; + float unitSize = CHUNK_SIZE / 8.0f; + + for (int ci : chunkIndices) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap()) continue; + + for (int i = 0; i < 145; i++) { + int row = i / 17; + int col = i % 17; + + // Get heights of neighbors + float hC = chunk.heightMap.heights[i]; + float hL = hC, hR = hC, hU = hC, hD = hC; + + if (col <= 8) { + // Outer vertex + int li = row * 17 + std::max(0, col - 1); + int ri = row * 17 + std::min(8, col + 1); + int ui = std::max(0, row - 1) * 17 + col; + int di = std::min(8, row + 1) * 17 + col; + hL = chunk.heightMap.heights[li]; + hR = chunk.heightMap.heights[ri]; + hU = chunk.heightMap.heights[ui]; + hD = chunk.heightMap.heights[di]; + } else { + // Inner vertex — use adjacent outer verts + int innerCol = col - 9; + 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 >= 0 && tl < 145) hU = chunk.heightMap.heights[tl]; + if (tr >= 0 && tr < 145) hR = chunk.heightMap.heights[tr]; + if (bl >= 0 && bl < 145) hD = chunk.heightMap.heights[bl]; + if (br >= 0 && br < 145) hL = chunk.heightMap.heights[br]; + } + + // Compute normal from height differences + float dx = (hL - hR) / (2.0f * unitSize); + float dy = (hU - hD) / (2.0f * unitSize); + float len = std::sqrt(dx * dx + dy * dy + 1.0f); + glm::vec3 n(dx / len, dy / len, 1.0f / len); + + chunk.normals[i * 3 + 0] = static_cast(n.x * 127.0f); + chunk.normals[i * 3 + 1] = static_cast(n.y * 127.0f); + chunk.normals[i * 3 + 2] = static_cast(n.z * 127.0f); + } + } +} + void TerrainEditor::redo() { if (!terrain_) return; history_.redo(*terrain_); diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 05cf4c37..d0796e7d 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -48,6 +48,9 @@ public: void undo(); void redo(); + // Recalculate normals for modified chunks (improves lighting after sculpt) + void recalcNormals(const std::vector& chunkIndices); + // Water editing void setWaterLevel(const glm::vec3& center, float radius, float waterHeight, uint16_t liquidType = 0); void removeWater(const glm::vec3& center, float radius);