feat: WOB→WMO conversion and loading in client terrain manager

- WoweeBuildingLoader::toWMOModel() converts WOB groups to WMOModel
  with vertices, indices, normals, texCoords, and vertex colors
- TerrainManager now loads WOB files from custom_zones/buildings/
  and converts to WMOModel for the WMO renderer pipeline
- WMOGroup indices converted from uint32 to uint16 for renderer compat

Client open format support — 4 of 6 now loading:
- FULL: WOT/WHM terrain, PNG textures, WOM models
- LOAD: WOB buildings (converts to WMOModel, render pipeline TODO)
- DETECT: zone.json (scanned), JSON DBC (scanned)
This commit is contained in:
Kelsi 2026-05-05 12:12:26 -07:00
parent aac854c8ad
commit 01d3638835
3 changed files with 46 additions and 1 deletions

View file

@ -53,6 +53,9 @@ public:
static WoweeBuilding load(const std::string& basePath);
static bool save(const WoweeBuilding& building, const std::string& basePath);
static bool exists(const std::string& basePath);
// Convert WOB to WMOModel for the client's WMO renderer
static bool toWMOModel(const WoweeBuilding& building, class WMOModel& outModel);
};
} // namespace pipeline

View file

@ -1,4 +1,5 @@
#include "pipeline/wowee_building.hpp"
#include "pipeline/wmo_loader.hpp"
#include "core/logger.hpp"
#include <fstream>
#include <filesystem>
@ -161,5 +162,39 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base
return true;
}
bool WoweeBuildingLoader::toWMOModel(const WoweeBuilding& building, WMOModel& outModel) {
if (building.groups.empty()) return false;
outModel.nGroups = static_cast<uint32_t>(building.groups.size());
outModel.groups.clear();
for (const auto& grp : building.groups) {
WMOGroup wmoGroup;
wmoGroup.name = grp.name;
// Convert vertices
wmoGroup.vertices.reserve(grp.vertices.size());
for (const auto& v : grp.vertices) {
WMOVertex wv;
wv.position = v.position;
wv.normal = v.normal;
wv.texCoord = v.texCoord;
wv.color = v.color;
wmoGroup.vertices.push_back(wv);
}
// Convert indices
wmoGroup.indices.reserve(grp.indices.size());
for (uint32_t idx : grp.indices)
wmoGroup.indices.push_back(static_cast<uint16_t>(idx));
outModel.groups.push_back(std::move(wmoGroup));
}
// WMOModel uses isValid() = nGroups > 0 && !groups.empty()
// Both are now set, so isValid() will return true
return true;
}
} // namespace pipeline
} // namespace wowee

View file

@ -605,7 +605,14 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
std::vector<std::string> wobPrefixes = {"custom_zones/buildings/", "output/" + mapName + "/buildings/"};
for (const auto& prefix : wobPrefixes) {
if (pipeline::WoweeBuildingLoader::exists(prefix + wobBase)) {
LOG_INFO("WOB building found: ", prefix + wobBase, " (loading not yet implemented)");
auto wob = pipeline::WoweeBuildingLoader::load(prefix + wobBase);
if (wob.isValid()) {
pipeline::WMOModel wobAsWmo;
if (pipeline::WoweeBuildingLoader::toWMOModel(wob, wobAsWmo)) {
LOG_INFO("Loaded WOB building: ", prefix + wobBase);
// TODO: feed wobAsWmo into the WMO render pipeline
}
}
break;
}
}