From 2cb079549e6f9a1827696c8134f33a9e25cf2c4f Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 06:47:43 -0700 Subject: [PATCH] feat(editor): add --center-mesh and --flip-mesh-normals quick fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two convenience commands for the common "fix imported mesh" cases: --center-mesh Translates the mesh so the bounds center lands at the origin. Useful when an OBJ comes in with its pivot in some weird corner and the user wants center-pivoted placement. Pure translation — shape unchanged, radius preserved. --flip-mesh-normals Inverts every vertex normal. Use case: an OBJ with flipped winding renders inside-out — flipping normals fixes shading without re-winding the index buffer (which would also need batch-aware care). Also useful for skybox-like inside-facing meshes. Verified: cube translated to (5,0,0) → center brings it back to ±0.5 with shift line "(-5, -0, -0)" reported; flip reports 24 vertices touched. Brings command count to 221 (kArgRequired 202). --- tools/editor/main.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 3effa581..a677092f 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -548,6 +548,10 @@ static void printUsage(const char* argv0) { std::printf(" Drop bones / animations from a WOM in place (smaller file, static-only use)\n"); std::printf(" --rotate-mesh \n"); std::printf(" Rotate every vertex + normal around the chosen axis by \n"); + std::printf(" --center-mesh \n"); + std::printf(" Translate so the bounds center lands at origin (no scale/rotation change)\n"); + std::printf(" --flip-mesh-normals \n"); + std::printf(" Invert every vertex normal (use for inside-out meshes or two-sided pre-flip)\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"); @@ -965,6 +969,7 @@ int main(int argc, char* argv[]) { "--gen-mesh-stairs", "--gen-texture-gradient", "--scale-mesh", "--translate-mesh", "--strip-mesh", "--gen-texture-noise", "--rotate-mesh", + "--center-mesh", "--flip-mesh-normals", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp", @@ -16483,6 +16488,79 @@ int main(int argc, char* argv[]) { wom.boundMin.x, wom.boundMin.y, wom.boundMin.z, wom.boundMax.x, wom.boundMax.y, wom.boundMax.z); return 0; + } else if (std::strcmp(argv[i], "--center-mesh") == 0 && i + 1 < argc) { + // Translate the mesh so the bounds center lands at the + // origin. Convenience for "this mesh's pivot is in some + // weird corner — make it center-pivoted." Doesn't change + // shape, just shifts. + std::string womBase = argv[++i]; + if (womBase.size() >= 4 && + womBase.substr(womBase.size() - 4) == ".wom") { + womBase = womBase.substr(0, womBase.size() - 4); + } + if (!wowee::pipeline::WoweeModelLoader::exists(womBase)) { + std::fprintf(stderr, + "center-mesh: %s.wom does not exist\n", womBase.c_str()); + return 1; + } + auto wom = wowee::pipeline::WoweeModelLoader::load(womBase); + if (!wom.isValid()) { + std::fprintf(stderr, + "center-mesh: failed to load %s.wom\n", womBase.c_str()); + return 1; + } + glm::vec3 center = (wom.boundMin + wom.boundMax) * 0.5f; + for (auto& v : wom.vertices) v.position -= center; + for (auto& b : wom.bones) b.pivot -= center; + wom.boundMin -= center; + wom.boundMax -= center; + // Radius is preserved (pure translation). + if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) { + std::fprintf(stderr, + "center-mesh: failed to save %s.wom\n", womBase.c_str()); + return 1; + } + std::printf("Centered %s.wom (shifted by %g, %g, %g)\n", + womBase.c_str(), -center.x, -center.y, -center.z); + std::printf(" new bounds : (%.3f, %.3f, %.3f) - (%.3f, %.3f, %.3f)\n", + wom.boundMin.x, wom.boundMin.y, wom.boundMin.z, + wom.boundMax.x, wom.boundMax.y, wom.boundMax.z); + return 0; + } else if (std::strcmp(argv[i], "--flip-mesh-normals") == 0 && i + 1 < argc) { + // Invert every vertex normal. Use case: an OBJ imported + // with flipped winding renders inside-out — flipping the + // normals makes shading correct without re-winding the + // index buffer (which would also need batch-aware care). + // Also useful for skybox-like meshes where the "outside" + // texture should appear when looking from inside. + std::string womBase = argv[++i]; + if (womBase.size() >= 4 && + womBase.substr(womBase.size() - 4) == ".wom") { + womBase = womBase.substr(0, womBase.size() - 4); + } + if (!wowee::pipeline::WoweeModelLoader::exists(womBase)) { + std::fprintf(stderr, + "flip-mesh-normals: %s.wom does not exist\n", + womBase.c_str()); + return 1; + } + auto wom = wowee::pipeline::WoweeModelLoader::load(womBase); + if (!wom.isValid()) { + std::fprintf(stderr, + "flip-mesh-normals: failed to load %s.wom\n", + womBase.c_str()); + return 1; + } + for (auto& v : wom.vertices) v.normal = -v.normal; + if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) { + std::fprintf(stderr, + "flip-mesh-normals: failed to save %s.wom\n", + womBase.c_str()); + return 1; + } + std::printf("Flipped normals on %s.wom (%zu vertices)\n", + womBase.c_str(), wom.vertices.size()); + return 0; } else if (std::strcmp(argv[i], "--add-texture-to-zone") == 0 && i + 2 < argc) { // Import an existing PNG into a zone directory. Useful // for the "I have an artist-painted texture, get it into