From ee22face41f02225593723ec80e28d1778d93562 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 14 Aug 2021 13:57:32 +0200 Subject: [PATCH] Add T5 model dumping --- src/Common/Game/T5/CommonT5.cpp | 42 +- src/Common/Game/T5/CommonT5.h | 24 +- src/Common/Game/T5/T5_Assets.h | 22 +- .../AssetLoaders/AssetLoaderStringTable.cpp | 2 +- .../T5/AssetDumpers/AssetDumperXModel.cpp | 642 ++++++++++++++++++ .../Game/T5/AssetDumpers/AssetDumperXModel.h | 39 ++ src/ObjWriting/Game/T5/ZoneDumperT5.cpp | 65 +- 7 files changed, 785 insertions(+), 51 deletions(-) create mode 100644 src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp create mode 100644 src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h diff --git a/src/Common/Game/T5/CommonT5.cpp b/src/Common/Game/T5/CommonT5.cpp index b65cf7c9..c9e7c1a6 100644 --- a/src/Common/Game/T5/CommonT5.cpp +++ b/src/Common/Game/T5/CommonT5.cpp @@ -2,7 +2,11 @@ #include -int CommonT5::Com_HashKey(const char* str, const int maxLen) +#include "Utils/Pack.h" + +using namespace T5; + +int Common::Com_HashKey(const char* str, const int maxLen) { if (str == nullptr) return 0; @@ -19,7 +23,7 @@ int CommonT5::Com_HashKey(const char* str, const int maxLen) return hash ^ ((hash ^ (hash >> 10)) >> 10); } -int CommonT5::Com_HashString(const char* str) +int Common::Com_HashString(const char* str) { if (!str) return 0; @@ -35,7 +39,7 @@ int CommonT5::Com_HashString(const char* str) return result; } -int CommonT5::Com_HashString(const char* str, const int len) +int Common::Com_HashString(const char* str, const int len) { if (!str) return 0; @@ -52,4 +56,34 @@ int CommonT5::Com_HashString(const char* str, const int len) } return result; -} \ No newline at end of file +} + +PackedTexCoords Common::Vec2PackTexCoords(const vec2_t* in) +{ + return PackedTexCoords{Pack32::Vec2PackTexCoords(reinterpret_cast(in))}; +} + +PackedUnitVec Common::Vec3PackUnitVec(const vec3_t* in) +{ + return PackedUnitVec{Pack32::Vec3PackUnitVec(reinterpret_cast(in))}; +} + +GfxColor Common::Vec4PackGfxColor(const vec4_t* in) +{ + return GfxColor{Pack32::Vec4PackGfxColor(reinterpret_cast(in))}; +} + +void Common::Vec2UnpackTexCoords(const PackedTexCoords& in, vec2_t* out) +{ + Pack32::Vec2UnpackTexCoords(in.packed, reinterpret_cast(out)); +} + +void Common::Vec3UnpackUnitVec(const PackedUnitVec& in, vec3_t* out) +{ + Pack32::Vec3UnpackUnitVec(in.packed, reinterpret_cast(out)); +} + +void Common::Vec4UnpackGfxColor(const GfxColor& in, vec4_t* out) +{ + Pack32::Vec4UnpackGfxColor(in.packed, reinterpret_cast(out)); +} diff --git a/src/Common/Game/T5/CommonT5.h b/src/Common/Game/T5/CommonT5.h index baec165c..2f431858 100644 --- a/src/Common/Game/T5/CommonT5.h +++ b/src/Common/Game/T5/CommonT5.h @@ -1,9 +1,21 @@ #pragma once -class CommonT5 +#include "T5.h" + +namespace T5 { -public: - static int Com_HashKey(const char* str, int maxLen); - static int Com_HashString(const char* str); - static int Com_HashString(const char* str, int len); -}; \ No newline at end of file + class Common + { + public: + static int Com_HashKey(const char* str, int maxLen); + static int Com_HashString(const char* str); + static int Com_HashString(const char* str, int len); + + static PackedTexCoords Vec2PackTexCoords(const vec2_t* in); + static PackedUnitVec Vec3PackUnitVec(const vec3_t* in); + static GfxColor Vec4PackGfxColor(const vec4_t* in); + static void Vec2UnpackTexCoords(const PackedTexCoords& in, vec2_t* out); + static void Vec3UnpackUnitVec(const PackedUnitVec& in, vec3_t* out); + static void Vec4UnpackGfxColor(const GfxColor& in, vec4_t* out); + }; +} \ No newline at end of file diff --git a/src/Common/Game/T5/T5_Assets.h b/src/Common/Game/T5/T5_Assets.h index 299cfd12..ce8ab5cd 100644 --- a/src/Common/Game/T5/T5_Assets.h +++ b/src/Common/Game/T5/T5_Assets.h @@ -413,6 +413,12 @@ namespace T5 XAnimDeltaPart* deltaPart; }; + struct DObjSkelMat + { + float axis[3][4]; + float origin[4]; + }; + struct DObjAnimMat { float quat[4]; @@ -496,7 +502,7 @@ namespace T5 struct XSurface { char tileMode; - char vertListCount; + unsigned char vertListCount; uint16_t flags; uint16_t vertCount; uint16_t triCount; @@ -637,14 +643,14 @@ namespace T5 float radius; float mins[3]; float maxs[3]; - int16_t numLods; - int16_t collLod; + uint16_t numLods; + uint16_t collLod; XModelStreamInfo streamInfo; int memUsage; int flags; bool bad; PhysPreset* physPreset; - char numCollmaps; + unsigned char numCollmaps; Collmap* collmaps; PhysConstraints* physConstraints; }; @@ -748,12 +754,12 @@ namespace T5 { MaterialInfo info; char stateBitsEntry[130]; - char textureCount; - char constantCount; - char stateBitsCount; + unsigned char textureCount; + unsigned char constantCount; + unsigned char stateBitsCount; char stateFlags; char cameraRegion; - char maxStreamedMips; + unsigned char maxStreamedMips; MaterialTechniqueSet* techniqueSet; MaterialTextureDef* textureTable; MaterialConstantDef* constantTable; diff --git a/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderStringTable.cpp b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderStringTable.cpp index a5172535..81887a8c 100644 --- a/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderStringTable.cpp +++ b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderStringTable.cpp @@ -66,7 +66,7 @@ bool AssetLoaderStringTable::LoadFromRaw(const std::string& assetName, ISearchPa else cell.string = memory->Dup(rowValues[col].c_str()); - cell.hash = CommonT5::Com_HashString(cell.string); + cell.hash = Common::Com_HashString(cell.string); } } diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp new file mode 100644 index 00000000..5f41c570 --- /dev/null +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp @@ -0,0 +1,642 @@ +#include "AssetDumperXModel.h" + +#include +#include + +#include "ObjWriting.h" +#include "Game/T5/CommonT5.h" +#include "Math/Quaternion.h" +#include "Model/XModel/XModelExportWriter.h" +#include "Utils/HalfFloat.h" +#include "Utils/QuatInt16.h" + +using namespace T5; + +bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) +{ + return !asset->m_name.empty() && asset->m_name[0] != ','; +} + +GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) +{ + std::vector potentialTextureDefs; + + for(auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for(const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'c' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; +} + +GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) +{ + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; +} + +GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) +{ + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; +} + +void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) +{ + if (!model->materialHandles) + return; + + for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) + { + Material* material = model->materialHandles[surfIndex]; + if (!materialMapper.Add(material)) + continue; + + MtlMaterial mtl; + mtl.materialName = std::string(material->info.name); + + GfxImage* colorMap = GetMaterialColorMap(material); + GfxImage* normalMap = GetMaterialNormalMap(material); + GfxImage* specularMap = GetMaterialSpecularMap(material); + + if (colorMap != nullptr) + mtl.colorMapName = colorMap->name; + if (normalMap != nullptr) + mtl.normalMapName = normalMap->name; + if (specularMap != nullptr) + mtl.specularMapName = specularMap->name; + + writer.AddMaterial(std::move(mtl)); + } +} + +void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) +{ + const auto surfCount = model->lodInfo[lod].numsurfs; + const auto baseSurfIndex = model->lodInfo[lod].surfIndex; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + ObjObject object; + object.name = "surf" + std::to_string(surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); + + writer.AddObject(std::move(object)); + } +} + +void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModel* model, const unsigned lod) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + + ObjVertex objVertex{}; + ObjNormal objNormal{}; + ObjUv objUv{}; + objVertex.coordinates[0] = v.xyz[0]; + objVertex.coordinates[1] = v.xyz[2]; + objVertex.coordinates[2] = -v.xyz[1]; + objNormal.normal[0] = normalVec[0]; + objNormal.normal[1] = normalVec[2]; + objNormal.normal[2] = -normalVec[1]; + objUv.uv[0] = uv[0]; + objUv.uv[1] = 1.0f - uv[1]; + + writer.AddVertex(static_cast(surfIndex), objVertex); + writer.AddNormal(static_cast(surfIndex), objNormal); + writer.AddUv(static_cast(surfIndex), objUv); + } + } +} + +void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModel* model, const unsigned lod) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) + { + const auto& tri = surface.triIndices[triIndex]; + + ObjFace face{}; + face.vertexIndex[0] = tri[2] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[0] + surface.baseVertIndex; + face.normalIndex[0] = face.vertexIndex[0]; + face.normalIndex[1] = face.vertexIndex[1]; + face.normalIndex[2] = face.vertexIndex[2]; + face.uvIndex[0] = face.vertexIndex[0]; + face.uvIndex[1] = face.vertexIndex[1]; + face.uvIndex[2] = face.vertexIndex[2]; + writer.AddFace(static_cast(surfIndex), face); + } + } +} + +void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); + + if (!matFile) + return; + + ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + AddObjMaterials(writer, materialMapper, model); + writer.WriteMtl(*matFile); +} + +void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) +{ + const auto* model = asset->Asset(); + std::ostringstream ss; + ss << "model_export/" << model->name << "_lod" << lod << ".OBJ"; + + const auto assetFile = context.OpenAssetFile(ss.str()); + + if (!assetFile) + return; + + ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + AddObjMaterials(writer, materialMapper, model); + AddObjObjects(writer, materialMapper, model, lod); + AddObjVertices(writer, model, lod); + AddObjFaces(writer, model, lod); + + writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); +} + +void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* model = asset->Asset(); + + DumpObjMat(context, asset); + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + DumpObjLod(context, asset, currentLod); + } +} + +void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) +{ + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; + bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; + bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; + bone.globalRotation = Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32( + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]) + ); + } + + writer.AddBone(std::move(bone)); + } +} + +void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) +{ + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = material->info.name; + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = std::string(colorMap->name); + + writer.AddMaterial(std::move(xMaterial)); + } + } +} + +void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) +{ + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + XModelObject object; + object.name = "surf" + std::to_string(surfIndex); + + writer.AddObject(std::move(object)); + } +} + +void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + vec4_t color; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz[0]; + vertex.coordinates[1] = v.xyz[1]; + vertex.coordinates[2] = v.xyz[2]; + vertex.normal[0] = normalVec[0]; + vertex.normal[1] = normalVec[1]; + vertex.normal[2] = normalVec[2]; + vertex.color[0] = color[0]; + vertex.color[1] = color[1]; + vertex.color[2] = color[2]; + vertex.color[3] = color[3]; + vertex.uv[0] = uv[0]; + vertex.uv[1] = uv[1]; + + writer.AddVertex(vertex); + } + } +} + +void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + weightCollection.totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertList) + { + weightCollection.totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; + weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; + weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; + weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); +} + +void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), + 1.0f + }; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + boneWeightOffset, + 1 + }); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex0, + 1.0f + }; + + vertsBlendOffset += 1; + + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + boneWeightOffset, + 1 + }); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex0, + boneWeight0 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex1, + boneWeight1 + }; + + vertsBlendOffset += 3; + + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + boneWeightOffset, + 2 + }); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex0, + boneWeight0 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex1, + boneWeight1 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex2, + boneWeight2 + }; + + vertsBlendOffset += 5; + + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + boneWeightOffset, + 3 + }); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex0, + boneWeight0 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex1, + boneWeight1 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex2, + boneWeight2 + }; + weightCollection.weights[weightOffset++] = XModelBoneWeight{ + boneIndex3, + boneWeight3 + }; + + vertsBlendOffset += 7; + + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + boneWeightOffset, + 4 + }); + } + + handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + writer.AddVertexBoneWeights(XModelVertexBoneWeights{ + nullptr, + 0 + }); + } + } +} + +void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) +{ + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + const auto baseSurfIndex = model->lodInfo[lod].surfIndex; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + face.objectIndex = static_cast(surfIndex); + face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); + writer.AddFace(face); + } + } +} + +void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) +{ + const auto* model = asset->Asset(); + + std::ostringstream ss; + ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; + + const auto assetFile = context.OpenAssetFile(ss.str()); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + XModelVertexBoneWeightCollection boneWeightCollection; + AllocateXModelBoneWeights(model, lod, boneWeightCollection); + + AddXModelBones(context, *writer, model); + AddXModelMaterials(*writer, materialMapper, model); + AddXModelObjects(*writer, model, lod); + AddXModelVertices(*writer, model, lod); + AddXModelVertexBoneWeights(*writer, model, lod, boneWeightCollection); + AddXModelFaces(*writer, materialMapper, model, lod); + + writer->Write(*assetFile); +} + +void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* model = asset->Asset(); + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + DumpXModelExportLod(context, asset, currentLod); + } +} + +void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObj(context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExport(context, asset); + break; + + default: + assert(false); + break; + } +} diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h new file mode 100644 index 00000000..41f16b53 --- /dev/null +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h @@ -0,0 +1,39 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/T5/T5.h" +#include "Utils/DistinctMapper.h" +#include "Model/XModel/AbstractXModelWriter.h" +#include "Model/Obj/ObjWriter.h" + +namespace T5 +{ + class AssetDumperXModel final : public AbstractAssetDumper + { + static GfxImage* GetMaterialColorMap(const Material* material); + static GfxImage* GetMaterialNormalMap(const Material* material); + static GfxImage* GetMaterialSpecularMap(const Material* material); + + static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); + static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); + static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod); + static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod); + static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); + static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); + static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); + + static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); + static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); + static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod); + static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod); + static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); + static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); + static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); + static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); + static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset); + + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + }; +} diff --git a/src/ObjWriting/Game/T5/ZoneDumperT5.cpp b/src/ObjWriting/Game/T5/ZoneDumperT5.cpp index cefa7564..b78b0165 100644 --- a/src/ObjWriting/Game/T5/ZoneDumperT5.cpp +++ b/src/ObjWriting/Game/T5/ZoneDumperT5.cpp @@ -11,6 +11,7 @@ #include "AssetDumpers/AssetDumperPhysPreset.h" #include "AssetDumpers/AssetDumperSndBank.h" #include "AssetDumpers/AssetDumperWeapon.h" +#include "AssetDumpers/AssetDumperXModel.h" using namespace T5; @@ -30,38 +31,38 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const const auto* assetPools = dynamic_cast(context.m_zone->m_pools.get()); - // DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset); - // DUMP_ASSET_POOL(AssetDumperPhysConstraints, m_phys_constraints); - // DUMP_ASSET_POOL(AssetDumperDestructibleDef, m_destructible_def); - // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts); - // DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel); - // DUMP_ASSET_POOL(AssetDumperMaterial, m_material); - // DUMP_ASSET_POOL(AssetDumperTechniqueSet, m_technique_set); - DUMP_ASSET_POOL(AssetDumperGfxImage, m_image); - // DUMP_ASSET_POOL(AssetDumperSndBank, m_sound_bank); - // DUMP_ASSET_POOL(AssetDumperSndPatch, m_sound_patch); - // DUMP_ASSET_POOL(AssetDumperClipMap, m_clip_map); - // DUMP_ASSET_POOL(AssetDumperComWorld, m_com_world); - // DUMP_ASSET_POOL(AssetDumperGameWorldSp, m_game_world_sp); - // DUMP_ASSET_POOL(AssetDumperGameWorldMp, m_game_world_mp); - // DUMP_ASSET_POOL(AssetDumperMapEnts, m_map_ents); - // DUMP_ASSET_POOL(AssetDumperGfxWorld, m_gfx_world); - // DUMP_ASSET_POOL(AssetDumperGfxLightDef, m_gfx_light_def); - // DUMP_ASSET_POOL(AssetDumperFont, m_font); - // DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list); - // DUMP_ASSET_POOL(AssetDumperMenuDef, m_menu_def); - DUMP_ASSET_POOL(AssetDumperLocalizeEntry, m_localize); - // DUMP_ASSET_POOL(AssetDumperWeapon, m_weapon); - // DUMP_ASSET_POOL(AssetDumperSndDriverGlobals, m_snd_driver_globals); - // DUMP_ASSET_POOL(AssetDumperFxEffectDef, m_fx); - // DUMP_ASSET_POOL(AssetDumperFxImpactTable, m_fx_impact_table); - DUMP_ASSET_POOL(AssetDumperRawFile, m_raw_file); - DUMP_ASSET_POOL(AssetDumperStringTable, m_string_table); - // DUMP_ASSET_POOL(AssetDumperPackIndex, m_pack_index); - // DUMP_ASSET_POOL(AssetDumperXGlobals, m_xglobals); - // DUMP_ASSET_POOL(AssetDumperDDLRoot, m_ddl); - // DUMP_ASSET_POOL(AssetDumperGlasses, m_glasses); - // DUMP_ASSET_POOL(AssetDumperEmblemSet, m_emblem_set); + // DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset) + // DUMP_ASSET_POOL(AssetDumperPhysConstraints, m_phys_constraints) + // DUMP_ASSET_POOL(AssetDumperDestructibleDef, m_destructible_def) + // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts) + DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel) + // DUMP_ASSET_POOL(AssetDumperMaterial, m_material) + // DUMP_ASSET_POOL(AssetDumperTechniqueSet, m_technique_set) + DUMP_ASSET_POOL(AssetDumperGfxImage, m_image) + // DUMP_ASSET_POOL(AssetDumperSndBank, m_sound_bank) + // DUMP_ASSET_POOL(AssetDumperSndPatch, m_sound_patch) + // DUMP_ASSET_POOL(AssetDumperClipMap, m_clip_map) + // DUMP_ASSET_POOL(AssetDumperComWorld, m_com_world) + // DUMP_ASSET_POOL(AssetDumperGameWorldSp, m_game_world_sp) + // DUMP_ASSET_POOL(AssetDumperGameWorldMp, m_game_world_mp) + // DUMP_ASSET_POOL(AssetDumperMapEnts, m_map_ents) + // DUMP_ASSET_POOL(AssetDumperGfxWorld, m_gfx_world) + // DUMP_ASSET_POOL(AssetDumperGfxLightDef, m_gfx_light_def) + // DUMP_ASSET_POOL(AssetDumperFont, m_font) + // DUMP_ASSET_POOL(AssetDumperMenuList, m_menu_list) + // DUMP_ASSET_POOL(AssetDumperMenuDef, m_menu_def) + DUMP_ASSET_POOL(AssetDumperLocalizeEntry, m_localize) + // DUMP_ASSET_POOL(AssetDumperWeapon, m_weapon) + // DUMP_ASSET_POOL(AssetDumperSndDriverGlobals, m_snd_driver_globals) + // DUMP_ASSET_POOL(AssetDumperFxEffectDef, m_fx) + // DUMP_ASSET_POOL(AssetDumperFxImpactTable, m_fx_impact_table) + DUMP_ASSET_POOL(AssetDumperRawFile, m_raw_file) + DUMP_ASSET_POOL(AssetDumperStringTable, m_string_table) + // DUMP_ASSET_POOL(AssetDumperPackIndex, m_pack_index) + // DUMP_ASSET_POOL(AssetDumperXGlobals, m_xglobals) + // DUMP_ASSET_POOL(AssetDumperDDLRoot, m_ddl) + // DUMP_ASSET_POOL(AssetDumperGlasses, m_glasses) + // DUMP_ASSET_POOL(AssetDumperEmblemSet, m_emblem_set) return true;