mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): stamp persistence, WCP file preview, enriched stats
- Terrain stamps save/load to JSON: reuse terrain features across zones and sessions. Save/Load buttons in Sculpt > Stamp/Clone panel - WCP Inspect now shows full file breakdown: terrain/model/building/ texture/data counts with total size. Powered by readInfo file list parsing with auto-categorization by extension - stats.json now includes chunk count, triangle count, tile count, and editor version alongside existing object/NPC/quest/texture counts - Fix unprotected std::stoi in custom zone WOT filename parser
This commit is contained in:
parent
d3e8f999c7
commit
7473728360
5 changed files with 117 additions and 10 deletions
|
|
@ -159,6 +159,26 @@ bool ContentPacker::readInfo(const std::string& wcpPath, ContentPackInfo& info)
|
|||
info.version = j.value("version", "");
|
||||
info.format = j.value("format", "");
|
||||
info.mapId = j.value("mapId", 9000u);
|
||||
info.files.clear();
|
||||
if (j.contains("files") && j["files"].is_array()) {
|
||||
for (const auto& jf : j["files"]) {
|
||||
ContentPackInfo::FileEntry fe;
|
||||
fe.path = jf.value("path", "");
|
||||
fe.size = jf.value("size", 0ULL);
|
||||
auto dot = fe.path.rfind('.');
|
||||
if (dot != std::string::npos) {
|
||||
std::string ext = fe.path.substr(dot);
|
||||
if (ext == ".wot" || ext == ".whm") fe.category = "terrain";
|
||||
else if (ext == ".wom") fe.category = "model";
|
||||
else if (ext == ".wob") fe.category = "building";
|
||||
else if (ext == ".png") fe.category = "texture";
|
||||
else if (ext == ".json") fe.category = "data";
|
||||
else if (ext == ".adt" || ext == ".wdt") fe.category = "legacy";
|
||||
else fe.category = "other";
|
||||
}
|
||||
info.files.push_back(fe);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1040,6 +1040,13 @@ void EditorApp::exportZone(const std::string& outputDir) {
|
|||
sj["textures"] = usedTextures.size();
|
||||
sj["openFormatScore"] = score;
|
||||
sj["formats"] = validation.summary();
|
||||
sj["tiles"] = static_cast<int>(manifest.tiles.size());
|
||||
auto* tr = viewport_.getTerrainRenderer();
|
||||
if (tr) {
|
||||
sj["chunks"] = tr->getChunkCount();
|
||||
sj["triangles"] = tr->getTriangleCount();
|
||||
}
|
||||
sj["editorVersion"] = "1.0.0";
|
||||
std::ofstream stats(base + "/stats.json");
|
||||
if (stats) stats << sj.dump(2) << "\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -236,8 +236,11 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
auto lastU = base.rfind('_');
|
||||
auto prevU = base.rfind('_', lastU - 1);
|
||||
if (lastU != std::string::npos && prevU != std::string::npos) {
|
||||
int tx = std::stoi(base.substr(prevU + 1, lastU - prevU - 1));
|
||||
int ty = std::stoi(base.substr(lastU + 1));
|
||||
int tx = 0, ty = 0;
|
||||
try {
|
||||
tx = std::stoi(base.substr(prevU + 1, lastU - prevU - 1));
|
||||
ty = std::stoi(base.substr(lastU + 1));
|
||||
} catch (...) { break; }
|
||||
app.createNewTerrain(z.name, tx, ty, 100.0f, Biome::Grassland);
|
||||
// Load the WOT/WHM data
|
||||
std::string wotBase = entry.path().parent_path().string() + "/" + base;
|
||||
|
|
@ -289,16 +292,37 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
app.showToast("Import failed — check path");
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("Inspect Pack Info")) {
|
||||
if (ImGui::BeginMenu("Inspect Pack Info")) {
|
||||
editor::ContentPackInfo info;
|
||||
if (editor::ContentPacker::readInfo(wcpImportPath, info)) {
|
||||
std::string msg = info.name + " v" + info.version;
|
||||
if (!info.author.empty()) msg += " by " + info.author;
|
||||
msg += " (" + info.format + ")";
|
||||
app.showToast(msg);
|
||||
ImGui::TextColored(ImVec4(1, 0.9f, 0.3f, 1), "%s v%s",
|
||||
info.name.c_str(), info.version.c_str());
|
||||
if (!info.author.empty())
|
||||
ImGui::Text("By: %s", info.author.c_str());
|
||||
if (!info.description.empty())
|
||||
ImGui::TextWrapped("%s", info.description.c_str());
|
||||
ImGui::Text("Map ID: %u, Files: %zu", info.mapId, info.files.size());
|
||||
if (!info.files.empty()) {
|
||||
ImGui::Separator();
|
||||
int terrain = 0, models = 0, buildings = 0, textures = 0, data = 0;
|
||||
uint64_t totalSize = 0;
|
||||
for (const auto& fe : info.files) {
|
||||
totalSize += fe.size;
|
||||
if (fe.category == "terrain") terrain++;
|
||||
else if (fe.category == "model") models++;
|
||||
else if (fe.category == "building") buildings++;
|
||||
else if (fe.category == "texture") textures++;
|
||||
else if (fe.category == "data") data++;
|
||||
}
|
||||
ImGui::Text("Terrain: %d Models: %d Buildings: %d",
|
||||
terrain, models, buildings);
|
||||
ImGui::Text("Textures: %d Data: %d Total: %.1f KB",
|
||||
textures, data, totalSize / 1024.0f);
|
||||
}
|
||||
} else {
|
||||
app.showToast("Cannot read pack — check path");
|
||||
ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "Cannot read pack");
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
|
@ -959,10 +983,18 @@ void EditorUI::renderBrushPanel(EditorApp& app) {
|
|||
if (ImGui::Button("Paste Stamp", ImVec2(120, 0)) &&
|
||||
app.getTerrainEditor().hasStamp())
|
||||
app.getTerrainEditor().pasteStamp(brush2.getPosition());
|
||||
if (app.getTerrainEditor().hasStamp())
|
||||
if (app.getTerrainEditor().hasStamp()) {
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.9f, 0.5f, 1), "Stamp ready");
|
||||
else
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Save##stamp"))
|
||||
if (app.getTerrainEditor().saveStamp("output/stamps/terrain_stamp.json"))
|
||||
app.showToast("Stamp saved");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "No stamp copied");
|
||||
}
|
||||
if (ImGui::SmallButton("Load##stamp"))
|
||||
if (app.getTerrainEditor().loadStamp("output/stamps/terrain_stamp.json"))
|
||||
app.showToast("Stamp loaded");
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Detail Noise")) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#include "terrain_editor.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
|
|
@ -1486,6 +1488,50 @@ void TerrainEditor::pasteStamp(const glm::vec3& center) {
|
|||
LOG_INFO("Stamp pasted at (", center.x, ",", center.y, ")");
|
||||
}
|
||||
|
||||
bool TerrainEditor::saveStamp(const std::string& path) const {
|
||||
if (stampData_.empty()) return false;
|
||||
nlohmann::json j;
|
||||
j["format"] = "wowee-stamp-1.0";
|
||||
j["vertexCount"] = stampData_.size();
|
||||
nlohmann::json verts = nlohmann::json::array();
|
||||
for (const auto& sv : stampData_)
|
||||
verts.push_back({sv.dx, sv.dy, sv.height});
|
||||
j["vertices"] = verts;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::create_directories(fs::path(path).parent_path());
|
||||
std::ofstream f(path);
|
||||
if (!f) return false;
|
||||
f << j.dump(2) << "\n";
|
||||
LOG_INFO("Stamp saved: ", path, " (", stampData_.size(), " vertices)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TerrainEditor::loadStamp(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return false;
|
||||
try {
|
||||
auto j = nlohmann::json::parse(f);
|
||||
if (!j.contains("vertices") || !j["vertices"].is_array()) return false;
|
||||
|
||||
stampData_.clear();
|
||||
for (const auto& v : j["vertices"]) {
|
||||
if (!v.is_array() || v.size() < 3) continue;
|
||||
StampVertex sv;
|
||||
sv.dx = v[0].get<float>();
|
||||
sv.dy = v[1].get<float>();
|
||||
sv.height = v[2].get<float>();
|
||||
stampData_.push_back(sv);
|
||||
}
|
||||
stampCenter_ = glm::vec3(0);
|
||||
LOG_INFO("Stamp loaded: ", path, " (", stampData_.size(), " vertices)");
|
||||
return !stampData_.empty();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Failed to load stamp: ", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainEditor::clampHeights(float minH, float maxH) {
|
||||
if (!terrain_) return;
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ public:
|
|||
// Terrain stamp: copy heights from source area, paste at destination
|
||||
void copyStamp(const glm::vec3& center, float radius);
|
||||
void pasteStamp(const glm::vec3& center);
|
||||
bool saveStamp(const std::string& path) const;
|
||||
bool loadStamp(const std::string& path);
|
||||
bool hasStamp() const { return !stampData_.empty(); }
|
||||
|
||||
// Mirror terrain along X or Y axis through tile center
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue