From 4e2f7041249078725144710374f91076f78394aa Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 12:58:11 -0700 Subject: [PATCH] fix(editor): undo now covers texture painting, fix stale buffer bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extend undo/redo to snapshot alpha maps and texture layers alongside heights — texture painting operations are now fully undoable - Bracket paint mode with beginStroke/endStroke like sculpt mode - Fix stale static char buffer in quest objective loop (showed wrong objective's description when editing multiple objectives) - Zero-initialize all quest UI text buffers for null termination safety - Fix unused variable warnings in terrain_editor.cpp --- tools/editor/editor_app.cpp | 4 ++-- tools/editor/editor_history.cpp | 42 +++++++++++++++++++++++---------- tools/editor/editor_history.hpp | 6 +++++ tools/editor/editor_ui.cpp | 10 ++++---- tools/editor/terrain_editor.cpp | 4 +--- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 9d58a6e3..08ef1926 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -400,12 +400,12 @@ void EditorApp::processEvents() { } } else { painting_ = true; - if (mode_ == EditorMode::Sculpt) + if (mode_ == EditorMode::Sculpt || mode_ == EditorMode::Paint) terrainEditor_.beginStroke(); } } else if (event.type == SDL_MOUSEBUTTONUP) { painting_ = false; - if (mode_ == EditorMode::Sculpt) + if (mode_ == EditorMode::Sculpt || mode_ == EditorMode::Paint) terrainEditor_.endStroke(); } } diff --git a/tools/editor/editor_history.cpp b/tools/editor/editor_history.cpp index 52821d85..18636f74 100644 --- a/tools/editor/editor_history.cpp +++ b/tools/editor/editor_history.cpp @@ -3,15 +3,37 @@ namespace wowee { namespace editor { +ChunkSnapshot EditorHistory::captureChunk(const pipeline::ADTTerrain& terrain, int idx) { + ChunkSnapshot snap; + snap.chunkIndex = idx; + snap.heights = terrain.chunks[idx].heightMap.heights; + snap.alphaMap = terrain.chunks[idx].alphaMap; + snap.layers = terrain.chunks[idx].layers; + return snap; +} + +void EditorHistory::restoreChunk(pipeline::ADTTerrain& terrain, const ChunkSnapshot& snap) { + terrain.chunks[snap.chunkIndex].heightMap.heights = snap.heights; + terrain.chunks[snap.chunkIndex].alphaMap = snap.alphaMap; + terrain.chunks[snap.chunkIndex].layers = snap.layers; +} + +bool EditorHistory::snapshotChanged(const ChunkSnapshot& a, const ChunkSnapshot& b) { + if (a.heights != b.heights) return true; + if (a.alphaMap != b.alphaMap) return true; + if (a.layers.size() != b.layers.size()) return true; + for (size_t i = 0; i < a.layers.size(); i++) { + if (a.layers[i].textureId != b.layers[i].textureId) return true; + } + return false; +} + void EditorHistory::beginEdit(const pipeline::ADTTerrain& terrain, const std::vector& affectedChunks) { pending_ = {}; pending_.before.reserve(affectedChunks.size()); for (int idx : affectedChunks) { - ChunkSnapshot snap; - snap.chunkIndex = idx; - snap.heights = terrain.chunks[idx].heightMap.heights; - pending_.before.push_back(snap); + pending_.before.push_back(captureChunk(terrain, idx)); } } @@ -19,17 +41,13 @@ void EditorHistory::endEdit(const pipeline::ADTTerrain& terrain) { pending_.after.reserve(pending_.before.size()); lastAffected_.clear(); for (const auto& snap : pending_.before) { - ChunkSnapshot after; - after.chunkIndex = snap.chunkIndex; - after.heights = terrain.chunks[snap.chunkIndex].heightMap.heights; - pending_.after.push_back(after); + pending_.after.push_back(captureChunk(terrain, snap.chunkIndex)); lastAffected_.push_back(snap.chunkIndex); } - // Only push if something actually changed bool changed = false; for (size_t i = 0; i < pending_.before.size(); i++) { - if (pending_.before[i].heights != pending_.after[i].heights) { + if (snapshotChanged(pending_.before[i], pending_.after[i])) { changed = true; break; } @@ -51,7 +69,7 @@ void EditorHistory::undo(pipeline::ADTTerrain& terrain) { lastAffected_.clear(); for (const auto& snap : cmd.before) { - terrain.chunks[snap.chunkIndex].heightMap.heights = snap.heights; + restoreChunk(terrain, snap); lastAffected_.push_back(snap.chunkIndex); } @@ -66,7 +84,7 @@ void EditorHistory::redo(pipeline::ADTTerrain& terrain) { lastAffected_.clear(); for (const auto& snap : cmd.after) { - terrain.chunks[snap.chunkIndex].heightMap.heights = snap.heights; + restoreChunk(terrain, snap); lastAffected_.push_back(snap.chunkIndex); } diff --git a/tools/editor/editor_history.hpp b/tools/editor/editor_history.hpp index d96f9518..fd18fe3e 100644 --- a/tools/editor/editor_history.hpp +++ b/tools/editor/editor_history.hpp @@ -11,6 +11,8 @@ namespace editor { struct ChunkSnapshot { int chunkIndex; std::array heights; + std::vector alphaMap; + std::vector layers; }; struct EditCommand { @@ -37,6 +39,10 @@ public: const std::vector& lastAffectedChunks() const { return lastAffected_; } private: + static ChunkSnapshot captureChunk(const pipeline::ADTTerrain& terrain, int idx); + static void restoreChunk(pipeline::ADTTerrain& terrain, const ChunkSnapshot& snap); + static bool snapshotChanged(const ChunkSnapshot& a, const ChunkSnapshot& b); + std::vector undoStack_; std::vector redoStack_; EditCommand pending_; diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 94a0a747..b8366633 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -1706,12 +1706,12 @@ void EditorUI::renderQuestPanel(EditorApp& app) { auto& tmpl = qe.getTemplate(); if (ImGui::CollapsingHeader("New Quest", ImGuiTreeNodeFlags_DefaultOpen)) { - static char titleBuf[128] = "New Quest"; + char titleBuf[128] = {}; std::strncpy(titleBuf, tmpl.title.c_str(), sizeof(titleBuf) - 1); if (ImGui::InputText("Title##q", titleBuf, sizeof(titleBuf))) tmpl.title = titleBuf; - static char descBuf[512] = ""; + char descBuf[512] = {}; std::strncpy(descBuf, tmpl.description.c_str(), sizeof(descBuf) - 1); if (ImGui::InputTextMultiline("Description##q", descBuf, sizeof(descBuf), ImVec2(-1, 60))) tmpl.description = descBuf; @@ -1756,7 +1756,7 @@ void EditorUI::renderQuestPanel(EditorApp& app) { int ti = static_cast(obj.type); ImGui::Combo("Type", &ti, types, 6); obj.type = static_cast(ti); - static char objDesc[128]; + char objDesc[128] = {}; std::strncpy(objDesc, obj.description.c_str(), sizeof(objDesc) - 1); if (ImGui::InputText("Desc", objDesc, sizeof(objDesc))) obj.description = objDesc; int cnt = obj.targetCount; @@ -1786,7 +1786,7 @@ void EditorUI::renderQuestPanel(EditorApp& app) { ImGui::EndCombo(); } - static char completeBuf[256] = ""; + char completeBuf[256] = {}; std::strncpy(completeBuf, tmpl.completionText.c_str(), sizeof(completeBuf) - 1); if (ImGui::InputTextMultiline("Completion Text##q", completeBuf, sizeof(completeBuf), ImVec2(-1, 40))) tmpl.completionText = completeBuf; @@ -1816,7 +1816,7 @@ void EditorUI::renderQuestPanel(EditorApp& app) { if (selectedQuest >= 0 && selectedQuest < static_cast(qe.questCount())) { auto* sq = qe.getQuest(selectedQuest); ImGui::TextColored(ImVec4(1, 0.8f, 0.3f, 1), "Editing: [%u] %s", sq->id, sq->title.c_str()); - static char etBuf[128]; + char etBuf[128] = {}; std::strncpy(etBuf, sq->title.c_str(), sizeof(etBuf) - 1); if (ImGui::InputText("Title##edit", etBuf, sizeof(etBuf))) sq->title = etBuf; int elv = sq->requiredLevel; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 0f488cab..a64d3f6b 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -1445,7 +1445,7 @@ void TerrainEditor::applyErode(float dt) { if (influence <= 0.0f) continue; float h = chunk.heightMap.heights[v]; - int row = v / 17, col = v % 17; + int col = v % 17; // Find lowest neighbor (same chunk) float lowestH = h; @@ -1505,8 +1505,6 @@ void TerrainEditor::applyNoise(float frequency, float amplitude, int octaves, ui 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++) { glm::vec3 wpos = chunkVertexWorldPos(ci, v);