Dump IW4 xmodels as obj

This commit is contained in:
Jan 2021-08-01 00:30:12 +02:00
parent 2c96bc5ef8
commit 24145e15e2
11 changed files with 365 additions and 5 deletions

View File

@ -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<const float*>(in))};
}
PackedUnitVec Common::Vec3PackUnitVec(const vec3_t* in)
{
return PackedUnitVec{Pack32::Vec3PackUnitVec(reinterpret_cast<const float*>(in))};
}
void Common::Vec2UnpackTexCoords(const PackedTexCoords& in, vec2_t* out)
{
Pack32::Vec2UnpackTexCoords(in.packed, reinterpret_cast<float*>(out));
}
void Common::Vec3UnpackUnitVec(const PackedUnitVec& in, vec3_t* out)
{
Pack32::Vec3UnpackUnitVec(in.packed, reinterpret_cast<float*>(out));
}

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "IW4.h"
namespace IW4 namespace IW4
{ {
inline const char* szWeapTypeNames[] inline const char* szWeapTypeNames[]
@ -192,4 +194,13 @@ namespace IW4
"rear", "rear",
"all", "all",
}; };
}
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);
};
}

View File

@ -650,9 +650,9 @@ namespace IW4
{ {
MaterialInfo info; MaterialInfo info;
char stateBitsEntry[48]; char stateBitsEntry[48];
char textureCount; unsigned char textureCount;
char constantCount; unsigned char constantCount;
char stateBitsCount; unsigned char stateBitsCount;
char stateFlags; char stateFlags;
char cameraRegion; char cameraRegion;
MaterialTechniqueSet* techniqueSet; MaterialTechniqueSet* techniqueSet;

57
src/Common/Utils/Pack.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "Pack.h"
#include <cassert>
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<float>(_in.uc[3]) - -192.0f) / 32385.0f;
out[0] = (static_cast<float>(_in.uc[0]) + -127.0f) * decodeScale;
out[1] = (static_cast<float>(_in.uc[1]) + -127.0f) * decodeScale;
out[2] = (static_cast<float>(_in.uc[2]) + -127.0f) * decodeScale;
}

12
src/Common/Utils/Pack.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
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);
};

View File

@ -0,0 +1,183 @@
#include "AssetDumperXModel.h"
#include <set>
#include "Game/IW4/CommonIW4.h"
using namespace IW4;
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* 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<XModel>* 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<Material*> 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<XModel>* 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<r_index16_t*>(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<XModel>* 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<XModel>* asset, unsigned lod)
{
}
void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpObj(context, asset);
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <ostream>
#include "Dumping/AbstractAssetDumper.h"
#include "Game/IW4/IW4.h"
namespace IW4
{
class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{
static void DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpObjMatMaterial(AssetDumpingContext& context, const Material* material, std::ostream& stream);
static void DumpObjMat(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpXModelExportLod(AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;
};
}

View File

@ -11,6 +11,7 @@
#include "AssetDumpers/AssetDumperStringTable.h" #include "AssetDumpers/AssetDumperStringTable.h"
#include "AssetDumpers/AssetDumperVehicle.h" #include "AssetDumpers/AssetDumperVehicle.h"
#include "AssetDumpers/AssetDumperWeapon.h" #include "AssetDumpers/AssetDumperWeapon.h"
#include "AssetDumpers/AssetDumperXModel.h"
using namespace IW4; using namespace IW4;
@ -33,7 +34,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const
// DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset) // DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset)
// DUMP_ASSET_POOL(AssetDumperPhysCollmap, m_phys_collmap) // DUMP_ASSET_POOL(AssetDumperPhysCollmap, m_phys_collmap)
// DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts) // 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(AssetDumperMaterial, m_material)
// DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader) // DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader)
// DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader) // DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader)

View File

@ -14,8 +14,15 @@ public:
IWI IWI
}; };
enum class ModelOutputFormat_e
{
XMODEL_EXPORT,
OBJ
};
bool Verbose = false; bool Verbose = false;
ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS;
ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT;
} Configuration; } Configuration;

View File

@ -66,6 +66,13 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT =
.WithParameter("imageFormatValue") .WithParameter("imageFormatValue")
.Build(); .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 = const CommandLineOption* const OPTION_GDT =
CommandLineOption::Builder::Create() CommandLineOption::Builder::Create()
.WithLongName("gdt") .WithLongName("gdt")
@ -82,6 +89,7 @@ const CommandLineOption* const COMMAND_LINE_OPTIONS[]
OPTION_OUTPUT_FOLDER, OPTION_OUTPUT_FOLDER,
OPTION_SEARCH_PATH, OPTION_SEARCH_PATH,
OPTION_IMAGE_FORMAT, OPTION_IMAGE_FORMAT,
OPTION_MODEL_FORMAT,
OPTION_GDT OPTION_GDT
}; };
@ -140,6 +148,29 @@ bool UnlinkerArgs::SetImageDumpingMode()
return false; return false;
} }
bool UnlinkerArgs::SetModelDumpingMode()
{
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
for (auto& c : specifiedValue)
c = static_cast<char>(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) bool UnlinkerArgs::ParseArgs(const int argc, const char** argv)
{ {
if (!m_argument_parser.ParseArguments(argc - 1, &argv[1])) 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 // --gdt
m_use_gdt = m_argument_parser.IsOptionSpecified(OPTION_GDT); m_use_gdt = m_argument_parser.IsOptionSpecified(OPTION_GDT);

View File

@ -22,6 +22,7 @@ private:
void SetVerbose(bool isVerbose); void SetVerbose(bool isVerbose);
bool SetImageDumpingMode(); bool SetImageDumpingMode();
bool SetModelDumpingMode();
public: public:
enum class ProcessingTask enum class ProcessingTask