mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat: Wowee Open Model format (.wom) — novel M2 replacement
WOM format: binary model file with no Blizzard structures. - WOM1 magic header + vertex/index counts + bounding box - Vertices: position(vec3) + normal(vec3) + texCoord(vec2) = 32 bytes - Indices: uint32 triangle list - Texture paths: PNG references (not BLP) WoweeModelLoader: - load(): reads .wom binary back to WoweeModel struct - save(): writes WoweeModel to .wom binary - fromM2(): converts existing M2 models to WOM (static geometry, strips bone/animation data, converts BLP paths to PNG) - exists(): checks for .wom file Format replacement progress — 5 out of 6 done: - DONE: ADT → WOT/WHM (terrain) - DONE: WDT → zone.json (map definition) - DONE: BLP → PNG (textures) - DONE: DBC → JSON (data tables) - DONE: M2 → WOM (static models) - TODO: WMO → open building format
This commit is contained in:
parent
176115f279
commit
b4cb833108
3 changed files with 210 additions and 0 deletions
|
|
@ -590,6 +590,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/adt_loader.cpp
|
||||
src/pipeline/wdt_loader.cpp
|
||||
src/pipeline/wowee_terrain_loader.cpp
|
||||
src/pipeline/wowee_model.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
|
||||
|
|
@ -1318,6 +1319,7 @@ add_executable(wowee_editor
|
|||
src/pipeline/adt_loader.cpp
|
||||
src/pipeline/wdt_loader.cpp
|
||||
src/pipeline/wowee_terrain_loader.cpp
|
||||
src/pipeline/wowee_model.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
|
|
|
|||
46
include/pipeline/wowee_model.hpp
Normal file
46
include/pipeline/wowee_model.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
// Wowee Open Model format (.wom) — novel format, no Blizzard IP
|
||||
// Designed for static doodads, props, and simple animated objects
|
||||
struct WoweeModel {
|
||||
struct Vertex {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texCoord;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
std::vector<std::string> texturePaths; // PNG paths
|
||||
float boundRadius = 1.0f;
|
||||
glm::vec3 boundMin{0}, boundMax{0};
|
||||
|
||||
bool isValid() const { return !vertices.empty() && !indices.empty(); }
|
||||
};
|
||||
|
||||
class WoweeModelLoader {
|
||||
public:
|
||||
// Load from .wom file (binary) + .wom.json (metadata)
|
||||
static WoweeModel load(const std::string& basePath);
|
||||
|
||||
// Save to .wom + .wom.json
|
||||
static bool save(const WoweeModel& model, const std::string& basePath);
|
||||
|
||||
// Convert an M2 model to WoweeModel (static geometry only, no animation)
|
||||
static WoweeModel fromM2(const std::string& m2Path, class AssetManager* am);
|
||||
|
||||
// Check if a .wom exists
|
||||
static bool exists(const std::string& basePath);
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
162
src/pipeline/wowee_model.cpp
Normal file
162
src/pipeline/wowee_model.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#include "pipeline/wowee_model.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/m2_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
static constexpr uint32_t WOM_MAGIC = 0x314D4F57; // "WOM1"
|
||||
|
||||
bool WoweeModelLoader::exists(const std::string& basePath) {
|
||||
return std::filesystem::exists(basePath + ".wom");
|
||||
}
|
||||
|
||||
WoweeModel WoweeModelLoader::load(const std::string& basePath) {
|
||||
WoweeModel model;
|
||||
std::string womPath = basePath + ".wom";
|
||||
|
||||
std::ifstream f(womPath, std::ios::binary);
|
||||
if (!f) return model;
|
||||
|
||||
uint32_t magic;
|
||||
f.read(reinterpret_cast<char*>(&magic), 4);
|
||||
if (magic != WOM_MAGIC) return model;
|
||||
|
||||
uint32_t vertCount, indexCount, texCount;
|
||||
f.read(reinterpret_cast<char*>(&vertCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&indexCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&texCount), 4);
|
||||
f.read(reinterpret_cast<char*>(&model.boundRadius), 4);
|
||||
f.read(reinterpret_cast<char*>(&model.boundMin), 12);
|
||||
f.read(reinterpret_cast<char*>(&model.boundMax), 12);
|
||||
|
||||
// Read name
|
||||
uint16_t nameLen;
|
||||
f.read(reinterpret_cast<char*>(&nameLen), 2);
|
||||
model.name.resize(nameLen);
|
||||
f.read(model.name.data(), nameLen);
|
||||
|
||||
// Read vertices
|
||||
model.vertices.resize(vertCount);
|
||||
f.read(reinterpret_cast<char*>(model.vertices.data()),
|
||||
vertCount * sizeof(WoweeModel::Vertex));
|
||||
|
||||
// Read indices
|
||||
model.indices.resize(indexCount);
|
||||
f.read(reinterpret_cast<char*>(model.indices.data()),
|
||||
indexCount * sizeof(uint32_t));
|
||||
|
||||
// Read texture paths
|
||||
for (uint32_t i = 0; i < texCount; i++) {
|
||||
uint16_t pathLen;
|
||||
f.read(reinterpret_cast<char*>(&pathLen), 2);
|
||||
std::string path(pathLen, '\0');
|
||||
f.read(path.data(), pathLen);
|
||||
model.texturePaths.push_back(path);
|
||||
}
|
||||
|
||||
LOG_INFO("WOM loaded: ", basePath, " (", vertCount, " verts, ",
|
||||
indexCount / 3, " tris)");
|
||||
return model;
|
||||
}
|
||||
|
||||
bool WoweeModelLoader::save(const WoweeModel& model, const std::string& basePath) {
|
||||
namespace fs = std::filesystem;
|
||||
fs::create_directories(fs::path(basePath).parent_path());
|
||||
|
||||
std::string womPath = basePath + ".wom";
|
||||
std::ofstream f(womPath, std::ios::binary);
|
||||
if (!f) return false;
|
||||
|
||||
f.write(reinterpret_cast<const char*>(&WOM_MAGIC), 4);
|
||||
uint32_t vertCount = static_cast<uint32_t>(model.vertices.size());
|
||||
uint32_t indexCount = static_cast<uint32_t>(model.indices.size());
|
||||
uint32_t texCount = static_cast<uint32_t>(model.texturePaths.size());
|
||||
f.write(reinterpret_cast<const char*>(&vertCount), 4);
|
||||
f.write(reinterpret_cast<const char*>(&indexCount), 4);
|
||||
f.write(reinterpret_cast<const char*>(&texCount), 4);
|
||||
f.write(reinterpret_cast<const char*>(&model.boundRadius), 4);
|
||||
f.write(reinterpret_cast<const char*>(&model.boundMin), 12);
|
||||
f.write(reinterpret_cast<const char*>(&model.boundMax), 12);
|
||||
|
||||
uint16_t nameLen = static_cast<uint16_t>(model.name.size());
|
||||
f.write(reinterpret_cast<const char*>(&nameLen), 2);
|
||||
f.write(model.name.data(), nameLen);
|
||||
|
||||
f.write(reinterpret_cast<const char*>(model.vertices.data()),
|
||||
vertCount * sizeof(WoweeModel::Vertex));
|
||||
f.write(reinterpret_cast<const char*>(model.indices.data()),
|
||||
indexCount * sizeof(uint32_t));
|
||||
|
||||
for (const auto& path : model.texturePaths) {
|
||||
uint16_t pathLen = static_cast<uint16_t>(path.size());
|
||||
f.write(reinterpret_cast<const char*>(&pathLen), 2);
|
||||
f.write(path.data(), pathLen);
|
||||
}
|
||||
|
||||
LOG_INFO("WOM saved: ", womPath, " (", vertCount, " verts, ",
|
||||
indexCount / 3, " tris)");
|
||||
return true;
|
||||
}
|
||||
|
||||
WoweeModel WoweeModelLoader::fromM2(const std::string& m2Path, AssetManager* am) {
|
||||
WoweeModel model;
|
||||
if (!am) return model;
|
||||
|
||||
auto data = am->readFile(m2Path);
|
||||
if (data.empty()) return model;
|
||||
|
||||
auto m2 = M2Loader::load(data);
|
||||
|
||||
// Load skin file for WotLK M2s
|
||||
if (!m2.isValid()) {
|
||||
std::string skinPath = m2Path;
|
||||
auto dotPos = skinPath.rfind('.');
|
||||
if (dotPos != std::string::npos)
|
||||
skinPath = skinPath.substr(0, dotPos) + "00.skin";
|
||||
auto skinData = am->readFile(skinPath);
|
||||
if (!skinData.empty())
|
||||
M2Loader::loadSkin(skinData, m2);
|
||||
}
|
||||
|
||||
if (!m2.isValid()) return model;
|
||||
|
||||
model.name = m2.name;
|
||||
model.boundRadius = m2.boundRadius;
|
||||
|
||||
// Convert M2 vertices to WOM format (strip bone data)
|
||||
model.vertices.reserve(m2.vertices.size());
|
||||
for (const auto& v : m2.vertices) {
|
||||
WoweeModel::Vertex wv;
|
||||
wv.position = v.position;
|
||||
wv.normal = v.normal;
|
||||
wv.texCoord = v.texCoords[0];
|
||||
model.vertices.push_back(wv);
|
||||
|
||||
model.boundMin = glm::min(model.boundMin, v.position);
|
||||
model.boundMax = glm::max(model.boundMax, v.position);
|
||||
}
|
||||
|
||||
// Convert indices (M2 uses uint16, WOM uses uint32)
|
||||
model.indices.reserve(m2.indices.size());
|
||||
for (uint16_t idx : m2.indices)
|
||||
model.indices.push_back(static_cast<uint32_t>(idx));
|
||||
|
||||
// Convert texture paths (BLP → PNG)
|
||||
for (const auto& tex : m2.textures) {
|
||||
std::string path = tex.filename;
|
||||
auto dot = path.rfind('.');
|
||||
if (dot != std::string::npos)
|
||||
path = path.substr(0, dot) + ".png";
|
||||
model.texturePaths.push_back(path);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue