From 24145e15e2a780606663a9ef4387e7c78d43bf95 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 1 Aug 2021 00:30:12 +0200 Subject: [PATCH] Dump IW4 xmodels as obj --- src/Common/Game/IW4/CommonIW4.cpp | 25 +++ src/Common/Game/IW4/CommonIW4.h | 13 +- src/Common/Game/IW4/IW4_Assets.h | 6 +- src/Common/Utils/Pack.cpp | 57 ++++++ src/Common/Utils/Pack.h | 12 ++ .../IW4/AssetDumpers/AssetDumperXModel.cpp | 183 ++++++++++++++++++ .../Game/IW4/AssetDumpers/AssetDumperXModel.h | 23 +++ src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp | 3 +- src/ObjWriting/ObjWriting.h | 7 + src/Unlinker/UnlinkerArgs.cpp | 40 ++++ src/Unlinker/UnlinkerArgs.h | 1 + 11 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 src/Common/Utils/Pack.cpp create mode 100644 src/Common/Utils/Pack.h create mode 100644 src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp create mode 100644 src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h diff --git a/src/Common/Game/IW4/CommonIW4.cpp b/src/Common/Game/IW4/CommonIW4.cpp index e69de29b..b3240062 100644 --- a/src/Common/Game/IW4/CommonIW4.cpp +++ b/src/Common/Game/IW4/CommonIW4.cpp @@ -0,0 +1,25 @@ +#include "CommonIW4.h" + +#include "Utils/Pack.h" + +using namespace IW4; + +PackedTexCoords Common::Vec2PackTexCoords(const vec2_t* in) +{ + return PackedTexCoords{Pack32::Vec2PackTexCoords(reinterpret_cast(in))}; +} + +PackedUnitVec Common::Vec3PackUnitVec(const vec3_t* in) +{ + return PackedUnitVec{Pack32::Vec3PackUnitVec(reinterpret_cast(in))}; +} + +void Common::Vec2UnpackTexCoords(const PackedTexCoords& in, vec2_t* out) +{ + Pack32::Vec2UnpackTexCoords(in.packed, reinterpret_cast(out)); +} + +void Common::Vec3UnpackUnitVec(const PackedUnitVec& in, vec3_t* out) +{ + Pack32::Vec3UnpackUnitVec(in.packed, reinterpret_cast(out)); +} diff --git a/src/Common/Game/IW4/CommonIW4.h b/src/Common/Game/IW4/CommonIW4.h index 53ca3f5d..cc601e8f 100644 --- a/src/Common/Game/IW4/CommonIW4.h +++ b/src/Common/Game/IW4/CommonIW4.h @@ -1,5 +1,7 @@ #pragma once +#include "IW4.h" + namespace IW4 { inline const char* szWeapTypeNames[] @@ -192,4 +194,13 @@ namespace IW4 "rear", "all", }; -} \ No newline at end of file + + class Common + { + public: + static PackedTexCoords Vec2PackTexCoords(const vec2_t* in); + static PackedUnitVec Vec3PackUnitVec(const vec3_t* in); + static void Vec2UnpackTexCoords(const PackedTexCoords& in, vec2_t* out); + static void Vec3UnpackUnitVec(const PackedUnitVec& in, vec3_t* out); + }; +} diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index bf282bbb..7202e538 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -650,9 +650,9 @@ namespace IW4 { MaterialInfo info; char stateBitsEntry[48]; - char textureCount; - char constantCount; - char stateBitsCount; + unsigned char textureCount; + unsigned char constantCount; + unsigned char stateBitsCount; char stateFlags; char cameraRegion; MaterialTechniqueSet* techniqueSet; diff --git a/src/Common/Utils/Pack.cpp b/src/Common/Utils/Pack.cpp new file mode 100644 index 00000000..b13f3a70 --- /dev/null +++ b/src/Common/Utils/Pack.cpp @@ -0,0 +1,57 @@ +#include "Pack.h" + +#include + +union PackUtil32 +{ + uint32_t u; + int32_t i; + float f; + int8_t c[4]; + uint8_t uc[4]; +}; + +uint32_t Pack32::Vec2PackTexCoords(const float* in) +{ + // TODO + return 0; +} + +uint32_t Pack32::Vec3PackUnitVec(const float* in) +{ + // TODO + return 0; +} + +void Pack32::Vec2UnpackTexCoords(const uint32_t in, float* out) +{ + PackUtil32 packTemp{}; + + const auto inHiDw = (in >> 16) & UINT16_MAX; + const auto inLoDw = in & UINT16_MAX; + + if (inHiDw) + packTemp.u = ((inHiDw << 16) & 0x80000000) | (((((inHiDw << 14) & 0xFFFC000) + - (~(inHiDw << 14) & 0x10000000)) ^ 0x80000000) >> 1); + else + packTemp.f = 0.0f; + out[0] = packTemp.f; + + if (inLoDw) + packTemp.u = ((inLoDw << 16) & 0x80000000) | (((((inLoDw << 14) & 0xFFFC000) + - (~(inLoDw << 14) & 0x10000000)) ^ 0x80000000) >> 1); + else + packTemp.f = 0.0f; + out[1] = packTemp.f; +} + +void Pack32::Vec3UnpackUnitVec(const uint32_t in, float* out) +{ + assert(out != nullptr); + + PackUtil32 _in{in}; + const float decodeScale = (static_cast(_in.uc[3]) - -192.0f) / 32385.0f; + out[0] = (static_cast(_in.uc[0]) + -127.0f) * decodeScale; + out[1] = (static_cast(_in.uc[1]) + -127.0f) * decodeScale; + out[2] = (static_cast(_in.uc[2]) + -127.0f) * decodeScale; +} diff --git a/src/Common/Utils/Pack.h b/src/Common/Utils/Pack.h new file mode 100644 index 00000000..8d558e8b --- /dev/null +++ b/src/Common/Utils/Pack.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class Pack32 +{ +public: + static uint32_t Vec2PackTexCoords(const float* in); + static uint32_t Vec3PackUnitVec(const float* in); + static void Vec2UnpackTexCoords(uint32_t in, float* out); + static void Vec3UnpackUnitVec(uint32_t in, float* out); +}; \ No newline at end of file diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp new file mode 100644 index 00000000..32161cb4 --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp @@ -0,0 +1,183 @@ +#include "AssetDumperXModel.h" + +#include + +#include "Game/IW4/CommonIW4.h" + +using namespace IW4; + +bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) +{ + return true; +} + +void AssetDumperXModel::DumpObjMatMaterial(AssetDumpingContext& context, const Material* material, std::ostream& stream) +{ + stream << "\n"; + stream << "newmtl " << material->info.name << "\n"; + + GfxImage* colorMap = nullptr; + GfxImage* normalMap = nullptr; + GfxImage* specularMap = nullptr; + + for(auto i = 0u; i < material->textureCount; i++) + { + const auto& texture = material->textureTable[i]; + + switch (texture.semantic) + { + case TS_COLOR_MAP: + colorMap = texture.u.image; + break; + + case TS_NORMAL_MAP: + normalMap = texture.u.image; + break; + + case TS_SPECULAR_MAP: + specularMap = texture.u.image; + break; + + default: + break; + } + } + + if (colorMap) + stream << "map_Ka " << colorMap->name << ".dds\n"; + + if (normalMap) + stream << "map_bump " << normalMap->name << ".dds\n"; + + if (specularMap) + stream << "map_Ks " << specularMap->name << ".dds\n"; +} + +void AssetDumperXModel::DumpObjMat(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto* model = asset->Asset(); + const auto matFile = context.OpenAssetFile("xmodelsurfs/" + std::string(model->name) + ".mat"); + + if (!matFile) + return; + + auto& stream = *matFile; + stream << "# OpenAssetTools MAT File (IW4)\n"; + + if (model->numsurfs == 0 || model->materialHandles == nullptr) + return; + + std::set uniqueMaterials; + for (auto i = 0u; i < model->numsurfs; i++) + { + if(model->materialHandles[i] != nullptr) + uniqueMaterials.emplace(model->materialHandles[i]); + } + + stream << "# Material count: " << uniqueMaterials.size() << "\n"; + + for(const auto* material : uniqueMaterials) + { + DumpObjMatMaterial(context, material, stream); + } +} + +void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) +{ + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile("xmodelsurfs/" + std::string(modelSurfs->name) + ".obj"); + + if (!assetFile) + return; + + auto& stream = *assetFile; + stream << "# OpenAssetTools OBJ File (IW4)\n"; + + stream << "mtllib " << model->name << ".mtl\n"; + + if (model->lodInfo[lod].modelSurfs == nullptr || model->lodInfo[lod].modelSurfs->surfs == nullptr) + return; + + for (auto i = 0; i < model->lodInfo[lod].numsurfs; i++) + { + const auto* surf = &modelSurfs->surfs[i]; + + stream << "o surf" << i << "\n"; + + for (auto vi = 0; vi < surf->vertCount; vi++) + { + const auto* vertex = &surf->verts0[vi]; + stream << "v " << vertex->xyz[0] << " " << vertex->xyz[1] << " " << vertex->xyz[2] << "\n"; + } + + stream << "\n"; + + for (auto vi = 0; vi < surf->vertCount; vi++) + { + const auto* vertex = &surf->verts0[vi]; + vec2_t texCoords; + Common::Vec2UnpackTexCoords(vertex->texCoord, &texCoords); + + stream << "vt " << texCoords[0] << " " << (1.0f - texCoords[1]) << "\n"; + } + + stream << "\n"; + + for (auto vi = 0; vi < surf->vertCount; vi++) + { + const auto* vertex = &surf->verts0[vi]; + vec3_t normalVec; + Common::Vec3UnpackUnitVec(vertex->normal, &normalVec); + + stream << "vn " << normalVec[0] << " " << normalVec[1] << " " << normalVec[2] << "\n"; + } + + stream << "\n"; + + if(model->numsurfs > i && model->materialHandles && model->materialHandles[i]) + { + stream << "usemtl " << model->materialHandles[i]->info.name << "\n"; + } + + stream << "\n"; + + for (auto ti = 0; ti < surf->triCount; ti++) + { + const auto* indices = reinterpret_cast(surf->triIndices); + + const auto i0 = surf->baseVertIndex + indices[ti * 3 + 0] + 1; + const auto i1 = surf->baseVertIndex + indices[ti * 3 + 1] + 1; + const auto i2 = surf->baseVertIndex + indices[ti * 3 + 2] + 1; + + stream << "f " << i2 << "/" << i2 << "/" << i2 + << " " << i1 << "/" << i1 << "/" << i1 + << " " << i0 << "/" << i0 << "/" << i0 + << "\n"; + } + } +} + +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::DumpXModelExportLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod) +{ +} + +void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo* asset) +{ +} + +void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + DumpObj(context, asset); +} diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h new file mode 100644 index 00000000..05237c5c --- /dev/null +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW4/IW4.h" + +namespace IW4 +{ + class AssetDumperXModel final : public AbstractAssetDumper + { + static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); + static void DumpObjMatMaterial(AssetDumpingContext& context, const Material* material, std::ostream& stream); + static void DumpObjMat(AssetDumpingContext& context, XAssetInfo* asset); + static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); + static void DumpXModelExportLod(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/IW4/ZoneDumperIW4.cpp b/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp index db9b1cb0..6f0cb54b 100644 --- a/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/ZoneDumperIW4.cpp @@ -11,6 +11,7 @@ #include "AssetDumpers/AssetDumperStringTable.h" #include "AssetDumpers/AssetDumperVehicle.h" #include "AssetDumpers/AssetDumperWeapon.h" +#include "AssetDumpers/AssetDumperXModel.h" using namespace IW4; @@ -33,7 +34,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset) // DUMP_ASSET_POOL(AssetDumperPhysCollmap, m_phys_collmap) // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts) - // DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel) + DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel) // DUMP_ASSET_POOL(AssetDumperMaterial, m_material) // DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader) // DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader) diff --git a/src/ObjWriting/ObjWriting.h b/src/ObjWriting/ObjWriting.h index fb8937ec..63c2e19d 100644 --- a/src/ObjWriting/ObjWriting.h +++ b/src/ObjWriting/ObjWriting.h @@ -14,8 +14,15 @@ public: IWI }; + enum class ModelOutputFormat_e + { + XMODEL_EXPORT, + OBJ + }; + bool Verbose = false; ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; + ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT; } Configuration; diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinker/UnlinkerArgs.cpp index 2b6ac195..7691bf7b 100644 --- a/src/Unlinker/UnlinkerArgs.cpp +++ b/src/Unlinker/UnlinkerArgs.cpp @@ -66,6 +66,13 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT = .WithParameter("imageFormatValue") .Build(); +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") + .WithParameter("modelFormatValue") + .Build(); + const CommandLineOption* const OPTION_GDT = CommandLineOption::Builder::Create() .WithLongName("gdt") @@ -82,6 +89,7 @@ const CommandLineOption* const COMMAND_LINE_OPTIONS[] OPTION_OUTPUT_FOLDER, OPTION_SEARCH_PATH, OPTION_IMAGE_FORMAT, + OPTION_MODEL_FORMAT, OPTION_GDT }; @@ -140,6 +148,29 @@ bool UnlinkerArgs::SetImageDumpingMode() return false; } +bool UnlinkerArgs::SetModelDumpingMode() +{ + auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); + for (auto& c : specifiedValue) + c = static_cast(tolower(c)); + + if (specifiedValue == "xmodel_export") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT; + return true; + } + + if (specifiedValue == "obj") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT; + 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; +} + bool UnlinkerArgs::ParseArgs(const int argc, const char** argv) { if (!m_argument_parser.ParseArguments(argc - 1, &argv[1])) @@ -203,6 +234,15 @@ bool UnlinkerArgs::ParseArgs(const int argc, const char** argv) } } + // --model-format + if (m_argument_parser.IsOptionSpecified(OPTION_MODEL_FORMAT)) + { + if (!SetModelDumpingMode()) + { + return false; + } + } + // --gdt m_use_gdt = m_argument_parser.IsOptionSpecified(OPTION_GDT); diff --git a/src/Unlinker/UnlinkerArgs.h b/src/Unlinker/UnlinkerArgs.h index 2f74fa08..1716821e 100644 --- a/src/Unlinker/UnlinkerArgs.h +++ b/src/Unlinker/UnlinkerArgs.h @@ -22,6 +22,7 @@ private: void SetVerbose(bool isVerbose); bool SetImageDumpingMode(); + bool SetModelDumpingMode(); public: enum class ProcessingTask