diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp index 776f68bb..1902ca10 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp @@ -3,591 +3,617 @@ #include "Game/IW3/CommonIW3.h" #include "Math/Quaternion.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.h" #include "Utils/QuatInt16.h" +#include "XModel/AbstractXModelWriter.h" #include "XModel/Export/XModelExportWriter.h" +#include "XModel/Gltf/GltfBinOutput.h" +#include "XModel/Gltf/GltfTextOutput.h" +#include "XModel/Gltf/GltfWriter.h" +#include "XModel/Obj/ObjWriter.h" #include -#include +#include using namespace IW3; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* 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) + 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* 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* 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 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 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 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 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 DumpObjMat(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name)); + + 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 DumpObjLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".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, model, lod); + AddObjVertices(writer, model, lod); + AddObjFaces(writer, model, lod); + + writer.WriteObj(*assetFile, std::format("{}.mtl", model->name)); + } + + void 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 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 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 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 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 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 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 PopulateXModelWriter(const AssetDumpingContext& context, const unsigned lod, const XModel* model, AbstractXModelWriter& writer) + { + 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); + } + + void DumpXModelExportLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + template void DumpGltfLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + if (ObjWriting::Configuration.ModelOutputFormat == ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ) + DumpObjMat(context, asset); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + 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) - 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; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h index a304d3b9..e8aa5df8 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW3/IW3.h" -#include "Utils/DistinctMapper.h" -#include "XModel/AbstractXModelWriter.h" -#include "XModel/Obj/ObjWriter.h" namespace IW3 { 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/IW4/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp index da956c9b..93141199 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp @@ -3,15 +3,22 @@ #include "Game/IW4/CommonIW4.h" #include "Math/Quaternion.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.h" #include "Utils/QuatInt16.h" +#include "XModel/AbstractXModelWriter.h" #include "XModel/Export/XModelExportWriter.h" +#include "XModel/Gltf/GltfBinOutput.h" +#include "XModel/Gltf/GltfTextOutput.h" +#include "XModel/Gltf/GltfWriter.h" +#include "XModel/Obj/ObjWriter.h" #include +#include using namespace IW4; -namespace IW4 +namespace { class SurfsDumpingZoneState final : public IZoneAssetDumperState { @@ -20,579 +27,589 @@ namespace IW4 public: bool ShouldDumpTechnique(const XModelSurfs* surfs) { - if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end()) + if (m_dumped_surfs.contains(surfs)) return false; m_dumped_surfs.emplace(surfs); return true; } }; -} // namespace IW4 + + GfxImage* 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) + 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* 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* 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 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 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 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[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 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 DumpObjMat(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name)); + + 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 DumpObjLod(const AssetDumpingContext& context, const 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(std::format("model_export/{}.obj", modelSurfs->name)); + + 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::format("{}.mtl", model->name)); + } + + void 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 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 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 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[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 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 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 + 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 PopulateXModelWriter(const AssetDumpingContext& context, const unsigned lod, const XModel* model, AbstractXModelWriter& writer) + { + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + 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); + } + + void DumpXModelExportLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.XMODEL_EXPORT", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + template void DumpGltfLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}{}", modelSurfs->name, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + if (ObjWriting::Configuration.ModelOutputFormat == ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ) + DumpObjMat(context, asset); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace 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) - 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 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[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(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(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) + ".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(); - auto* surfZoneState = context.GetZoneAssetDumperState(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - - 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 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[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(AssetDumpingContext& context, XAssetInfo* asset) -{ - auto* surfZoneState = context.GetZoneAssetDumperState(); - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - 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; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h index 0d2b058d..84be5262 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW4/IW4.h" -#include "Utils/DistinctMapper.h" -#include "XModel/AbstractXModelWriter.h" -#include "XModel/Obj/ObjWriter.h" namespace IW4 { 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 XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const 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 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(AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp index b5aef51c..8d986960 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp @@ -3,15 +3,22 @@ #include "Game/IW5/CommonIW5.h" #include "Math/Quaternion.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.h" #include "Utils/QuatInt16.h" +#include "XModel/AbstractXModelWriter.h" #include "XModel/Export/XModelExportWriter.h" +#include "XModel/Gltf/GltfBinOutput.h" +#include "XModel/Gltf/GltfTextOutput.h" +#include "XModel/Gltf/GltfWriter.h" +#include "XModel/Obj/ObjWriter.h" #include +#include using namespace IW5; -namespace IW5 +namespace { class SurfsDumpingZoneState final : public IZoneAssetDumperState { @@ -20,578 +27,588 @@ namespace IW5 public: bool ShouldDumpTechnique(const XModelSurfs* surfs) { - if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end()) + if (m_dumped_surfs.contains(surfs)) return false; m_dumped_surfs.emplace(surfs); return true; } }; -} // namespace IW5 + + GfxImage* 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) + 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* 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* 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 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 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 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 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 DumpObjMat(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name)); + + 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 DumpObjLod(const AssetDumpingContext& context, const 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(std::format("model_export/{}.obj", modelSurfs->name)); + + 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::format("{}.mtl", model->name)); + } + + void 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 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 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 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 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 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 + 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 PopulateXModelWriter(const AssetDumpingContext& context, const unsigned lod, const XModel* model, AbstractXModelWriter& writer) + { + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + 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); + } + + void DumpXModelExportLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.XMODEL_EXPORT", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + template void DumpGltfLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}{}", modelSurfs->name, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + if (ObjWriting::Configuration.ModelOutputFormat == ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ) + DumpObjMat(context, asset); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace 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) - 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 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(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(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) + ".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(); - auto* surfZoneState = context.GetZoneAssetDumperState(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - - 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 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(AssetDumpingContext& context, XAssetInfo* asset) -{ - auto* surfZoneState = context.GetZoneAssetDumperState(); - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - 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; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h index 6a6196a5..28880a59 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW5/IW5.h" -#include "Utils/DistinctMapper.h" -#include "XModel/AbstractXModelWriter.h" -#include "XModel/Obj/ObjWriter.h" namespace IW5 { 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 XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const 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 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(AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp index ef75590a..3cf83c41 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp @@ -3,591 +3,617 @@ #include "Game/T5/CommonT5.h" #include "Math/Quaternion.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.h" #include "Utils/QuatInt16.h" +#include "XModel/AbstractXModelWriter.h" #include "XModel/Export/XModelExportWriter.h" +#include "XModel/Gltf/GltfBinOutput.h" +#include "XModel/Gltf/GltfTextOutput.h" +#include "XModel/Gltf/GltfWriter.h" +#include "XModel/Obj/ObjWriter.h" #include -#include +#include using namespace T5; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* 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* 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* 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 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 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 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 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 DumpObjMat(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name)); + + 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 DumpObjLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".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, model, lod); + AddObjVertices(writer, model, lod); + AddObjFaces(writer, model, lod); + + writer.WriteObj(*assetFile, std::format("{}.mtl", model->name)); + } + + void 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 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 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 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 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 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 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 PopulateXModelWriter(const AssetDumpingContext& context, const unsigned lod, const XModel* model, AbstractXModelWriter& writer) + { + 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); + } + + void DumpXModelExportLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + template void DumpGltfLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + if (ObjWriting::Configuration.ModelOutputFormat == ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ) + DumpObjMat(context, asset); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + 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; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h index 155d79d0..08a1a274 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/T5/T5.h" -#include "Utils/DistinctMapper.h" -#include "XModel/AbstractXModelWriter.h" -#include "XModel/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/T6/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp index 712cf309..50753b03 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp @@ -3,615 +3,641 @@ #include "Game/T6/CommonT6.h" #include "Math/Quaternion.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.h" #include "Utils/QuatInt16.h" +#include "XModel/AbstractXModelWriter.h" #include "XModel/Export/XModelExportWriter.h" +#include "XModel/Gltf/GltfBinOutput.h" +#include "XModel/Gltf/GltfTextOutput.h" +#include "XModel/Gltf/GltfWriter.h" +#include "XModel/Obj/ObjWriter.h" #include -#include +#include using namespace T6; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* 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]->image; + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') + return def->image; + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') + return def->image; + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + GfxImage* 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]->image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + GfxImage* 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]->image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + void 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 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 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.x; + objVertex.coordinates[1] = v.xyz.z; + objVertex.coordinates[2] = -v.xyz.y; + objNormal.normal[0] = normalVec.x; + objNormal.normal[1] = normalVec.z; + objNormal.normal[2] = -normalVec.y; + objUv.uv[0] = uv.x; + objUv.uv[1] = 1.0f - uv.y; + + writer.AddVertex(static_cast(surfIndex), objVertex); + writer.AddNormal(static_cast(surfIndex), objNormal); + writer.AddUv(static_cast(surfIndex), objUv); + } + } + } + + void 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 DumpObjMat(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name)); + + 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 DumpObjLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".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, model, lod); + AddObjVertices(writer, model, lod); + AddObjFaces(writer, model, lod); + + writer.WriteObj(*assetFile, std::format("{}.mtl", model->name)); + } + + void 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.x; + bone.globalOffset[1] = model->baseMat[boneNum].trans.y; + bone.globalOffset[2] = model->baseMat[boneNum].trans.z; + bone.globalRotation = + Quaternion32(model->baseMat[boneNum].quat.x, model->baseMat[boneNum].quat.y, model->baseMat[boneNum].quat.z, model->baseMat[boneNum].quat.w); + + 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 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 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 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; + + if (!surfs) + return; + + 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.x; + vertex.coordinates[1] = v.xyz.y; + vertex.coordinates[2] = v.xyz.z; + vertex.normal[0] = normalVec.x; + vertex.normal[1] = normalVec.y; + vertex.normal[2] = normalVec.z; + vertex.color[0] = color.x; + vertex.color[1] = color.y; + vertex.color[2] = color.z; + vertex.color[3] = color.w; + vertex.uv[0] = uv.x; + vertex.uv[1] = uv.y; + + writer.AddVertex(vertex); + } + } + } + + void 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; + + if (!surfs) + return; + + 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 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; + + if (!surfs) + return; + + 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 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; + + if (!surfs) + return; + + 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 PopulateXModelWriter(const AssetDumpingContext& context, const unsigned lod, const XModel* model, AbstractXModelWriter& writer) + { + 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); + } + + void DumpXModelExportLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + template void DumpGltfLod(const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + PopulateXModelWriter(context, lod, model, *writer); + + writer->Write(*assetFile); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + if (ObjWriting::Configuration.ModelOutputFormat == ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ) + DumpObjMat(context, asset); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + 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]->image; - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') - return def->image; - } - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') - return def->image; - } - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') - return def->image; - } - - return potentialTextureDefs[0]->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]->image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->image; - } - - return potentialTextureDefs[0]->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]->image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->image; - } - - return potentialTextureDefs[0]->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.x; - objVertex.coordinates[1] = v.xyz.z; - objVertex.coordinates[2] = -v.xyz.y; - objNormal.normal[0] = normalVec.x; - objNormal.normal[1] = normalVec.z; - objNormal.normal[2] = -normalVec.y; - objUv.uv[0] = uv.x; - objUv.uv[1] = 1.0f - uv.y; - - 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.x; - bone.globalOffset[1] = model->baseMat[boneNum].trans.y; - bone.globalOffset[2] = model->baseMat[boneNum].trans.z; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat.x, model->baseMat[boneNum].quat.y, model->baseMat[boneNum].quat.z, model->baseMat[boneNum].quat.w); - - 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; - - if (!surfs) - return; - - 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.x; - vertex.coordinates[1] = v.xyz.y; - vertex.coordinates[2] = v.xyz.z; - vertex.normal[0] = normalVec.x; - vertex.normal[1] = normalVec.y; - vertex.normal[2] = normalVec.z; - vertex.color[0] = color.x; - vertex.color[1] = color.y; - vertex.color[2] = color.z; - vertex.color[3] = color.w; - vertex.uv[0] = uv.x; - vertex.uv[1] = uv.y; - - 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; - - if (!surfs) - return; - - 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; - - if (!surfs) - return; - - 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; - - if (!surfs) - return; - - 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; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h index fd56c376..75218729 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/T6/T6.h" -#include "Utils/DistinctMapper.h" -#include "XModel/AbstractXModelWriter.h" -#include "XModel/Obj/ObjWriter.h" namespace T6 { 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/ObjWriting.h b/src/ObjWriting/ObjWriting.h index e69903e2..e8796b2b 100644 --- a/src/ObjWriting/ObjWriting.h +++ b/src/ObjWriting/ObjWriting.h @@ -20,14 +20,16 @@ public: enum class ModelOutputFormat_e { XMODEL_EXPORT, - OBJ + OBJ, + GLTF, + GLB }; bool Verbose = false; std::vector AssetTypesToHandleBitfield; ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; - ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT; + ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::GLB; bool MenuLegacyMode = false; } Configuration; diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinker/UnlinkerArgs.cpp index 29276c66..43aac8f7 100644 --- a/src/Unlinker/UnlinkerArgs.cpp +++ b/src/Unlinker/UnlinkerArgs.cpp @@ -5,6 +5,7 @@ #include "ObjWriting.h" #include "Utils/Arguments/UsageInformation.h" #include "Utils/FileUtils.h" +#include "Utils/StringUtils.h" #include #include @@ -79,7 +80,7 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT = const CommandLineOption* const OPTION_MODEL_FORMAT = CommandLineOption::Builder::Create() .WithLongName("model-format") - .WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ") + .WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ, GLTF, GLB") .WithParameter("modelFormatValue") .Build(); @@ -179,8 +180,7 @@ void UnlinkerArgs::SetVerbose(const bool isVerbose) bool UnlinkerArgs::SetImageDumpingMode() { auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT); - for (auto& c : specifiedValue) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(specifiedValue); if (specifiedValue == "dds") { @@ -202,8 +202,7 @@ bool UnlinkerArgs::SetImageDumpingMode() bool UnlinkerArgs::SetModelDumpingMode() { auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); - for (auto& c : specifiedValue) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(specifiedValue); if (specifiedValue == "xmodel_export") { @@ -217,6 +216,18 @@ bool UnlinkerArgs::SetModelDumpingMode() return true; } + if (specifiedValue == "gltf") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF; + return true; + } + + if (specifiedValue == "glb") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLB; + return true; + } + const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); printf("Illegal value: \"%s\" is not a valid model output format. Use -? to see usage information.\n", originalValue.c_str()); return false; @@ -238,8 +249,7 @@ void UnlinkerArgs::ParseCommaSeparatedAssetTypeString(const std::string& input) size_t endPos; std::string lowerInput(input); - for (auto& c : lowerInput) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(lowerInput); while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos) {