From 77a91de9f1eb12a6552a3ba1edb1e26a2f747923 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 11:14:58 -0700 Subject: [PATCH] feat(editor): heightmap preview PNG export for zone documentation - Exports 129x129 grayscale PNG showing terrain elevation - Auto-normalizes to 0-255 based on actual height range - Useful for zone documentation, thumbnails, and previews - Auto-exported alongside WOT/WHM/normals on every save --- tools/editor/editor_app.cpp | 1 + tools/editor/wowee_terrain.cpp | 38 ++++++++++++++++++++++++++++++++++ tools/editor/wowee_terrain.hpp | 4 ++++ 3 files changed, 43 insertions(+) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 276079be..545d34bd 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -793,6 +793,7 @@ void EditorApp::exportZone(const std::string& outputDir) { WoweeTerrain::exportOpen(terrain_, openBase, loadedTileX_, loadedTileY_); WoweeTerrain::exportNormalMap(terrain_, openBase + "_normals.png"); WoweeTerrain::exportAlphaMaps(terrain_, base + "/alphamaps"); + WoweeTerrain::exportHeightmapPreview(terrain_, openBase + "_heightmap.png"); // Write zone info README { diff --git a/tools/editor/wowee_terrain.cpp b/tools/editor/wowee_terrain.cpp index 445d568b..882234ba 100644 --- a/tools/editor/wowee_terrain.cpp +++ b/tools/editor/wowee_terrain.cpp @@ -127,6 +127,44 @@ bool WoweeTerrain::exportNormalMap(const pipeline::ADTTerrain& terrain, return true; } +bool WoweeTerrain::exportHeightmapPreview(const pipeline::ADTTerrain& terrain, + const std::string& path) { + constexpr int res = 129; + std::vector pixels(res * res); + + float minH = 1e30f, maxH = -1e30f; + for (int ci = 0; ci < 256; ci++) { + const auto& chunk = terrain.chunks[ci]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + float h = chunk.position[2] + chunk.heightMap.heights[v]; + minH = std::min(minH, h); + maxH = std::max(maxH, h); + } + } + float range = std::max(1.0f, maxH - minH); + + for (int cy = 0; cy < 16; cy++) { + for (int cx = 0; cx < 16; cx++) { + const 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; + int px = cx * 8 + col, py = cy * 8 + row; + if (px >= res || py >= res) continue; + float h = chunk.position[2] + chunk.heightMap.heights[v]; + float t = (h - minH) / range; + pixels[py * res + px] = static_cast(t * 255.0f); + } + } + } + + std::filesystem::create_directories(std::filesystem::path(path).parent_path()); + stbi_write_png(path.c_str(), res, res, 1, pixels.data(), res); + return true; +} + int WoweeTerrain::exportAlphaMaps(const pipeline::ADTTerrain& terrain, const std::string& outputDir) { namespace fs = std::filesystem; diff --git a/tools/editor/wowee_terrain.hpp b/tools/editor/wowee_terrain.hpp index 825a4982..695a4bc8 100644 --- a/tools/editor/wowee_terrain.hpp +++ b/tools/editor/wowee_terrain.hpp @@ -22,6 +22,10 @@ public: static int exportAlphaMaps(const pipeline::ADTTerrain& terrain, const std::string& outputDir); + // Export heightmap as grayscale PNG preview (129x129) + static bool exportHeightmapPreview(const pipeline::ADTTerrain& terrain, + const std::string& path); + // Import terrain from open format back to ADTTerrain static bool importOpen(const std::string& basePath, pipeline::ADTTerrain& terrain); };