diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 59179b14..57ddec7c 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -141,7 +141,7 @@ const char* const kArgRequired[] = { "--export-stl", "--import-stl", "--bake-zone-glb", "--bake-zone-stl", "--bake-zone-obj", "--bake-project-obj", "--bake-project-stl", "--bake-project-glb", - "--bake-wom-collision", "--bake-wob-collision", + "--bake-wom-collision", "--bake-wob-collision", "--bake-zone-collision", "--audit-watertight", "--audit-watertight-wob", "--convert-m2", "--convert-m2-batch", "--convert-wmo", "--convert-wmo-batch", diff --git a/tools/editor/cli_bake.cpp b/tools/editor/cli_bake.cpp index 751ca42a..ae74347a 100644 --- a/tools/editor/cli_bake.cpp +++ b/tools/editor/cli_bake.cpp @@ -1105,6 +1105,128 @@ int handleBakeWobCollision(int& i, int argc, char** argv) { return 0; } +int handleBakeZoneCollision(int& i, int argc, char** argv) { + // Walk every .wom and .wob under , weld each one + // independently (per-mesh / per-WOB-group), and append its + // triangles to a single WoweeCollision. Useful for shipping + // a zone — one .woc file holds all object collision so the + // server side has a single artifact to serve. + // + // Per-file weld preserves between-object boundaries: two + // distinct WOMs sitting at the same world position keep + // their topology separate even if their corner positions + // happen to overlap. + std::string root = argv[++i]; + std::string outPath; + if (i + 1 < argc && argv[i + 1][0] != '-') { + outPath = argv[++i]; + } + bool useWeld = false; + float weldEps = 1e-5f; + float steepAngle = 50.0f; + while (i + 1 < argc && argv[i + 1][0] == '-') { + if (std::strcmp(argv[i + 1], "--weld") == 0 && i + 2 < argc) { + useWeld = true; + try { weldEps = std::stof(argv[i + 2]); } catch (...) {} + i += 2; + } else if (std::strcmp(argv[i + 1], "--steep") == 0 && i + 2 < argc) { + try { steepAngle = std::stof(argv[i + 2]); } catch (...) {} + i += 2; + } else { + break; + } + } + namespace fs = std::filesystem; + if (!fs::exists(root) || !fs::is_directory(root)) { + std::fprintf(stderr, + "bake-zone-collision: %s is not a directory\n", root.c_str()); + return 1; + } + if (outPath.empty()) { + outPath = (fs::path(root) / "zone.woc").string(); + } + wowee::pipeline::WoweeCollision collision; + glm::mat4 identity(1.0f); + auto weldOne = [&](std::vector& positions, + std::vector& indices) { + if (!useWeld) return; + std::size_t uniq = 0; + std::vector canon = buildWeldMap(positions, weldEps, uniq); + std::vector compacted; + std::vector remap(positions.size(), + std::numeric_limits::max()); + compacted.reserve(uniq); + for (std::size_t v = 0; v < positions.size(); ++v) { + uint32_t c = canon[v]; + if (remap[c] == std::numeric_limits::max()) { + remap[c] = static_cast(compacted.size()); + compacted.push_back(positions[c]); + } + } + for (uint32_t& idx : indices) idx = remap[canon[idx]]; + positions = std::move(compacted); + }; + int wcount = 0, bcount = 0; + std::error_code ec; + for (const auto& e : fs::recursive_directory_iterator(root, ec)) { + if (!e.is_regular_file()) continue; + const auto ext = e.path().extension(); + if (ext == ".wom") { + std::string base = e.path().string(); + base = base.substr(0, base.size() - 4); + auto wom = wowee::pipeline::WoweeModelLoader::load(base); + if (!wom.isValid() || wom.indices.size() % 3 != 0) continue; + std::vector positions; + positions.reserve(wom.vertices.size()); + for (const auto& v : wom.vertices) positions.push_back(v.position); + std::vector indices = wom.indices; + weldOne(positions, indices); + wowee::pipeline::WoweeCollisionBuilder::addMesh( + collision, positions, indices, identity, 0, steepAngle); + ++wcount; + } else if (ext == ".wob") { + std::string base = e.path().string(); + base = base.substr(0, base.size() - 4); + auto bld = wowee::pipeline::WoweeBuildingLoader::load(base); + if (!bld.isValid()) continue; + for (const auto& g : bld.groups) { + if (g.indices.size() % 3 != 0) continue; + std::vector positions; + positions.reserve(g.vertices.size()); + for (const auto& v : g.vertices) positions.push_back(v.position); + std::vector indices = g.indices; + weldOne(positions, indices); + wowee::pipeline::WoweeCollisionBuilder::addMesh( + collision, positions, indices, identity, 0, steepAngle); + } + ++bcount; + } + } + if (collision.triangles.empty()) { + std::fprintf(stderr, + "bake-zone-collision: no .wom or .wob found under %s\n", + root.c_str()); + return 1; + } + if (!wowee::pipeline::WoweeCollisionBuilder::save(collision, outPath)) { + std::fprintf(stderr, + "bake-zone-collision: failed to write %s\n", outPath.c_str()); + return 1; + } + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" scanned : %d WOM + %d WOB under %s\n", + wcount, bcount, root.c_str()); + std::printf(" triangles : %zu (%zu walkable, %zu steep)\n", + collision.triangles.size(), + collision.walkableCount(), + collision.steepCount()); + std::printf(" steep cut : %.1f° from horizontal\n", steepAngle); + if (useWeld) { + std::printf(" weld eps : %.6f (per file/group)\n", weldEps); + } + return 0; +} + bool handleBake(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--bake-zone-glb") == 0 && i + 1 < argc) { outRc = handleBakeZoneGlb(i, argc, argv); return true; @@ -1129,6 +1251,9 @@ bool handleBake(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--bake-wob-collision") == 0 && i + 1 < argc) { outRc = handleBakeWobCollision(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--bake-zone-collision") == 0 && i + 1 < argc) { + outRc = handleBakeZoneCollision(i, argc, argv); return true; + } return false; } diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 2d8f649a..c0029a04 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -511,6 +511,8 @@ void printUsage(const char* argv0) { std::printf(" Convert a WOM into a WOC collision file (raycast / walkability mesh) with optional vertex weld\n"); std::printf(" --bake-wob-collision [out.woc] [--weld ] [--steep ]\n"); std::printf(" Convert a multi-group WOB building into a single WOC collision file (weld is per-group)\n"); + std::printf(" --bake-zone-collision [out.woc] [--weld ] [--steep ]\n"); + std::printf(" Walk every .wom + .wob under zoneDir, weld each independently, append to one shared WOC\n"); std::printf(" --audit-watertight [--weld ] [--json]\n"); std::printf(" Walk every .wom under root, run welded watertight check; exit code = failure count (CI-friendly)\n"); std::printf(" --audit-watertight-wob [--weld ] [--json]\n");