mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat: Wowee Open Building format (.wob) — novel WMO replacement
ALL 6 BLIZZARD FORMATS NOW HAVE OPEN REPLACEMENTS. WOB format: binary building file with groups, portals, and doodads. - WOB1 magic header - Groups: vertices (pos+normal+uv+color), indices, texture paths, bounding box, indoor/outdoor flag - Portals: connect groups with polygon boundaries - Doodad placements: reference .wom models with transform WoweeBuildingLoader: load/save/exists for .wob files. Complete format replacement table: - ADT → WOT/WHM (terrain heightmaps + metadata) - WDT → zone.json (map definition) - BLP → PNG (textures) - DBC → JSON (data tables) - M2 → WOM (static models) - WMO → WOB (buildings with groups/portals/doodads) The wowee project now has a complete suite of novel, open file formats for creating and distributing custom WoW content without any dependency on Blizzard proprietary file formats.
This commit is contained in:
parent
b4cb833108
commit
71c3eb0fe6
3 changed files with 226 additions and 0 deletions
|
|
@ -591,6 +591,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/wdt_loader.cpp
|
||||
src/pipeline/wowee_terrain_loader.cpp
|
||||
src/pipeline/wowee_model.cpp
|
||||
src/pipeline/wowee_building.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
|
||||
|
|
@ -1320,6 +1321,7 @@ add_executable(wowee_editor
|
|||
src/pipeline/wdt_loader.cpp
|
||||
src/pipeline/wowee_terrain_loader.cpp
|
||||
src/pipeline/wowee_model.cpp
|
||||
src/pipeline/wowee_building.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
|
|
|
|||
59
include/pipeline/wowee_building.hpp
Normal file
59
include/pipeline/wowee_building.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
// Wowee Open Building format (.wob) — novel WMO replacement
|
||||
// Buildings with multiple groups, portals, and doodad sets
|
||||
struct WoweeBuilding {
|
||||
struct Vertex {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texCoord;
|
||||
glm::vec4 color; // vertex color/lighting
|
||||
};
|
||||
|
||||
struct Group {
|
||||
std::string name;
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
std::vector<std::string> texturePaths;
|
||||
glm::vec3 boundMin{0}, boundMax{0};
|
||||
bool isOutdoor = false;
|
||||
};
|
||||
|
||||
struct Portal {
|
||||
int groupA = -1, groupB = -1;
|
||||
std::vector<glm::vec3> vertices; // portal polygon
|
||||
};
|
||||
|
||||
struct DoodadPlacement {
|
||||
std::string modelPath; // .wom path
|
||||
glm::vec3 position;
|
||||
glm::vec3 rotation;
|
||||
float scale = 1.0f;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::vector<Group> groups;
|
||||
std::vector<Portal> portals;
|
||||
std::vector<DoodadPlacement> doodads;
|
||||
float boundRadius = 1.0f;
|
||||
|
||||
bool isValid() const { return !groups.empty(); }
|
||||
};
|
||||
|
||||
class WoweeBuildingLoader {
|
||||
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);
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
165
src/pipeline/wowee_building.cpp
Normal file
165
src/pipeline/wowee_building.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include "pipeline/wowee_building.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
static constexpr uint32_t WOB_MAGIC = 0x31424F57; // "WOB1"
|
||||
|
||||
bool WoweeBuildingLoader::exists(const std::string& basePath) {
|
||||
return std::filesystem::exists(basePath + ".wob");
|
||||
}
|
||||
|
||||
WoweeBuilding WoweeBuildingLoader::load(const std::string& basePath) {
|
||||
WoweeBuilding bld;
|
||||
std::ifstream f(basePath + ".wob", std::ios::binary);
|
||||
if (!f) return bld;
|
||||
|
||||
uint32_t magic;
|
||||
f.read(reinterpret_cast<char*>(&magic), 4);
|
||||
if (magic != WOB_MAGIC) return bld;
|
||||
|
||||
uint32_t groupCount, portalCount, doodadCount;
|
||||
f.read(reinterpret_cast<char*>(&groupCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&portalCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&doodadCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&bld.boundRadius), 4);
|
||||
|
||||
uint16_t nameLen;
|
||||
f.read(reinterpret_cast<char*>(&nameLen), 2);
|
||||
bld.name.resize(nameLen);
|
||||
f.read(bld.name.data(), nameLen);
|
||||
|
||||
for (uint32_t gi = 0; gi < groupCount; gi++) {
|
||||
WoweeBuilding::Group grp;
|
||||
uint16_t gnLen;
|
||||
f.read(reinterpret_cast<char*>(&gnLen), 2);
|
||||
grp.name.resize(gnLen);
|
||||
f.read(grp.name.data(), gnLen);
|
||||
|
||||
uint32_t vc, ic, tc;
|
||||
f.read(reinterpret_cast<char*>(&vc), 4);
|
||||
f.read(reinterpret_cast<char*>(&ic), 4);
|
||||
f.read(reinterpret_cast<char*>(&tc), 4);
|
||||
uint8_t outdoor;
|
||||
f.read(reinterpret_cast<char*>(&outdoor), 1);
|
||||
grp.isOutdoor = (outdoor != 0);
|
||||
f.read(reinterpret_cast<char*>(&grp.boundMin), 12);
|
||||
f.read(reinterpret_cast<char*>(&grp.boundMax), 12);
|
||||
|
||||
grp.vertices.resize(vc);
|
||||
f.read(reinterpret_cast<char*>(grp.vertices.data()), vc * sizeof(WoweeBuilding::Vertex));
|
||||
grp.indices.resize(ic);
|
||||
f.read(reinterpret_cast<char*>(grp.indices.data()), ic * 4);
|
||||
|
||||
for (uint32_t ti = 0; ti < tc; ti++) {
|
||||
uint16_t tl;
|
||||
f.read(reinterpret_cast<char*>(&tl), 2);
|
||||
std::string tp(tl, '\0');
|
||||
f.read(tp.data(), tl);
|
||||
grp.texturePaths.push_back(tp);
|
||||
}
|
||||
bld.groups.push_back(std::move(grp));
|
||||
}
|
||||
|
||||
for (uint32_t pi = 0; pi < portalCount; pi++) {
|
||||
WoweeBuilding::Portal portal;
|
||||
f.read(reinterpret_cast<char*>(&portal.groupA), 4);
|
||||
f.read(reinterpret_cast<char*>(&portal.groupB), 4);
|
||||
uint32_t pvCount;
|
||||
f.read(reinterpret_cast<char*>(&pvCount), 4);
|
||||
portal.vertices.resize(pvCount);
|
||||
f.read(reinterpret_cast<char*>(portal.vertices.data()), pvCount * 12);
|
||||
bld.portals.push_back(portal);
|
||||
}
|
||||
|
||||
for (uint32_t di = 0; di < doodadCount; di++) {
|
||||
WoweeBuilding::DoodadPlacement dp;
|
||||
uint16_t pl;
|
||||
f.read(reinterpret_cast<char*>(&pl), 2);
|
||||
dp.modelPath.resize(pl);
|
||||
f.read(dp.modelPath.data(), pl);
|
||||
f.read(reinterpret_cast<char*>(&dp.position), 12);
|
||||
f.read(reinterpret_cast<char*>(&dp.rotation), 12);
|
||||
f.read(reinterpret_cast<char*>(&dp.scale), 4);
|
||||
bld.doodads.push_back(dp);
|
||||
}
|
||||
|
||||
LOG_INFO("WOB loaded: ", basePath, " (", groupCount, " groups, ",
|
||||
portalCount, " portals, ", doodadCount, " doodads)");
|
||||
return bld;
|
||||
}
|
||||
|
||||
bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& basePath) {
|
||||
namespace fs = std::filesystem;
|
||||
fs::create_directories(fs::path(basePath).parent_path());
|
||||
|
||||
std::ofstream f(basePath + ".wob", std::ios::binary);
|
||||
if (!f) return false;
|
||||
|
||||
f.write(reinterpret_cast<const char*>(&WOB_MAGIC), 4);
|
||||
uint32_t gc = static_cast<uint32_t>(bld.groups.size());
|
||||
uint32_t pc = static_cast<uint32_t>(bld.portals.size());
|
||||
uint32_t dc = static_cast<uint32_t>(bld.doodads.size());
|
||||
f.write(reinterpret_cast<const char*>(&gc), 4);
|
||||
f.write(reinterpret_cast<const char*>(&pc), 4);
|
||||
f.write(reinterpret_cast<const char*>(&dc), 4);
|
||||
f.write(reinterpret_cast<const char*>(&bld.boundRadius), 4);
|
||||
|
||||
uint16_t nl = static_cast<uint16_t>(bld.name.size());
|
||||
f.write(reinterpret_cast<const char*>(&nl), 2);
|
||||
f.write(bld.name.data(), nl);
|
||||
|
||||
for (const auto& grp : bld.groups) {
|
||||
uint16_t gnl = static_cast<uint16_t>(grp.name.size());
|
||||
f.write(reinterpret_cast<const char*>(&gnl), 2);
|
||||
f.write(grp.name.data(), gnl);
|
||||
|
||||
uint32_t vc = static_cast<uint32_t>(grp.vertices.size());
|
||||
uint32_t ic = static_cast<uint32_t>(grp.indices.size());
|
||||
uint32_t tc = static_cast<uint32_t>(grp.texturePaths.size());
|
||||
f.write(reinterpret_cast<const char*>(&vc), 4);
|
||||
f.write(reinterpret_cast<const char*>(&ic), 4);
|
||||
f.write(reinterpret_cast<const char*>(&tc), 4);
|
||||
uint8_t outdoor = grp.isOutdoor ? 1 : 0;
|
||||
f.write(reinterpret_cast<const char*>(&outdoor), 1);
|
||||
f.write(reinterpret_cast<const char*>(&grp.boundMin), 12);
|
||||
f.write(reinterpret_cast<const char*>(&grp.boundMax), 12);
|
||||
|
||||
f.write(reinterpret_cast<const char*>(grp.vertices.data()),
|
||||
vc * sizeof(WoweeBuilding::Vertex));
|
||||
f.write(reinterpret_cast<const char*>(grp.indices.data()), ic * 4);
|
||||
|
||||
for (const auto& tp : grp.texturePaths) {
|
||||
uint16_t tl = static_cast<uint16_t>(tp.size());
|
||||
f.write(reinterpret_cast<const char*>(&tl), 2);
|
||||
f.write(tp.data(), tl);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& portal : bld.portals) {
|
||||
f.write(reinterpret_cast<const char*>(&portal.groupA), 4);
|
||||
f.write(reinterpret_cast<const char*>(&portal.groupB), 4);
|
||||
uint32_t pvCount = static_cast<uint32_t>(portal.vertices.size());
|
||||
f.write(reinterpret_cast<const char*>(&pvCount), 4);
|
||||
f.write(reinterpret_cast<const char*>(portal.vertices.data()), pvCount * 12);
|
||||
}
|
||||
|
||||
for (const auto& dp : bld.doodads) {
|
||||
uint16_t pl = static_cast<uint16_t>(dp.modelPath.size());
|
||||
f.write(reinterpret_cast<const char*>(&pl), 2);
|
||||
f.write(dp.modelPath.data(), pl);
|
||||
f.write(reinterpret_cast<const char*>(&dp.position), 12);
|
||||
f.write(reinterpret_cast<const char*>(&dp.rotation), 12);
|
||||
f.write(reinterpret_cast<const char*>(&dp.scale), 4);
|
||||
}
|
||||
|
||||
LOG_INFO("WOB saved: ", basePath, ".wob (", gc, " groups)");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue