From 89312120f4b3ab97892d1099a823a5182d821cf4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 04:52:36 -0700 Subject: [PATCH] feat(editor): heightmap export, help overlay, keyboard reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Export Heightmap: File > Export Heightmap saves terrain as 16-bit RAW grayscale (129x129) for use in external terrain editors or as a backup. Configurable max height scale. - Help overlay (F1 or Help menu): lists all keyboard shortcuts organized by category (navigation, editing, object transform, view) - Round-trip heightmap workflow: import → edit → export --- tools/editor/editor_app.cpp | 1 + tools/editor/editor_ui.cpp | 53 +++++++++++++++++++++++++++++++++ tools/editor/editor_ui.hpp | 1 + tools/editor/terrain_editor.cpp | 28 +++++++++++++++++ tools/editor/terrain_editor.hpp | 3 +- 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index b7163630..53d7c69d 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -205,6 +205,7 @@ void EditorApp::processEvents() { auto sc = event.key.keysym.scancode; if (sc == SDL_SCANCODE_F3) setWireframe(!isWireframe()); if (sc == SDL_SCANCODE_F5) saveBookmark(""); + // F1 handled by UI (showHelp_ toggle) // Transform shortcuts (Blender-style) if (objectPlacer_.getSelected()) { if (sc == SDL_SCANCODE_G) startGizmoMode(TransformMode::Move); diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 49dc937a..ded58532 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -120,6 +120,20 @@ void EditorUI::renderMenuBar(EditorApp& app) { ImGui::EndMenu(); } ImGui::Separator(); + if (ImGui::BeginMenu("Export Heightmap", app.hasTerrainLoaded())) { + static char expHmPath[256] = "output/heightmap.raw"; + static float expHmScale = 500.0f; + ImGui::InputText("File##exphm", expHmPath, sizeof(expHmPath)); + ImGui::SliderFloat("Max Height##exphm", &expHmScale, 50.0f, 2000.0f); + if (ImGui::MenuItem("Export 16-bit RAW (129x129)")) { + if (app.getTerrainEditor().exportHeightmap(expHmPath, expHmScale)) + app.showToast("Heightmap exported"); + else + app.showToast("Export failed"); + } + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("Quit", "Alt+F4")) app.requestQuit(); ImGui::EndMenu(); } @@ -152,8 +166,47 @@ void EditorUI::renderMenuBar(EditorApp& app) { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("Help")) { + if (ImGui::MenuItem("Keyboard Shortcuts", "F1")) showHelp_ = !showHelp_; + ImGui::EndMenu(); + } ImGui::EndMainMenuBar(); } + + // Help overlay + if (showHelp_) { + ImGui::SetNextWindowSize(ImVec2(400, 350), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Keyboard Shortcuts", &showHelp_)) { + ImGui::Text("Navigation:"); + ImGui::BulletText("WASD — fly camera"); + ImGui::BulletText("Q/E — descend/ascend"); + ImGui::BulletText("Right-drag — look around"); + ImGui::BulletText("Scroll — adjust camera speed"); + ImGui::BulletText("Shift — sprint"); + ImGui::Separator(); + ImGui::Text("Editing:"); + ImGui::BulletText("Left-click — paint/place (depends on mode)"); + ImGui::BulletText("Ctrl+click — select object/NPC"); + ImGui::BulletText("Ctrl+S — quick save"); + ImGui::BulletText("Ctrl+Z — undo"); + ImGui::BulletText("Ctrl+Shift+Z — redo"); + ImGui::BulletText("Delete — remove selected"); + ImGui::Separator(); + ImGui::Text("Object Transform:"); + ImGui::BulletText("G — move mode (then drag)"); + ImGui::BulletText("R — rotate mode (then drag)"); + ImGui::BulletText("T — scale mode (then drag)"); + ImGui::BulletText("X/Y — constrain to axis"); + ImGui::BulletText("Escape — deselect / cancel"); + ImGui::BulletText("Right-click — context menu"); + ImGui::Separator(); + ImGui::Text("View:"); + ImGui::BulletText("F1 — this help"); + ImGui::BulletText("F3 — wireframe toggle"); + ImGui::BulletText("F5 — save camera bookmark"); + } + ImGui::End(); + } } void EditorUI::renderToolbar(EditorApp& app) { diff --git a/tools/editor/editor_ui.hpp b/tools/editor/editor_ui.hpp index 92c371ab..b97578bf 100644 --- a/tools/editor/editor_ui.hpp +++ b/tools/editor/editor_ui.hpp @@ -39,6 +39,7 @@ private: bool showNewDialog_ = false; bool showLoadDialog_ = false; bool showSaveDialog_ = false; + bool showHelp_ = false; char newMapNameBuf_[256] = "CustomZone"; int newTileX_ = 32; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index d9dd2aa0..0c910b13 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -742,6 +742,34 @@ bool TerrainEditor::importHeightmap(const std::string& path, float heightScale) return true; } +bool TerrainEditor::exportHeightmap(const std::string& path, float heightScale) { + if (!terrain_) return false; + constexpr int res = 129; + std::vector data(res * res, 0); + + for (int cy = 0; cy < 16; cy++) { + for (int cx = 0; cx < 16; cx++) { + auto& chunk = terrain_->chunks[cy * 16 + cx]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col > 8) continue; // outer vertices only for 129x129 + int px = cx * 8 + col; + int py = cy * 8 + row; + if (px >= res || py >= res) continue; + float h = (chunk.position[2] + chunk.heightMap.heights[v]) / heightScale; + h = std::clamp(h, 0.0f, 1.0f); + data[py * res + px] = static_cast(h * 65535.0f); + } + } + } + + std::ofstream f(path, std::ios::binary); + if (!f) return false; + f.write(reinterpret_cast(data.data()), data.size() * 2); + return true; +} + void TerrainEditor::punchHole(const glm::vec3& center, float radius) { if (!terrain_) return; auto affected = getAffectedChunks(center, radius); diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index c65d8894..48cec874 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -54,8 +54,9 @@ public: // Noise generator: applies procedural height noise to the terrain void applyNoise(float frequency, float amplitude, int octaves, uint32_t seed); - // Import heightmap from raw 16-bit grayscale (129x129 or 257x257) + // Import/export heightmap (raw 16-bit grayscale, 129x129) bool importHeightmap(const std::string& path, float heightScale); + bool exportHeightmap(const std::string& path, float heightScale); // Water editing void setWaterLevel(const glm::vec3& center, float radius, float waterHeight, uint16_t liquidType = 0);