From 8c61fa3b62d454e44cfe9bcdfb6d9b0e60ad7fff Mon Sep 17 00:00:00 2001 From: mo Date: Thu, 25 Jun 2026 14:03:07 +0100 Subject: [PATCH] feat: T4 XModel dumper (#852) --- docs/SupportedAssetTypes.md | 2 +- src/ObjCommon/XModel/JsonXModel.h.template | 8 ++++-- src/ObjWriting/Game/T4/ObjWriterT4.cpp | 2 ++ .../XModel/XModelDumper.cpp.template | 9 ++++-- src/ObjWriting/XModel/XModelDumper.h.template | 2 +- .../XModel/XModelToCommonConverter.cpp | 3 +- .../XModelToCommonConverter.cpp.template | 28 +++++++++++++------ .../XModel/XModelToCommonConverter.h.template | 2 +- 8 files changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index d12bfc55..73a570c0 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -130,7 +130,7 @@ The following section specify which assets are supported to be dumped to disk (u | PhysConstraints | ❌ | ❌ | | | DestructibleDef | ❌ | ❌ | | | XAnimParts | ❌ | ❌ | | -| XModel | ❌ | ❌ | | +| XModel | ✅ | ❌ | Model data can be exported to `XMODEL_EXPORT/XMODEL_BIN`, `OBJ`, `GLB/GLTF`. | | Material | ❌ | ❌ | | | MaterialTechniqueSet | ❌ | ❌ | | | GfxImage | ✅ | ❌ | A few special image encodings are not yet supported. | diff --git a/src/ObjCommon/XModel/JsonXModel.h.template b/src/ObjCommon/XModel/JsonXModel.h.template index cd8caedc..4fed72ef 100644 --- a/src/ObjCommon/XModel/JsonXModel.h.template +++ b/src/ObjCommon/XModel/JsonXModel.h.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T5, T6) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/XModel/JsonXModel" + GAME + ".h" @@ -10,6 +10,8 @@ #define FEATURE_IW4 #elif GAME == "IW5" #define FEATURE_IW5 +#elif GAME == "T4" +#define FEATURE_T4 #elif GAME == "T5" #define FEATURE_T5 #elif GAME == "T6" @@ -61,7 +63,7 @@ namespace GAME std::optional physPreset; #if defined(FEATURE_IW4) || defined(FEATURE_IW5) std::optional physCollmap; -#elif defined(FEATURE_T5) || defined(FEATURE_T6) +#elif defined(FEATURE_T4) || defined(FEATURE_T5) || defined(FEATURE_T6) std::optional physConstraints; #endif #if defined(FEATURE_T6) @@ -80,7 +82,7 @@ namespace GAME physPreset, #if defined(FEATURE_IW4) || defined(FEATURE_IW5) physCollmap, -#elif defined(FEATURE_T5) || defined(FEATURE_T6) +#elif defined(FEATURE_T4) || defined(FEATURE_T5) || defined(FEATURE_T6) physConstraints, #endif #if defined(FEATURE_T6) diff --git a/src/ObjWriting/Game/T4/ObjWriterT4.cpp b/src/ObjWriting/Game/T4/ObjWriterT4.cpp index 47ea2d9a..b73f3a5e 100644 --- a/src/ObjWriting/Game/T4/ObjWriterT4.cpp +++ b/src/ObjWriting/Game/T4/ObjWriterT4.cpp @@ -1,6 +1,7 @@ #include "ObjWriterT4.h" #include "Game/T4/Image/ImageDumperT4.h" +#include "Game/T4/XModel/XModelDumperT4.h" #include "Localize/LocalizeDumperT4.h" #include "RawFile/RawFileDumperT4.h" #include "StringTable/StringTableDumperT4.h" @@ -9,6 +10,7 @@ using namespace T4; void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) { + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/XModel/XModelDumper.cpp.template b/src/ObjWriting/XModel/XModelDumper.cpp.template index cf2a9a8c..c2304395 100644 --- a/src/ObjWriting/XModel/XModelDumper.cpp.template +++ b/src/ObjWriting/XModel/XModelDumper.cpp.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T5, T6) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".cpp" @@ -16,6 +16,9 @@ #elif GAME == "IW5" #define FEATURE_IW5 #define GAME_LOWER "iw5" +#elif GAME == "T4" +#define FEATURE_T4 +#define GAME_LOWER "t4" #elif GAME == "T5" #define FEATURE_T5 #define GAME_LOWER "t5" @@ -331,7 +334,7 @@ namespace return false; const auto& vertList = surface.vertList[0]; -#ifdef FEATURE_IW3 +#if defined(FEATURE_IW3) || defined(FEATURE_T4) // IW3 has some models that are missing 1 (a single) tri in its first lod. // It is not contained in any vert list or blend // I think this is a bug (?), so omit anyway. @@ -395,7 +398,7 @@ namespace jXModel.physCollmap = AssetName(model.physCollmap->name); #endif -#if defined(FEATURE_T5) || defined(FEATURE_T6) +#if defined(FEATURE_T4) || defined(FEATURE_T5) || defined(FEATURE_T6) if (model.physConstraints && model.physConstraints->name) jXModel.physConstraints = AssetName(model.physConstraints->name); #endif diff --git a/src/ObjWriting/XModel/XModelDumper.h.template b/src/ObjWriting/XModel/XModelDumper.h.template index 88a56ea4..1beb0405 100644 --- a/src/ObjWriting/XModel/XModelDumper.h.template +++ b/src/ObjWriting/XModel/XModelDumper.h.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T5, T6) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".h" diff --git a/src/ObjWriting/XModel/XModelToCommonConverter.cpp b/src/ObjWriting/XModel/XModelToCommonConverter.cpp index 5565f50c..3475675e 100644 --- a/src/ObjWriting/XModel/XModelToCommonConverter.cpp +++ b/src/ObjWriting/XModel/XModelToCommonConverter.cpp @@ -3,6 +3,7 @@ #include "Game/IW3/XModel/XModelToCommonConverterIW3.h" #include "Game/IW4/XModel/XModelToCommonConverterIW4.h" #include "Game/IW5/XModel/XModelToCommonConverterIW5.h" +#include "Game/T4/XModel/XModelToCommonConverterT4.h" #include "Game/T5/XModel/XModelToCommonConverterT5.h" #include "Game/T6/XModel/XModelToCommonConverterT6.h" @@ -16,7 +17,7 @@ namespace xmodel new ToCommonConverterIW3(), new ToCommonConverterIW4(), new ToCommonConverterIW5(), - nullptr, + new ToCommonConverterT4(), new ToCommonConverterT5(), new ToCommonConverterT6(), }; diff --git a/src/ObjWriting/XModel/XModelToCommonConverter.cpp.template b/src/ObjWriting/XModel/XModelToCommonConverter.cpp.template index 6067b01e..25996078 100644 --- a/src/ObjWriting/XModel/XModelToCommonConverter.cpp.template +++ b/src/ObjWriting/XModel/XModelToCommonConverter.cpp.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T5, T6) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/XModel/XModelToCommonConverter" + GAME + ".cpp" @@ -11,6 +11,8 @@ #define FEATURE_IW4 #elif GAME == "IW5" #define FEATURE_IW5 +#elif GAME == "T4" +#define FEATURE_T4 #elif GAME == "T5" #define FEATURE_T5 #elif GAME == "T6" @@ -54,7 +56,7 @@ namespace { MaterialTextureDef* def = &material->textureTable[textureIndex]; -#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) || defined(FEATURE_T4) if (def->semantic == TS_COLOR_MAP) potentialTextureDefs.push_back(def); #else @@ -178,7 +180,7 @@ namespace return false; const auto& vertList = surface.vertList[0]; -#ifdef FEATURE_IW3 +#if defined(FEATURE_IW3) || defined(FEATURE_T4) // IW3 has some models that are missing 1 (a single) tri in its first lod. // It is not contained in any vert list or blend // I think this is a bug (?), so omit anyway. @@ -403,7 +405,7 @@ namespace if (surface.vertList) { -#if defined(FEATURE_IW3) || defined(FEATURE_IW4) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_T4) assert(!surface.deformed); #else assert((surface.flags & XSURFACE_FLAG_DEFORMED) == 0); @@ -428,7 +430,7 @@ namespace auto vertsBlendOffset = 0u; if (surface.vertInfo.vertsBlend) { -#if defined(FEATURE_IW3) || defined(FEATURE_IW4) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_T4) assert(surface.deformed); #else assert((surface.flags & XSURFACE_FLAG_DEFORMED) > 0); @@ -530,14 +532,24 @@ namespace auto& object = out.m_objects[surfIndex]; object.m_faces.reserve(surface.triCount); +#ifdef FEATURE_T4 + // T4 tri indices are local to the surface, while baseVertIndex does not match + // the flattened common vertex buffer we build here. + auto surfaceVertexOffset = 0u; + for (auto previousSurfIndex = 0u; previousSurfIndex < surfIndex; previousSurfIndex++) + surfaceVertexOffset += surfs[previousSurfIndex].vertCount; +#else + const auto surfaceVertexOffset = surface.baseVertIndex; +#endif + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) { const auto& tri = surface.triIndices[triIndex]; XModelFace face{}; - face.vertexIndex[0] = tri.i[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri.i[2] + surface.baseVertIndex; + face.vertexIndex[0] = tri.i[0] + surfaceVertexOffset; + face.vertexIndex[1] = tri.i[1] + surfaceVertexOffset; + face.vertexIndex[2] = tri.i[2] + surfaceVertexOffset; object.m_faces.emplace_back(face); } } diff --git a/src/ObjWriting/XModel/XModelToCommonConverter.h.template b/src/ObjWriting/XModel/XModelToCommonConverter.h.template index 7f120964..1a7ef87a 100644 --- a/src/ObjWriting/XModel/XModelToCommonConverter.h.template +++ b/src/ObjWriting/XModel/XModelToCommonConverter.h.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T5, T6) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/XModel/XModelToCommonConverter" + GAME + ".h"