From 173ad7796e803983669369c6f1fca1c8ed76113e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 11:19:09 -0700 Subject: [PATCH] =?UTF-8?q?feat(editor):=20add=20--bake-wob-collision=20mu?= =?UTF-8?q?lti-group=20WOB=E2=86=92WOC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sibling of --bake-wom-collision for buildings. Walks every group in a WOB, optionally welds vertices PER GROUP (groups are intentionally separate — rooms with portals between them, so welding across groups would fuse walls that should remain distinct collision surfaces), and appends each to a single WoweeCollision via WoweeCollisionBuilder::addMesh. Same flag surface as the WOM variant: optional [out.woc] output path, --weld for per-group vertex merge, --steep for the walkable/steep slope cutoff. Smoke tested on a 1-group cube WOB: 8 verts → 12-triangle WOC with proper bounds (-1,-1,-1)..(1,1,1) and walkable/ steep classification matching the cube face orientations. --- tools/editor/cli_arg_required.cpp | 2 +- tools/editor/cli_bake.cpp | 104 ++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 7be8dc74..c2b3b005 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -139,7 +139,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-wom-collision", "--bake-wob-collision", "--audit-watertight", "--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 08cffc1f..751ca42a 100644 --- a/tools/editor/cli_bake.cpp +++ b/tools/editor/cli_bake.cpp @@ -1004,6 +1004,107 @@ int handleBakeWomCollision(int& i, int argc, char** argv) { return 0; } +int handleBakeWobCollision(int& i, int argc, char** argv) { + // Convert a multi-group WOB into a single WOC collision file. + // Each group's triangles are appended via WoweeCollisionBuilder + // ::addMesh. Optional --weld is applied PER GROUP — groups + // are intentionally separate (rooms with portals between them), + // so welding across groups would fuse walls that should remain + // distinct collision surfaces. + std::string base = 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; + } + } + if (base.size() >= 4 && base.substr(base.size() - 4) == ".wob") { + base = base.substr(0, base.size() - 4); + } + if (!wowee::pipeline::WoweeBuildingLoader::exists(base)) { + std::fprintf(stderr, + "bake-wob-collision: %s.wob does not exist\n", base.c_str()); + return 1; + } + auto bld = wowee::pipeline::WoweeBuildingLoader::load(base); + if (!bld.isValid()) { + std::fprintf(stderr, + "bake-wob-collision: %s.wob has no groups\n", base.c_str()); + return 1; + } + if (outPath.empty()) outPath = base + ".woc"; + wowee::pipeline::WoweeCollision collision; + glm::mat4 identity(1.0f); + std::size_t totalSrc = 0, totalUniq = 0; + for (const auto& g : bld.groups) { + if (g.indices.size() % 3 != 0) { + std::fprintf(stderr, + "bake-wob-collision: group '%s' has indices %% 3 != 0\n", + g.name.c_str()); + return 1; + } + std::vector positions; + std::vector indices; + if (useWeld) { + std::vector srcPositions; + srcPositions.reserve(g.vertices.size()); + for (const auto& v : g.vertices) srcPositions.push_back(v.position); + std::size_t uniq = 0; + std::vector canon = buildWeldMap(srcPositions, weldEps, uniq); + std::vector remap(g.vertices.size(), + std::numeric_limits::max()); + positions.reserve(uniq); + for (std::size_t v = 0; v < g.vertices.size(); ++v) { + uint32_t c = canon[v]; + if (remap[c] == std::numeric_limits::max()) { + remap[c] = static_cast(positions.size()); + positions.push_back(srcPositions[c]); + } + } + indices.reserve(g.indices.size()); + for (uint32_t orig : g.indices) indices.push_back(remap[canon[orig]]); + } else { + positions.reserve(g.vertices.size()); + for (const auto& v : g.vertices) positions.push_back(v.position); + indices = g.indices; + } + wowee::pipeline::WoweeCollisionBuilder::addMesh( + collision, positions, indices, identity, 0, steepAngle); + totalSrc += g.vertices.size(); + totalUniq += positions.size(); + } + if (!wowee::pipeline::WoweeCollisionBuilder::save(collision, outPath)) { + std::fprintf(stderr, + "bake-wob-collision: failed to write %s\n", outPath.c_str()); + return 1; + } + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" source : %s.wob (%zu groups, %zu verts -> %zu)\n", + base.c_str(), bld.groups.size(), totalSrc, totalUniq); + 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 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; @@ -1025,6 +1126,9 @@ bool handleBake(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--bake-wom-collision") == 0 && i + 1 < argc) { outRc = handleBakeWomCollision(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--bake-wob-collision") == 0 && i + 1 < argc) { + outRc = handleBakeWobCollision(i, argc, argv); return true; + } return false; } diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 2aa1fe49..ac71e4f7 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -501,6 +501,8 @@ void printUsage(const char* argv0) { std::printf(" Bake every zone in a project into one glTF 2.0 (one mesh per zone)\n"); std::printf(" --bake-wom-collision [out.woc] [--weld ] [--steep ]\n"); 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(" --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(" --import-obj [wom-base]\n");