feat(editor): add --build-woc CLI for headless collision generation

Loads a WHM/WOT terrain pair and writes a .woc collision mesh
alongside it. Terrain triangles only (no WMO overlays — those need
the asset manager) but enough for first-pass walkability while
authoring.

Verified end-to-end: scaffold-zone → build-woc → info-woc reports
32k triangles for a flat 256-chunk tile.
This commit is contained in:
Kelsi 2026-05-06 08:21:14 -07:00
parent 81832ea676
commit 21078f8806

View file

@ -31,6 +31,7 @@ static void printUsage(const char* argv0) {
std::printf(" --convert-wmo <path> Convert WMO building to WOB open format (no GUI)\n");
std::printf(" --list-zones List discovered custom zones and exit\n");
std::printf(" --scaffold-zone <name> [tx ty] Create a blank zone in custom_zones/<name>/ and exit\n");
std::printf(" --build-woc <wot-base> Generate a WOC collision mesh from WHM/WOT and exit\n");
std::printf(" --validate <zoneDir> Score zone open-format completeness and exit\n");
std::printf(" --info <wom-base> Print WOM file metadata (version, counts) and exit\n");
std::printf(" --info-wob <wob-base> Print WOB building metadata (groups, portals, doodads) and exit\n");
@ -59,7 +60,7 @@ int main(int argc, char* argv[]) {
"--data", "--info", "--info-wob", "--info-woc", "--info-wot",
"--info-creatures", "--info-objects", "--info-quests",
"--info-wcp", "--list-wcp", "--unpack-wcp", "--pack-wcp",
"--validate", "--scaffold-zone",
"--validate", "--scaffold-zone", "--build-woc",
"--convert-m2", "--convert-wmo",
};
for (int i = 1; i < argc; i++) {
@ -375,6 +376,36 @@ int main(int argc, char* argv[]) {
std::printf(" quests.json : %s\n", v.hasQuests ? "yes" : "no");
std::printf(" objects.json : %s\n", v.hasObjects ? "yes" : "no");
return score == 7 ? 0 : 1;
} else if (std::strcmp(argv[i], "--build-woc") == 0 && i + 1 < argc) {
// Generate a WOC collision mesh from a WHM/WOT terrain pair.
// Uses terrain triangles only (no WMO overlays); useful as a
// first-pass collision build before the editor adds buildings.
std::string base = argv[++i];
for (const char* ext : {".wot", ".whm", ".woc"}) {
if (base.size() >= 4 && base.substr(base.size() - 4) == ext) {
base = base.substr(0, base.size() - 4);
break;
}
}
if (!wowee::pipeline::WoweeTerrainLoader::exists(base)) {
std::fprintf(stderr, "WOT/WHM not found at base: %s\n", base.c_str());
return 1;
}
wowee::pipeline::ADTTerrain terrain;
if (!wowee::pipeline::WoweeTerrainLoader::load(base, terrain)) {
std::fprintf(stderr, "Failed to load terrain: %s\n", base.c_str());
return 1;
}
auto col = wowee::pipeline::WoweeCollisionBuilder::fromTerrain(terrain);
std::string outPath = base + ".woc";
if (!wowee::pipeline::WoweeCollisionBuilder::save(col, outPath)) {
std::fprintf(stderr, "WOC save failed: %s\n", outPath.c_str());
return 1;
}
std::printf("WOC built: %s (%zu triangles, %zu walkable, %zu steep)\n",
outPath.c_str(),
col.triangles.size(), col.walkableCount(), col.steepCount());
return 0;
} else if (std::strcmp(argv[i], "--scaffold-zone") == 0 && i + 1 < argc) {
// Generate a minimal valid empty zone — useful for kickstarting
// a new authoring session without needing to launch the GUI.