refactor(terrain): use WoweeModelLoader::toM2 for WOM loading in main game

terrain_manager.cpp had a 70-line duplicate of the WOM->M2 conversion that
ignored WOM3 multi-batch support. Replaced with a single toM2() call.
Also extended toM2 to copy bones and animation sequence headers so the
shared helper now produces a fully renderable M2Model from any WOM
version, with main game and editor both using the same code path.
This commit is contained in:
Kelsi 2026-05-06 01:38:31 -07:00
parent 23951d4075
commit a711a92875
2 changed files with 27 additions and 66 deletions

View file

@ -459,6 +459,29 @@ M2Model WoweeModelLoader::toM2(const WoweeModel& wom) {
m.materials.push_back(mat);
}
// Copy bones (WOM2/WOM3) — pivot/parent only, animation tracks are filled
// from the WoM animation block below.
for (const auto& wb : wom.bones) {
M2Bone bone;
bone.keyBoneId = wb.keyBoneId;
bone.parentBone = wb.parentBone;
bone.pivot = wb.pivot;
bone.flags = wb.flags;
m.bones.push_back(bone);
}
// Copy animation sequence headers (id/duration/movingSpeed). Per-bone
// keyframes inside WoM are richer than M2Sequence captures so a future
// animator may want a deeper conversion; this is enough for length-based
// selection in the renderer.
for (const auto& wa : wom.animations) {
M2Sequence seq;
seq.id = wa.id;
seq.duration = wa.durationMs;
seq.movingSpeed = wa.movingSpeed;
m.sequences.push_back(seq);
}
return m;
}

View file

@ -493,74 +493,12 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
if (pipeline::WoweeModelLoader::exists(womPath)) {
auto wom = pipeline::WoweeModelLoader::load(womPath);
if (wom.isValid()) {
// Convert WOM to M2Model for the renderer
pipeline::M2Model m2Model;
m2Model.name = wom.name;
m2Model.boundRadius = wom.boundRadius;
m2Model.vertices.reserve(wom.vertices.size());
for (const auto& v : wom.vertices) {
pipeline::M2Vertex mv;
mv.position = v.position;
mv.normal = v.normal;
mv.texCoords[0] = v.texCoord;
std::memcpy(mv.boneWeights, v.boneWeights, 4);
std::memcpy(mv.boneIndices, v.boneIndices, 4);
m2Model.vertices.push_back(mv);
}
m2Model.indices.reserve(wom.indices.size());
for (uint32_t idx : wom.indices)
m2Model.indices.push_back(static_cast<uint16_t>(idx));
// Set up textures from WOM paths
for (const auto& texPath : wom.texturePaths) {
pipeline::M2Texture tex;
tex.type = 0;
tex.flags = 0;
tex.filename = texPath;
m2Model.textures.push_back(tex);
}
m2Model.textureLookup = {0};
// Create default render batch covering all geometry
pipeline::M2Batch batch{};
batch.flags = 0;
batch.shader = 0;
batch.textureCount = std::min(1u, static_cast<uint32_t>(wom.texturePaths.size()));
batch.textureIndex = 0;
batch.indexStart = 0;
batch.indexCount = static_cast<uint32_t>(m2Model.indices.size());
batch.vertexStart = 0;
batch.vertexCount = static_cast<uint32_t>(m2Model.vertices.size());
m2Model.batches.push_back(batch);
// Default opaque material
pipeline::M2Material mat;
mat.flags = 0;
mat.blendMode = 0;
m2Model.materials.push_back(mat);
// Copy bone hierarchy from WOM2
for (const auto& wb : wom.bones) {
pipeline::M2Bone bone;
bone.keyBoneId = wb.keyBoneId;
bone.parentBone = wb.parentBone;
bone.pivot = wb.pivot;
bone.flags = wb.flags;
m2Model.bones.push_back(bone);
}
// Copy animation sequences from WOM2
for (const auto& wa : wom.animations) {
pipeline::M2Sequence seq;
seq.id = wa.id;
seq.duration = wa.durationMs;
seq.movingSpeed = wa.movingSpeed;
m2Model.sequences.push_back(seq);
}
// toM2() handles WOM1/WOM2/WOM3 (multi-batch + materials)
auto m2Model = pipeline::WoweeModelLoader::toM2(wom);
pending->m2Models.push_back({modelId, std::move(m2Model), {}});
preparedModelIds.insert(modelId);
LOG_INFO("Loaded WOM model: ", womPath);
LOG_INFO("Loaded WOM model: ", womPath, " (v", wom.version,
", ", wom.batches.size(), " batches)");
return true;
}
}