From 8588279a88f5ce95bb9e3b89539b374ffe52e110 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 02:43:32 -0700 Subject: [PATCH] feat(editor): add --add-texture-to-mesh manual texture binder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual companion to --gen-mesh-textured. Binds an existing PNG into a WOM's texturePaths and points the chosen batch (default 0) at it. Useful when the texture wasn't synthesized — e.g., an artist-painted PNG, a converted .blp, or a texture shared across multiple meshes. Slot dedup: if the leaf path already exists in texturePaths, the existing slot is reused rather than appended, so repeated invocations don't bloat the table. Warns (but doesn't fail) if the PNG isn't sitting next to the WOM, since runtime path resolution is relative to the WOM's own directory. Verified: bind once → slot 1 added (slot 0 is the placeholder from --gen-mesh), batch wired; rebind same path → slot 1 reused, count stays at 2; missing PNG → exit 1 with clear error. Brings command count to 203. --- tools/editor/main.cpp | 100 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 0fd659f8..74da1e0d 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -530,6 +530,8 @@ static void printUsage(const char* argv0) { std::printf(" Synthesize a procedural WOM primitive with proper normals, UVs, and bounds\n"); std::printf(" --gen-mesh-textured [size]\n"); std::printf(" Compose a procedural mesh + matching PNG texture wired into the WOM's batch\n"); + std::printf(" --add-texture-to-mesh [batchIdx]\n"); + std::printf(" Bind an existing PNG into a WOM's texturePaths and point batchIdx (default 0) at it\n"); std::printf(" --add-item [id] [quality] [displayId] [itemLevel]\n"); std::printf(" Append one item entry to /items.json (auto-creates the file)\n"); std::printf(" --list-items [--json]\n"); @@ -926,6 +928,7 @@ int main(int argc, char* argv[]) { "--bench-validate-project", "--bench-bake-project", "--bench-migrate-data-tree", "--list-data-tree-largest", "--export-data-tree-md", "--gen-texture", "--gen-mesh", "--gen-mesh-textured", + "--add-texture-to-mesh", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp", @@ -14711,6 +14714,103 @@ int main(int argc, char* argv[]) { std::printf(" vertices : %zu\n", wom.vertices.size()); std::printf(" texture : %s (wired into batch 0)\n", pngLeaf.c_str()); return 0; + } else if (std::strcmp(argv[i], "--add-texture-to-mesh") == 0 && i + 2 < argc) { + // Manual companion to --gen-mesh-textured. Binds an + // existing PNG to a WOM by appending it to texturePaths + // (or reusing the slot if already present) and pointing + // the chosen batch at it. + // + // The PNG path stored in the WOM is just the leaf — the + // runtime resolves textures relative to the model's own + // directory, so the user is responsible for placing the + // PNG next to the WOM. + std::string womBase = argv[++i]; + std::string pngPath = argv[++i]; + int batchIdx = 0; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { batchIdx = std::stoi(argv[++i]); } + catch (...) { + std::fprintf(stderr, + "add-texture-to-mesh: batchIdx must be an integer\n"); + return 1; + } + } + // Strip .wom if user passed a full filename. + if (womBase.size() >= 4 && + womBase.substr(womBase.size() - 4) == ".wom") { + womBase = womBase.substr(0, womBase.size() - 4); + } + namespace fs = std::filesystem; + if (!wowee::pipeline::WoweeModelLoader::exists(womBase)) { + std::fprintf(stderr, + "add-texture-to-mesh: %s.wom does not exist\n", + womBase.c_str()); + return 1; + } + if (!fs::exists(pngPath)) { + std::fprintf(stderr, + "add-texture-to-mesh: png '%s' does not exist\n", + pngPath.c_str()); + return 1; + } + auto wom = wowee::pipeline::WoweeModelLoader::load(womBase); + if (!wom.isValid()) { + std::fprintf(stderr, + "add-texture-to-mesh: failed to load %s.wom\n", + womBase.c_str()); + return 1; + } + if (wom.batches.empty()) { + std::fprintf(stderr, + "add-texture-to-mesh: %s.wom has no batches " + "(run --migrate-wom to upgrade WOM1/WOM2 first)\n", + womBase.c_str()); + return 1; + } + if (batchIdx < 0 || + static_cast(batchIdx) >= wom.batches.size()) { + std::fprintf(stderr, + "add-texture-to-mesh: batchIdx %d out of range " + "(have %zu batches)\n", + batchIdx, wom.batches.size()); + return 1; + } + std::string pngLeaf = fs::path(pngPath).filename().string(); + // Reuse texture slot if the leaf is already in the table; + // otherwise append a new slot at the end. + uint32_t texIdx = static_cast(wom.texturePaths.size()); + for (size_t k = 0; k < wom.texturePaths.size(); ++k) { + if (wom.texturePaths[k] == pngLeaf) { + texIdx = static_cast(k); + break; + } + } + if (texIdx == wom.texturePaths.size()) { + wom.texturePaths.push_back(pngLeaf); + } + wom.batches[batchIdx].textureIndex = texIdx; + if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) { + std::fprintf(stderr, + "add-texture-to-mesh: failed to re-save %s.wom\n", + womBase.c_str()); + return 1; + } + std::printf("Bound %s -> %s.wom batch %d (texture slot %u)\n", + pngLeaf.c_str(), womBase.c_str(), + batchIdx, texIdx); + std::printf(" total texture slots : %zu\n", wom.texturePaths.size()); + // Warn if the PNG isn't sitting next to the WOM — the + // runtime resolves leaf paths relative to the WOM dir. + std::string womDir = fs::path(womBase).parent_path().string(); + if (womDir.empty()) womDir = "."; + std::string expected = womDir + "/" + pngLeaf; + if (!fs::exists(expected)) { + std::printf(" NOTE: %s does not exist next to the WOM\n", + expected.c_str()); + std::printf(" copy or move %s -> %s before shipping\n", + pngPath.c_str(), expected.c_str()); + } + return 0; } else if (std::strcmp(argv[i], "--info-data-tree") == 0 && i + 1 < argc) { // Non-destructive companion to --migrate-data-tree. Walks // recursively, counts files per format pair