mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 09:33:51 +00:00
feat(editor): add --export-woc-obj for collision-mesh visualization
WOC is the open collision format used for movement queries. When the player gets stuck or walks somewhere they shouldn't, you need to SEE the mesh — eyeballing flag bits in --info-woc only goes so far. wowee_editor --export-woc-obj custom_zones/Z/Z_30_30.woc Each flag class gets its own OBJ 'g' block so designers can hide categories independently in Blender to debug: - walkable / steep / water / indoor / nonwalkable - and combinations like 'walkable_water' for shallow swimable areas Implementation notes: - Triangle-soup topology preserved (no vertex dedupe). Adjacent triangles often have different flags; deduping would merge categories and lose the boundary. - Bucket by exact flag value (uint8) so combination flags become their own group rather than counting twice. - Index math: triangle t vertex k -> OBJ index (t*3 + k + 1). - Header comment preserves source path, triangle counts, walkable/ steep counts, and tile (x, y) for provenance. Verified on a freshly-scaffolded zone's auto-built WOC (32768 triangles, all walkable on flat terrain): export reports '1 flag class(es)', single 'g walkable' block in the OBJ, last face index correctly = 32768*3 = 98304.
This commit is contained in:
parent
a9789b0154
commit
a20e795ddb
1 changed files with 85 additions and 0 deletions
|
|
@ -437,6 +437,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Convert a WOB building to Wavefront OBJ (one group per WOB group)\n");
|
||||
std::printf(" --import-wob-obj <obj-path> [wob-base]\n");
|
||||
std::printf(" Convert a Wavefront OBJ back into WOB (round-trips with --export-wob-obj)\n");
|
||||
std::printf(" --export-woc-obj <woc-path> [out.obj]\n");
|
||||
std::printf(" Convert a WOC collision mesh to OBJ for visualization (per-flag color groups)\n");
|
||||
std::printf(" --validate <zoneDir> [--json]\n");
|
||||
std::printf(" Score zone open-format completeness and exit\n");
|
||||
std::printf(" --validate-wom <wom-base> [--json]\n");
|
||||
|
|
@ -519,6 +521,7 @@ int main(int argc, char* argv[]) {
|
|||
"--build-woc", "--regen-collision", "--fix-zone",
|
||||
"--export-png", "--export-obj", "--import-obj",
|
||||
"--export-wob-obj", "--import-wob-obj",
|
||||
"--export-woc-obj",
|
||||
"--convert-m2", "--convert-wmo",
|
||||
};
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
|
@ -2601,6 +2604,88 @@ int main(int argc, char* argv[]) {
|
|||
std::printf(" warning: skipped %d malformed face(s)\n", badFaces);
|
||||
}
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-woc-obj") == 0 && i + 1 < argc) {
|
||||
// Visualize a WOC collision mesh in any 3D tool. Each
|
||||
// walkability class becomes its own OBJ group (walkable /
|
||||
// steep / water / indoor) so designers can hide categories
|
||||
// independently in Blender to debug 'why can the player
|
||||
// walk here?' or 'why can't they walk there?'.
|
||||
std::string path = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
outPath = argv[++i];
|
||||
}
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::fprintf(stderr, "WOC not found: %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outPath.empty()) {
|
||||
outPath = path;
|
||||
if (outPath.size() >= 4 &&
|
||||
outPath.substr(outPath.size() - 4) == ".woc") {
|
||||
outPath = outPath.substr(0, outPath.size() - 4);
|
||||
}
|
||||
outPath += ".obj";
|
||||
}
|
||||
auto woc = wowee::pipeline::WoweeCollisionBuilder::load(path);
|
||||
if (!woc.isValid()) {
|
||||
std::fprintf(stderr, "WOC has no triangles: %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::ofstream obj(outPath);
|
||||
if (!obj) {
|
||||
std::fprintf(stderr, "Failed to open output file: %s\n", outPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
// Bucket triangles by flag combination so the OBJ can split
|
||||
// them into named groups. Flag bits: walkable=0x01, water=0x02,
|
||||
// steep=0x04, indoor=0x08 (per WoweeCollision::Triangle).
|
||||
// Triangles can have multiple flags set so a per-flag group
|
||||
// would over-count; instead we bucket by exact flag value.
|
||||
std::unordered_map<uint8_t, std::vector<size_t>> byFlag;
|
||||
for (size_t t = 0; t < woc.triangles.size(); ++t) {
|
||||
byFlag[woc.triangles[t].flags].push_back(t);
|
||||
}
|
||||
obj << "# Wavefront OBJ generated by wowee_editor --export-woc-obj\n";
|
||||
obj << "# Source: " << path << "\n";
|
||||
obj << "# Triangles: " << woc.triangles.size()
|
||||
<< " (walkable=" << woc.walkableCount()
|
||||
<< " steep=" << woc.steepCount() << ")\n";
|
||||
obj << "# Tile: (" << woc.tileX << ", " << woc.tileY << ")\n\n";
|
||||
obj << "o WoweeCollision\n";
|
||||
// Emit ALL vertices first (3 per triangle, no dedupe — the
|
||||
// collision mesh has triangle-soup topology where shared
|
||||
// verts often have different flags, so deduping would
|
||||
// actually merge categories).
|
||||
for (const auto& tri : woc.triangles) {
|
||||
obj << "v " << tri.v0.x << " " << tri.v0.y << " " << tri.v0.z << "\n";
|
||||
obj << "v " << tri.v1.x << " " << tri.v1.y << " " << tri.v1.z << "\n";
|
||||
obj << "v " << tri.v2.x << " " << tri.v2.y << " " << tri.v2.z << "\n";
|
||||
}
|
||||
// Emit faces grouped by flag class. OBJ index of triangle t
|
||||
// vertex k is (t * 3 + k + 1) — 1-based, three verts per tri.
|
||||
auto flagName = [](uint8_t f) {
|
||||
if (f == 0) return std::string("nonwalkable");
|
||||
std::string s;
|
||||
if (f & 0x01) s += "walkable";
|
||||
if (f & 0x02) { if (!s.empty()) s += "_"; s += "water"; }
|
||||
if (f & 0x04) { if (!s.empty()) s += "_"; s += "steep"; }
|
||||
if (f & 0x08) { if (!s.empty()) s += "_"; s += "indoor"; }
|
||||
if (s.empty()) s = "flag" + std::to_string(int(f));
|
||||
return s;
|
||||
};
|
||||
for (const auto& [flag, tris] : byFlag) {
|
||||
obj << "g " << flagName(flag) << "\n";
|
||||
for (size_t t : tris) {
|
||||
uint32_t base = static_cast<uint32_t>(t * 3 + 1);
|
||||
obj << "f " << base << " " << (base + 1) << " " << (base + 2) << "\n";
|
||||
}
|
||||
}
|
||||
obj.close();
|
||||
std::printf("Exported %s -> %s\n", path.c_str(), outPath.c_str());
|
||||
std::printf(" %zu triangles in %zu flag class(es), tile (%u, %u)\n",
|
||||
woc.triangles.size(), byFlag.size(), woc.tileX, woc.tileY);
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--import-obj") == 0 && i + 1 < argc) {
|
||||
// Convert a Wavefront OBJ back into WOM. Round-trips with
|
||||
// --export-obj for the geometry/UV/normal data; bones,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue