mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 09:33:51 +00:00
feat(editor): add --export-wob-obj for buildings -> Wavefront OBJ
WOM (the open M2 replacement) already round-trips through OBJ; this extends the universal-format bridge to WOB (the open WMO replacement) so buildings can also be edited in Blender / MeshLab / Maya / etc. wowee_editor --export-wob-obj House # writes House.obj wowee_editor --export-wob-obj House out.obj # custom path Mapping decisions: - Each WOB group becomes one OBJ 'g' block (named after the group; outdoor groups get an '_outdoor' suffix). Preserves the room/floor structure for downstream selection and per-area editing. - Single global vertex pool with per-group offsets (OBJ requires v indices to be globally 1-based; we track a running vertOffset). - UV V flipped (1.0 - v) so texturing matches Blender bottom-left convention, same as --export-obj for WOM. - Doodad placements written as # comment lines at the end. OBJ has no native concept for instanced models, but emitting them as structured comments keeps the placement data recoverable for tools that want to re-instance them. - Portals and material flags drop on the floor — OBJ has no semantics for either. The native WOB always remains canonical. Verified on a synthesized 2-group house (4-vert floor + 3-vert wall, 1 doodad): output OBJ has 7 verts / 7 vt / 7 vn entries, 2 'g' blocks with proper index offsetting, doodad comment line preserved.
This commit is contained in:
parent
caa0df7e5e
commit
23a2233852
1 changed files with 98 additions and 1 deletions
|
|
@ -429,6 +429,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Convert a WOM model to Wavefront OBJ for use in Blender/MeshLab\n");
|
||||
std::printf(" --import-obj <obj-path> [wom-base]\n");
|
||||
std::printf(" Convert a Wavefront OBJ back into WOM (round-trips with --export-obj)\n");
|
||||
std::printf(" --export-wob-obj <wob-base> [out.obj]\n");
|
||||
std::printf(" Convert a WOB building to Wavefront OBJ (one group per WOB group)\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");
|
||||
|
|
@ -504,7 +506,7 @@ int main(int argc, char* argv[]) {
|
|||
"--remove-creature", "--remove-object", "--remove-quest",
|
||||
"--copy-zone",
|
||||
"--build-woc", "--regen-collision", "--fix-zone",
|
||||
"--export-png", "--export-obj", "--import-obj",
|
||||
"--export-png", "--export-obj", "--import-obj", "--export-wob-obj",
|
||||
"--convert-m2", "--convert-wmo",
|
||||
};
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
|
@ -2116,6 +2118,101 @@ int main(int argc, char* argv[]) {
|
|||
wom.vertices.size(), wom.indices.size() / 3,
|
||||
wom.batches.empty() ? size_t(1) : wom.batches.size());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-wob-obj") == 0 && i + 1 < argc) {
|
||||
// WOB is the WMO replacement; like --export-obj for WOM, this
|
||||
// bridges WOB into the universal-3D-tool ecosystem. Each WOB
|
||||
// group becomes one OBJ 'g' block, preserving the room/floor
|
||||
// structure for downstream selection in Blender/MeshLab.
|
||||
std::string base = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
outPath = argv[++i];
|
||||
}
|
||||
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, "WOB not found: %s.wob\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outPath.empty()) outPath = base + ".obj";
|
||||
auto bld = wowee::pipeline::WoweeBuildingLoader::load(base);
|
||||
if (!bld.isValid()) {
|
||||
std::fprintf(stderr, "WOB has no groups to export: %s.wob\n", base.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;
|
||||
}
|
||||
// Total verts/tris across all groups for the header.
|
||||
size_t totalV = 0, totalI = 0;
|
||||
for (const auto& g : bld.groups) {
|
||||
totalV += g.vertices.size();
|
||||
totalI += g.indices.size();
|
||||
}
|
||||
obj << "# Wavefront OBJ generated by wowee_editor --export-wob-obj\n";
|
||||
obj << "# Source: " << base << ".wob\n";
|
||||
obj << "# Groups: " << bld.groups.size()
|
||||
<< " Verts: " << totalV
|
||||
<< " Tris: " << totalI / 3
|
||||
<< " Portals: " << bld.portals.size()
|
||||
<< " Doodads: " << bld.doodads.size() << "\n\n";
|
||||
obj << "o " << (bld.name.empty() ? "WoweeBuilding" : bld.name) << "\n";
|
||||
// OBJ uses a single global vertex pool, so we offset each group's
|
||||
// local indices by the running total of verts written so far.
|
||||
uint32_t vertOffset = 0;
|
||||
for (size_t g = 0; g < bld.groups.size(); ++g) {
|
||||
const auto& grp = bld.groups[g];
|
||||
if (grp.vertices.empty()) continue;
|
||||
for (const auto& v : grp.vertices) {
|
||||
obj << "v " << v.position.x << " "
|
||||
<< v.position.y << " "
|
||||
<< v.position.z << "\n";
|
||||
}
|
||||
for (const auto& v : grp.vertices) {
|
||||
obj << "vt " << v.texCoord.x << " "
|
||||
<< (1.0f - v.texCoord.y) << "\n";
|
||||
}
|
||||
for (const auto& v : grp.vertices) {
|
||||
obj << "vn " << v.normal.x << " "
|
||||
<< v.normal.y << " "
|
||||
<< v.normal.z << "\n";
|
||||
}
|
||||
std::string groupName = grp.name.empty()
|
||||
? "group_" + std::to_string(g)
|
||||
: grp.name;
|
||||
if (grp.isOutdoor) groupName += "_outdoor";
|
||||
obj << "g " << groupName << "\n";
|
||||
for (size_t k = 0; k + 2 < grp.indices.size(); k += 3) {
|
||||
uint32_t i0 = grp.indices[k] + 1 + vertOffset;
|
||||
uint32_t i1 = grp.indices[k + 1] + 1 + vertOffset;
|
||||
uint32_t i2 = grp.indices[k + 2] + 1 + vertOffset;
|
||||
obj << "f "
|
||||
<< i0 << "/" << i0 << "/" << i0 << " "
|
||||
<< i1 << "/" << i1 << "/" << i1 << " "
|
||||
<< i2 << "/" << i2 << "/" << i2 << "\n";
|
||||
}
|
||||
vertOffset += static_cast<uint32_t>(grp.vertices.size());
|
||||
}
|
||||
// Doodad placements as a separate informational block — emit
|
||||
// each as a comment line so OBJ stays valid but the data is
|
||||
// recoverable for tools that want to re-create the placements.
|
||||
if (!bld.doodads.empty()) {
|
||||
obj << "\n# Doodad placements (model, position, rotation, scale):\n";
|
||||
for (const auto& d : bld.doodads) {
|
||||
obj << "# doodad " << d.modelPath
|
||||
<< " pos " << d.position.x << "," << d.position.y << "," << d.position.z
|
||||
<< " rot " << d.rotation.x << "," << d.rotation.y << "," << d.rotation.z
|
||||
<< " scale " << d.scale << "\n";
|
||||
}
|
||||
}
|
||||
obj.close();
|
||||
std::printf("Exported %s.wob -> %s\n", base.c_str(), outPath.c_str());
|
||||
std::printf(" %zu groups, %zu verts, %zu tris, %zu doodad placements\n",
|
||||
bld.groups.size(), totalV, totalI / 3,
|
||||
bld.doodads.size());
|
||||
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