diff --git a/src/Common/Game/IW5/CommonIW5.cpp b/src/Common/Game/IW5/CommonIW5.cpp index e69de29b..00965d1d 100644 --- a/src/Common/Game/IW5/CommonIW5.cpp +++ b/src/Common/Game/IW5/CommonIW5.cpp @@ -0,0 +1,35 @@ +#include "CommonIW5.h" + +#include "Utils/Pack.h" + +using namespace IW5; + +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/IW5/CommonIW5.h b/src/Common/Game/IW5/CommonIW5.h index 6c1b246a..a081144c 100644 --- a/src/Common/Game/IW5/CommonIW5.h +++ b/src/Common/Game/IW5/CommonIW5.h @@ -1,5 +1,17 @@ #pragma once +#include "IW5.h" + namespace IW5 { + class Common + { + public: + 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); + }; } diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 7f33e5b1..2b0aa3a4 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -179,6 +179,7 @@ namespace IW5 typedef float vec2_t[2]; typedef float vec3_t[3]; + typedef float vec4_t[4]; typedef tdef_align(16) uint16_t r_index16_t; typedef tdef_align(16) char raw_byte16; @@ -422,6 +423,12 @@ namespace IW5 XAnimDeltaPart* deltaPart; }; + struct DObjSkelMat + { + float axis[3][4]; + float origin[4]; + }; + struct XSurfaceVertexInfo { short vertCount[4]; @@ -450,8 +457,8 @@ namespace IW5 union GfxColor { - unsigned char array[4]; unsigned int packed; + unsigned char array[4]; }; struct GfxQuantizedVertex @@ -526,9 +533,9 @@ namespace IW5 unsigned short vertCount; unsigned short triCount; char zoneHandle; - float quantizeScale; uint16_t baseTriIndex; uint16_t baseVertIndex; + float quantizeScale; r_index16_t(*triIndices)[3]; XSurfaceVertexInfo vertInfo; GfxVertexUnion0 verts0; diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp new file mode 100644 index 00000000..690bb8db --- /dev/null +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp @@ -0,0 +1,575 @@ +#include "AssetDumperXModel.h" + +#include + +#include "ObjWriting.h" +#include "Game/IW5/CommonIW5.h" +#include "Math/Quaternion.h" +#include "Model/XModel/XModelExportWriter.h" +#include "Utils/HalfFloat.h" +#include "Utils/QuatInt16.h" + +using namespace IW5; + +bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) +{ + return !asset->m_name.empty() && asset->m_name[0] != ','; +} + +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 = nullptr; + GfxImage* normalMap = nullptr; + GfxImage* specularMap = nullptr; + + for (auto i = 0u; i < material->textureCount; i++) + { + const auto& texture = material->textureTable[i]; + + switch (texture.semantic) + { + case TS_COLOR_MAP: + if(texture.nameStart == 'c' && texture.nameEnd == 'p') + colorMap = texture.u.image; + break; + + // Disabled due to looking weird in Blender + // case TS_NORMAL_MAP: + // normalMap = texture.u.image; + // break; + + case TS_SPECULAR_MAP: + specularMap = texture.u.image; + break; + + default: + break; + } + } + + 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 XModelSurfs* modelSurfs, int baseSurfaceIndex) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + ObjObject object; + object.name = "surf" + std::to_string(surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + writer.AddObject(std::move(object)); + } +} + +void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0.packedVerts0[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 XModelSurfs* modelSurfs) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->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(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(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) + return; + + const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".obj"); + + 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, modelSurfs, model->lodInfo[lod].surfIndex); + AddObjVertices(writer, modelSurfs); + AddObjFaces(writer, modelSurfs); + + 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; + + for (auto textureIndex = 0; textureIndex < material->textureCount; textureIndex++) + { + const auto* textureTableEntry = &material->textureTable[textureIndex]; + if (textureTableEntry->semantic == TS_COLOR_MAP && textureTableEntry->nameStart == 'c' && textureTableEntry->nameEnd == 'p' && textureTableEntry->u.image) + { + xMaterial.colorMapName = textureTableEntry->u.image->name; + break; + } + } + + writer.AddMaterial(std::move(xMaterial)); + } + } +} + +void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + XModelObject object; + object.name = "surf" + std::to_string(surfIndex); + + writer.AddObject(std::move(object)); + } +} + +void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0.packedVerts0[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 XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) +{ + weightCollection.totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->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 XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) +{ + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->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 XModelSurfs* modelSurfs, + const int baseSurfaceIndex) +{ + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->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 + baseSurfaceIndex)); + writer.AddFace(face); + } + } +} + +void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) +{ + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) + return; + + const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".XMODEL_EXPORT"); + + 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(modelSurfs, boneWeightCollection); + + AddXModelBones(context, *writer, model); + AddXModelMaterials(*writer, materialMapper, model); + AddXModelObjects(*writer, modelSurfs); + AddXModelVertices(*writer, modelSurfs); + AddXModelVertexBoneWeights(*writer, modelSurfs, boneWeightCollection); + AddXModelFaces(*writer, materialMapper, modelSurfs, model->lodInfo[lod].surfIndex); + + 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/IW5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h new file mode 100644 index 00000000..b67d52c5 --- /dev/null +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW5/IW5.h" +#include "Utils/DistinctMapper.h" +#include "Model/XModel/AbstractXModelWriter.h" +#include "Model/Obj/ObjWriter.h" + +namespace IW5 +{ + class AssetDumperXModel final : public AbstractAssetDumper + { + static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); + static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); + static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs); + static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs); + static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); + static void DumpObjMat(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 XModelSurfs* modelSurfs); + static void AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs); + static void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); + static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); + static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); + 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/IW5/ZoneDumperIW5.cpp b/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp index 508deb3c..8dcac040 100644 --- a/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp @@ -9,6 +9,7 @@ #include "AssetDumpers/AssetDumperLocalizeEntry.h" #include "AssetDumpers/AssetDumperRawFile.h" #include "AssetDumpers/AssetDumperStringTable.h" +#include "AssetDumpers/AssetDumperXModel.h" using namespace IW5; @@ -31,7 +32,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperPhysCollmap, m_phys_collmap) // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts) // DUMP_ASSET_POOL(AssetDumperXModelSurfs, m_xmodel_surfs) - // DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel) + DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel) // DUMP_ASSET_POOL(AssetDumperMaterial, m_material) // DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader) // DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader)