From 3cebcab0480b5ca7619808fdf0cd910c177fcaec Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 13:12:28 -0700 Subject: [PATCH] feat(editor): add --info-batches per-batch breakdown for WOM3 models --info shows aggregate batch count; this drills into each one: wowee_editor --info-batches HumanMale WOM batches: HumanMale.wom (v3, 2 batches) idx iStart iCount tris blend flags texture 0 0 3 1 opaque - Body.blp 1 3 3 1 alpha two-sided no-zwrite Hair.blp Useful for debugging: - 'why is this submesh transparent?' -> check blendMode column - 'why is the back face missing?' -> check flags for two-sided - 'why is depth wrong?' -> check no-zwrite flag - 'which batch has the bad UV?' -> indexStart/indexCount range - 'why does this material show wrong texture?' -> textureIndex + path Decodes: - Blend modes: 0=opaque, 1=alpha-test, 2=alpha, 3=add - Flags: 0x01=unlit, 0x02=two-sided, 0x04=no-zwrite Falls back to a 'no batches (WOM1/WOM2 single-material model)' note when called on an older-version model that doesn't have a batch table. Verified on a synthesized 2-batch model (opaque body + alpha hair with two-sided + no-zwrite flags): all fields decoded correctly, table aligns, JSON output enumerable. --- tools/editor/main.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index dfc946c1..7fcac8f4 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -490,6 +490,8 @@ static void printUsage(const char* argv0) { std::printf(" Render a markdown documentation page for a zone (manifest + content)\n"); std::printf(" --info [--json]\n"); std::printf(" Print WOM file metadata (version, counts) and exit\n"); + std::printf(" --info-batches [--json]\n"); + std::printf(" Per-batch breakdown of a WOM3 (index range, texture, blend mode, flags)\n"); std::printf(" --info-wob [--json]\n"); std::printf(" Print WOB building metadata (groups, portals, doodads) and exit\n"); std::printf(" --info-woc [--json]\n"); @@ -552,7 +554,7 @@ int main(int argc, char* argv[]) { // Detect non-GUI options that are missing their argument and bail out // with a helpful message instead of silently dropping into the GUI. static const char* kArgRequired[] = { - "--data", "--info", "--info-wob", "--info-woc", "--info-wot", + "--data", "--info", "--info-batches", "--info-wob", "--info-woc", "--info-wot", "--info-creatures", "--info-objects", "--info-quests", "--info-extract", "--list-missing-sidecars", "--info-png", "--info-jsondbc", "--info-blp", @@ -728,6 +730,94 @@ int main(int argc, char* argv[]) { std::printf(" batches : %zu\n", wom.batches.size()); std::printf(" boundRadius: %.2f\n", wom.boundRadius); return 0; + } else if (std::strcmp(argv[i], "--info-batches") == 0 && i + 1 < argc) { + // Per-batch breakdown of a WOM3 (multi-material) model. + // --info shows the total batch count; this drills into each + // one's index range, texture, blend mode, and flags. Useful + // for debugging 'why is this submesh transparent?' or + // 'which batch has the bad UV?'. + std::string base = argv[++i]; + bool jsonOut = (i + 1 < argc && + std::strcmp(argv[i + 1], "--json") == 0); + if (jsonOut) i++; + if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom") + base = base.substr(0, base.size() - 4); + if (!wowee::pipeline::WoweeModelLoader::exists(base)) { + std::fprintf(stderr, "WOM not found: %s.wom\n", base.c_str()); + return 1; + } + auto wom = wowee::pipeline::WoweeModelLoader::load(base); + // Blend modes per WoweeModel::Batch comment: + // 0=opaque, 1=alpha-test, 2=alpha, 3=add + auto blendName = [](uint16_t b) { + switch (b) { + case 0: return "opaque"; + case 1: return "alpha-test"; + case 2: return "alpha"; + case 3: return "add"; + } + return "?"; + }; + // Flags bits: + // bit 0 (0x01) = unlit + // bit 1 (0x02) = two-sided + // bit 2 (0x04) = no z-write + auto flagsStr = [](uint16_t f) { + std::string s; + if (f & 0x01) s += "unlit "; + if (f & 0x02) s += "two-sided "; + if (f & 0x04) s += "no-zwrite "; + if (s.empty()) s = "-"; + else s.pop_back(); // drop trailing space + return s; + }; + if (jsonOut) { + nlohmann::json j; + j["wom"] = base + ".wom"; + j["version"] = wom.version; + j["totalBatches"] = wom.batches.size(); + nlohmann::json arr = nlohmann::json::array(); + for (size_t k = 0; k < wom.batches.size(); ++k) { + const auto& b = wom.batches[k]; + std::string tex = (b.textureIndex < wom.texturePaths.size()) + ? wom.texturePaths[b.textureIndex] + : std::string(""); + arr.push_back({ + {"index", k}, + {"indexStart", b.indexStart}, + {"indexCount", b.indexCount}, + {"triangles", b.indexCount / 3}, + {"textureIndex", b.textureIndex}, + {"texturePath", tex}, + {"blendMode", b.blendMode}, + {"blendName", blendName(b.blendMode)}, + {"flags", b.flags}, + {"flagsStr", flagsStr(b.flags)}, + }); + } + j["batches"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("WOM batches: %s.wom (v%u, %zu batches)\n", + base.c_str(), wom.version, wom.batches.size()); + if (wom.batches.empty()) { + std::printf(" *no batches (WOM1/WOM2 single-material model)*\n"); + return 0; + } + std::printf(" idx iStart iCount tris blend flags texture\n"); + for (size_t k = 0; k < wom.batches.size(); ++k) { + const auto& b = wom.batches[k]; + std::string tex = (b.textureIndex < wom.texturePaths.size()) + ? wom.texturePaths[b.textureIndex] + : std::string(""); + std::printf(" %3zu %6u %6u %5u %-10s %-13s %s\n", + k, b.indexStart, b.indexCount, b.indexCount / 3, + blendName(b.blendMode), + flagsStr(b.flags).c_str(), + tex.c_str()); + } + return 0; } else if (std::strcmp(argv[i], "--info-wob") == 0 && i + 1 < argc) { std::string base = argv[++i]; bool jsonOut = (i + 1 < argc &&