Merge pull request #182 from Laupetin/feature/gltf-xmodel-dumping

feat: dump xmodels as gltf/glb
This commit is contained in:
Jan 2024-05-11 02:08:55 +02:00 committed by GitHub
commit 35474c217e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 4842 additions and 3542 deletions

View File

@ -22,6 +22,8 @@ namespace T6
typedef tdef_align(128) float float_align128; typedef tdef_align(128) float float_align128;
typedef uint16_t ScriptString;
struct dvar_t; struct dvar_t;
struct MenuCell; struct MenuCell;
struct cplane_s; struct cplane_s;
@ -591,14 +593,22 @@ namespace T6
int partBits[5]; int partBits[5];
}; };
enum XModelLodRampType : unsigned char
{
XMODEL_LOD_RAMP_RIGID = 0x0,
XMODEL_LOD_RAMP_SKINNED = 0x1,
XMODEL_LOD_RAMP_COUNT
};
struct XModel struct XModel
{ {
const char* name; const char* name;
unsigned char numBones; unsigned char numBones;
unsigned char numRootBones; unsigned char numRootBones;
unsigned char numsurfs; unsigned char numsurfs;
char lodRampType; XModelLodRampType lodRampType;
uint16_t* boneNames; ScriptString* boneNames;
unsigned char* parentList; unsigned char* parentList;
uint16_t (*quats)[4]; uint16_t (*quats)[4];
float (*trans)[4]; float (*trans)[4];
@ -618,7 +628,7 @@ namespace T6
uint16_t collLod; uint16_t collLod;
float* himipInvSqRadii; float* himipInvSqRadii;
int memUsage; int memUsage;
int flags; unsigned int flags;
bool bad; bool bad;
PhysPreset* physPreset; PhysPreset* physPreset;
unsigned char numCollmaps; unsigned char numCollmaps;

View File

@ -0,0 +1,36 @@
#pragma once
#include "Game/T6/T6.h"
#include "Json/JsonCommon.h"
#include "Json/JsonExtension.h"
#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace T6
{
class JsonXModelLod
{
public:
std::string file;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file);
class JsonXModel
{
public:
std::vector<JsonXModelLod> lods;
unsigned collLod;
std::optional<std::string> physPreset;
std::optional<std::string> physConstraints;
unsigned flags;
JsonVec3 lightingOriginOffset;
float lightingOriginRange;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physConstraints, flags, lightingOriginOffset, lightingOriginRange);
} // namespace T6

View File

@ -0,0 +1,27 @@
#pragma once
#include "Utils/FileUtils.h"
#include <cstdint>
namespace gltf
{
constexpr uint32_t GLTF_MAGIC = FileUtils::MakeMagic32('g', 'l', 'T', 'F');
constexpr uint32_t GLTF_VERSION = 2u;
constexpr auto GLTF_VERSION_STRING = "2.0";
constexpr uint32_t CHUNK_MAGIC_JSON = FileUtils::MakeMagic32('J', 'S', 'O', 'N');
constexpr uint32_t CHUNK_MAGIC_BIN = FileUtils::MakeMagic32('B', 'I', 'N', '\x00');
constexpr auto GLTF_LENGTH_OFFSET = 8u;
constexpr auto GLTF_JSON_CHUNK_LENGTH_OFFSET = 12u;
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,";
constexpr auto GLTF_ATTRIBUTE_POSITION = "POSITION";
constexpr auto GLTF_ATTRIBUTE_NORMAL = "NORMAL";
constexpr auto GLTF_ATTRIBUTE_TEXCOORD_0 = "TEXCOORD_0";
constexpr auto GLTF_ATTRIBUTE_JOINTS_0 = "JOINTS_0";
constexpr auto GLTF_ATTRIBUTE_WEIGHTS_0 = "WEIGHTS_0";
} // namespace gltf

View File

@ -0,0 +1,343 @@
#pragma once
#include "Json/JsonExtension.h"
#include <array>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace gltf
{
class JsonAsset
{
public:
std::string version;
std::optional<std::string> generator;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAsset, version, generator);
class JsonNode
{
public:
std::optional<std::string> name;
std::optional<std::array<float, 3>> translation;
std::optional<std::array<float, 4>> rotation;
std::optional<std::array<float, 3>> scale;
std::optional<std::vector<unsigned>> children;
std::optional<unsigned> skin;
std::optional<unsigned> mesh;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, children, skin, mesh);
class JsonBuffer
{
public:
unsigned byteLength;
std::optional<std::string> uri;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBuffer, byteLength, uri);
enum class JsonAccessorComponentType
{
SIGNED_BYTE = 5120,
UNSIGNED_BYTE = 5121,
SIGNED_SHORT = 5122,
UNSIGNED_SHORT = 5123,
UNSIGNED_INT = 5125,
FLOAT = 5126
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAccessorComponentType,
{
{JsonAccessorComponentType::SIGNED_BYTE, static_cast<unsigned>(JsonAccessorComponentType::SIGNED_BYTE) },
{JsonAccessorComponentType::UNSIGNED_BYTE, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_BYTE) },
{JsonAccessorComponentType::SIGNED_SHORT, static_cast<unsigned>(JsonAccessorComponentType::SIGNED_SHORT) },
{JsonAccessorComponentType::UNSIGNED_SHORT, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_SHORT)},
{JsonAccessorComponentType::UNSIGNED_INT, static_cast<unsigned>(JsonAccessorComponentType::UNSIGNED_INT) },
{JsonAccessorComponentType::FLOAT, static_cast<unsigned>(JsonAccessorComponentType::FLOAT) },
});
enum class JsonAccessorType
{
SCALAR,
VEC2,
VEC3,
VEC4,
MAT2,
MAT3,
MAT4
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAccessorType,
{
{JsonAccessorType::SCALAR, "SCALAR"},
{JsonAccessorType::VEC2, "VEC2" },
{JsonAccessorType::VEC3, "VEC3" },
{JsonAccessorType::VEC4, "VEC4" },
{JsonAccessorType::MAT2, "MAT2" },
{JsonAccessorType::MAT3, "MAT3" },
{JsonAccessorType::MAT4, "MAT4" },
});
class JsonAccessor
{
public:
std::optional<unsigned> bufferView;
std::optional<unsigned> byteOffset;
JsonAccessorComponentType componentType;
// std::optional<boolean> normalized
unsigned count;
JsonAccessorType type;
std::optional<std::vector<float>> max;
std::optional<std::vector<float>> min;
// std::optional<JsonAccessorSparse> sparse;
// std::optional<std::string> name;
// extensions
// extras
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAccessor, bufferView, byteOffset, componentType, count, type, min, max);
enum class JsonBufferViewTarget
{
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonBufferViewTarget,
{
{JsonBufferViewTarget::ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ARRAY_BUFFER) },
{JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER)},
});
class JsonBufferView
{
public:
unsigned buffer;
unsigned byteLength;
std::optional<unsigned> byteOffset;
std::optional<unsigned> byteStride;
std::optional<JsonBufferViewTarget> target;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBufferView, buffer, byteLength, byteOffset, byteStride, target);
enum class JsonAnimationChannelTargetPath
{
TRANSLATION,
ROTATION,
SCALE,
WEIGHTS
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAnimationChannelTargetPath,
{
{JsonAnimationChannelTargetPath::TRANSLATION, "translation"},
{JsonAnimationChannelTargetPath::ROTATION, "rotation" },
{JsonAnimationChannelTargetPath::SCALE, "scale" },
{JsonAnimationChannelTargetPath::WEIGHTS, "weights" },
});
class JsonAnimationChannelTarget
{
public:
unsigned node;
JsonAnimationChannelTargetPath path;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationChannelTarget, node, path);
class JsonAnimationChannel
{
public:
unsigned sampler;
JsonAnimationChannelTarget target;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationChannel, sampler, target);
enum class JsonAnimationSamplerInterpolation
{
LINEAR,
STEP,
CUBIC_SPLINE
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonAnimationSamplerInterpolation,
{
{JsonAnimationSamplerInterpolation::LINEAR, "LINEAR" },
{JsonAnimationSamplerInterpolation::STEP, "STEP" },
{JsonAnimationSamplerInterpolation::CUBIC_SPLINE, "CUBICSPLINE"},
});
class JsonAnimationSampler
{
public:
unsigned input;
std::optional<JsonAnimationSamplerInterpolation> interpolation;
unsigned output;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationSampler, input, interpolation, output);
class JsonAnimation
{
public:
std::vector<JsonAnimationChannel> channels;
std::vector<JsonAnimationSampler> samplers;
std::optional<std::string> name;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimation, channels, samplers, name);
class JsonTextureInfo
{
public:
unsigned index;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTextureInfo, index);
class JsonPbrMetallicRoughness
{
public:
std::optional<JsonTextureInfo> baseColorTexture;
std::optional<float> metallicFactor;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPbrMetallicRoughness, baseColorTexture, metallicFactor);
class JsonNormalTextureInfo
{
public:
unsigned index;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNormalTextureInfo, index);
class JsonMaterial
{
public:
std::optional<std::string> name;
std::optional<JsonPbrMetallicRoughness> pbrMetallicRoughness;
std::optional<JsonNormalTextureInfo> normalTexture;
std::optional<bool> doubleSided;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial, name, pbrMetallicRoughness, normalTexture, doubleSided);
enum class JsonMeshPrimitivesMode
{
POINTS = 0,
LINES = 1,
LINE_LOOP = 2,
LINE_STRIP = 3,
TRIANGLES = 4,
TRIANGLES_STRIP = 5,
TRIANGLE_FAN = 6
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonMeshPrimitivesMode,
{
{JsonMeshPrimitivesMode::POINTS, static_cast<unsigned>(JsonMeshPrimitivesMode::POINTS) },
{JsonMeshPrimitivesMode::LINES, static_cast<unsigned>(JsonMeshPrimitivesMode::LINES) },
{JsonMeshPrimitivesMode::LINE_LOOP, static_cast<unsigned>(JsonMeshPrimitivesMode::LINE_LOOP) },
{JsonMeshPrimitivesMode::LINE_STRIP, static_cast<unsigned>(JsonMeshPrimitivesMode::LINE_STRIP) },
{JsonMeshPrimitivesMode::TRIANGLES, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLES) },
{JsonMeshPrimitivesMode::TRIANGLES_STRIP, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLES_STRIP)},
{JsonMeshPrimitivesMode::TRIANGLE_FAN, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLE_FAN) },
});
// This should probably be a map, but the supported models do not have arbitrary primitives anyway
class JsonMeshPrimitivesAttributes
{
public:
std::optional<unsigned> POSITION;
std::optional<unsigned> NORMAL;
std::optional<unsigned> TEXCOORD_0;
std::optional<unsigned> JOINTS_0;
std::optional<unsigned> WEIGHTS_0;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitivesAttributes, POSITION, NORMAL, TEXCOORD_0, JOINTS_0, WEIGHTS_0);
class JsonMeshPrimitives
{
public:
JsonMeshPrimitivesAttributes attributes;
std::optional<unsigned> indices;
std::optional<unsigned> material;
std::optional<JsonMeshPrimitivesMode> mode;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitives, attributes, indices, material, mode);
class JsonMesh
{
public:
std::vector<JsonMeshPrimitives> primitives;
std::optional<std::vector<float>> weights;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMesh, primitives, weights);
class JsonSkin
{
public:
std::optional<unsigned> inverseBindMatrices;
std::optional<unsigned> skeleton;
std::vector<unsigned> joints;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSkin, inverseBindMatrices, skeleton, joints);
class JsonScene
{
public:
std::vector<unsigned> nodes;
std::optional<std::string> name;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonScene, nodes, name);
class JsonTexture
{
public:
unsigned source;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTexture, source);
class JsonImage
{
public:
std::optional<std::string> uri;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonImage, uri);
class JsonRoot
{
public:
std::optional<std::vector<JsonAccessor>> accessors;
std::optional<std::vector<JsonAnimation>> animations;
JsonAsset asset;
std::optional<std::vector<JsonBuffer>> buffers;
std::optional<std::vector<JsonBufferView>> bufferViews;
std::optional<std::vector<JsonImage>> images;
std::optional<std::vector<JsonMaterial>> materials;
std::optional<std::vector<JsonMesh>> meshes;
std::optional<std::vector<JsonNode>> nodes;
std::optional<std::vector<JsonSkin>> skins;
std::optional<unsigned> scene;
std::optional<std::vector<JsonScene>> scenes;
std::optional<std::vector<JsonTexture>> textures;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(
JsonRoot, accessors, animations, asset, buffers, bufferViews, images, materials, meshes, nodes, skins, scene, scenes, textures);
} // namespace gltf

View File

@ -3,13 +3,8 @@
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Utils/DistinctMapper.h" #include "Utils/DistinctMapper.h"
#include <memory>
#include <string> #include <string>
#include <vector>
struct XModelObject
{
std::string name;
};
struct XModelBone struct XModelBone
{ {
@ -30,8 +25,7 @@ struct XModelBoneWeight
struct XModelVertexBoneWeightCollection struct XModelVertexBoneWeightCollection
{ {
std::unique_ptr<XModelBoneWeight[]> weights; std::vector<XModelBoneWeight> weights;
size_t totalWeightCount;
}; };
struct XModelVertexBoneWeights struct XModelVertexBoneWeights
@ -51,8 +45,6 @@ struct XModelVertex
struct XModelFace struct XModelFace
{ {
int vertexIndex[3]; int vertexIndex[3];
int objectIndex;
int materialIndex;
}; };
struct XModelMaterial struct XModelMaterial
@ -89,10 +81,30 @@ struct XModelMaterial
float blinn[2]; float blinn[2];
float phong; float phong;
std::string colorMapName; std::string colorMapName;
std::string normalMapName;
std::string specularMapName;
void ApplyDefaults(); void ApplyDefaults();
}; };
struct XModelObject
{
std::string name;
int materialIndex;
std::vector<XModelFace> m_faces;
};
struct XModelCommon
{
std::string m_name;
std::vector<XModelObject> m_objects;
std::vector<XModelBone> m_bones;
std::vector<XModelMaterial> m_materials;
std::vector<XModelVertex> m_vertices;
std::vector<XModelVertexBoneWeights> m_vertex_bone_weights;
XModelVertexBoneWeightCollection m_bone_weight_data;
};
struct VertexMergerPos struct VertexMergerPos
{ {
float x; float x;

View File

@ -0,0 +1,44 @@
#include "AssetLoaderXModel.h"
#include "Game/T6/T6.h"
#include "Game/T6/XModel/JsonXModelLoader.h"
#include "Pool/GlobalAssetPool.h"
#include <cstring>
#include <format>
#include <iostream>
using namespace T6;
void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
{
auto* xmodel = memory->Create<XModel>();
memset(xmodel, 0, sizeof(XModel));
xmodel->name = memory->Dup(assetName.c_str());
return xmodel;
}
bool AssetLoaderXModel::CanLoadFromRaw() const
{
return true;
}
bool AssetLoaderXModel::LoadFromRaw(
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
{
const auto file = searchPath->Open(std::format("xmodel/{}.json", assetName));
if (!file.IsOpen())
return false;
auto* xmodel = memory->Alloc<XModel>();
xmodel->name = memory->Dup(assetName.c_str());
std::vector<XAssetInfoGeneric*> dependencies;
if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies))
manager->AddAsset<AssetXModel>(assetName, xmodel, std::move(dependencies));
else
std::cerr << "Failed to load xmodel \"" << assetName << "\"\n";
return true;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "AssetLoading/BasicAssetLoader.h"
#include "AssetLoading/IAssetLoadingManager.h"
#include "Game/T6/T6.h"
#include "SearchPath/ISearchPath.h"
namespace T6
{
class AssetLoaderXModel final : public BasicAssetLoader<ASSET_TYPE_XMODEL, XModel>
{
static std::string GetFileNameForAsset(const std::string& assetName);
public:
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
_NODISCARD bool CanLoadFromRaw() const override;
bool
LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override;
};
} // namespace T6

View File

@ -0,0 +1,117 @@
#include "JsonXModelLoader.h"
#include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h"
#include <format>
#include <iostream>
#include <nlohmann/json.hpp>
#include <vector>
using namespace nlohmann;
using namespace T6;
namespace
{
class JsonLoader
{
public:
JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set<XAssetInfoGeneric*>& dependencies)
: m_stream(stream),
m_memory(memory),
m_manager(manager),
m_dependencies(dependencies)
{
}
bool Load(XModel& xmodel) const
{
const auto jRoot = json::parse(m_stream);
std::string type;
unsigned version;
jRoot.at("_type").get_to(type);
jRoot.at("_version").get_to(version);
if (type != "xmodel" || version != 1u)
{
std::cerr << "Tried to load xmodel \"" << xmodel.name << "\" but did not find expected type material of version 1\n";
return false;
}
const auto jXModel = jRoot.get<JsonXModel>();
return CreateXModelFromJson(jXModel, xmodel);
}
private:
static void PrintError(const XModel& xmodel, const std::string& message)
{
std::cerr << "Cannot load xmodel \"" << xmodel.name << "\": " << message << "\n";
}
bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel) const
{
xmodel.collLod = static_cast<uint16_t>(jXModel.collLod);
if (jXModel.physPreset)
{
auto* physPreset = m_manager.LoadDependency<AssetPhysPreset>(jXModel.physPreset.value());
if (!physPreset)
{
PrintError(xmodel, "Could not find phys preset");
return false;
}
m_dependencies.emplace(physPreset);
xmodel.physPreset = physPreset->Asset();
}
else
{
xmodel.physPreset = nullptr;
}
if (jXModel.physConstraints)
{
auto* physConstraints = m_manager.LoadDependency<AssetPhysConstraints>(jXModel.physConstraints.value());
if (!physConstraints)
{
PrintError(xmodel, "Could not find phys constraints");
return false;
}
m_dependencies.emplace(physConstraints);
xmodel.physConstraints = physConstraints->Asset();
}
else
{
xmodel.physConstraints = nullptr;
}
xmodel.flags = jXModel.flags;
xmodel.lightingOriginOffset.x = jXModel.lightingOriginOffset.x;
xmodel.lightingOriginOffset.y = jXModel.lightingOriginOffset.y;
xmodel.lightingOriginOffset.z = jXModel.lightingOriginOffset.z;
xmodel.lightingOriginRange = jXModel.lightingOriginRange;
return true;
}
std::istream& m_stream;
MemoryManager& m_memory;
IAssetLoadingManager& m_manager;
std::set<XAssetInfoGeneric*>& m_dependencies;
};
} // namespace
namespace T6
{
bool LoadXModelAsJson(
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies)
{
std::set<XAssetInfoGeneric*> dependenciesSet;
const JsonLoader loader(stream, *memory, *manager, dependenciesSet);
dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend());
return loader.Load(xmodel);
}
} // namespace T6

View File

@ -0,0 +1,13 @@
#pragma once
#include "AssetLoading/IAssetLoadingManager.h"
#include "Game/T6/T6.h"
#include "Utils/MemoryManager.h"
#include <istream>
namespace T6
{
bool LoadXModelAsJson(
std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector<XAssetInfoGeneric*>& dependencies);
} // namespace T6

View File

@ -19,6 +19,7 @@ function ObjWriting:link(links)
links:linkto(ZoneCommon) links:linkto(ZoneCommon)
links:linkto(minilzo) links:linkto(minilzo)
links:linkto(minizip) links:linkto(minizip)
links:linkto(libtomcrypt)
end end
function ObjWriting:use() function ObjWriting:use()
@ -55,5 +56,6 @@ function ObjWriting:project()
minilzo:include(includes) minilzo:include(includes)
minizip:include(includes) minizip:include(includes)
json:include(includes) json:include(includes)
libtomcrypt:include(includes)
end end

View File

@ -2,23 +2,31 @@
#include "Game/IW3/CommonIW3.h" #include "Game/IW3/CommonIW3.h"
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Model/XModel/XModelExportWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h"
#include "Utils/HalfFloat.h" #include "Utils/HalfFloat.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.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 "XModel/XModelWriter.h"
#include <cassert> #include <cassert>
#include <sstream> #include <format>
using namespace IW3; using namespace IW3;
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset) namespace
{ {
return !asset->m_name.empty() && asset->m_name[0] != ','; std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
} {
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
}
GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) GfxImage* GetMaterialColorMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -41,10 +49,10 @@ GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) GfxImage* GetMaterialNormalMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -67,10 +75,10 @@ GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) GfxImage* GetMaterialSpecularMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -93,166 +101,10 @@ GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
}
void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
{
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<int>(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<int>(surfIndex), objVertex);
writer.AddNormal(static_cast<int>(surfIndex), objNormal);
writer.AddUv(static_cast<int>(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<int>(surfIndex), face);
}
}
}
void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> materialMapper(model->numsurfs);
AddObjMaterials(writer, materialMapper, model);
writer.WriteMtl(*matFile);
}
void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> 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<XModel>* 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++) for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
{ {
XModelBone bone; XModelBone bone;
@ -273,8 +125,8 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
bone.globalRotation = bone.globalRotation = Quaternion32(
Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]);
if (boneNum < model->numRootBones) if (boneNum < model->numRootBones)
{ {
@ -294,12 +146,20 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]));
} }
writer.AddBone(std::move(bone)); out.m_bones.emplace_back(std::move(bone));
}
} }
}
void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model) const char* AssetName(const char* input)
{ {
if (input && input[0] == ',')
return &input[1];
return input;
}
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
{
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
{ {
Material* material = model->materialHandles[surfaceMaterialNum]; Material* material = model->materialHandles[surfaceMaterialNum];
@ -308,31 +168,41 @@ void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, Distinc
XModelMaterial xMaterial; XModelMaterial xMaterial;
xMaterial.ApplyDefaults(); xMaterial.ApplyDefaults();
xMaterial.name = material->info.name; xMaterial.name = AssetName(material->info.name);
const auto* colorMap = GetMaterialColorMap(material); const auto* colorMap = GetMaterialColorMap(material);
if (colorMap) if (colorMap)
xMaterial.colorMapName = std::string(colorMap->name); xMaterial.colorMapName = AssetName(colorMap->name);
writer.AddMaterial(std::move(xMaterial)); const auto* normalMap = GetMaterialNormalMap(material);
if (normalMap)
xMaterial.normalMapName = AssetName(normalMap->name);
const auto* specularMap = GetMaterialSpecularMap(material);
if (specularMap)
xMaterial.specularMapName = AssetName(specularMap->name);
out.m_materials.emplace_back(std::move(xMaterial));
}
} }
} }
}
void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
{ {
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
XModelObject object; XModelObject object;
object.name = "surf" + std::to_string(surfIndex); object.name = std::format("surf{}", surfIndex);
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
writer.AddObject(std::move(object)); out.m_objects.emplace_back(std::move(object));
}
} }
}
void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
@ -365,45 +235,43 @@ void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XM
vertex.uv[0] = uv[0]; vertex.uv[0] = uv[0];
vertex.uv[1] = uv[1]; vertex.uv[1] = uv[1];
writer.AddVertex(vertex); out.m_vertices.emplace_back(vertex);
}
} }
} }
}
void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
weightCollection.totalWeightCount = 0u; auto totalWeightCount = 0u;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
if (surface.vertList) if (surface.vertList)
{ {
weightCollection.totalWeightCount += surface.vertListCount; totalWeightCount += surface.vertListCount;
} }
if (surface.vertInfo.vertsBlend) if (surface.vertInfo.vertsBlend)
{ {
weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; totalWeightCount += surface.vertInfo.vertCount[0] * 1;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; totalWeightCount += surface.vertInfo.vertCount[1] * 2;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; totalWeightCount += surface.vertInfo.vertCount[2] * 3;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; totalWeightCount += surface.vertInfo.vertCount[3] * 4;
} }
} }
weightCollection.weights = std::make_unique<XModelBoneWeight[]>(weightCollection.totalWeightCount); weightCollection.weights.resize(totalWeightCount);
} }
void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
const XModel* model, {
const unsigned lod,
XModelVertexBoneWeightCollection& weightCollection)
{
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
auto& weightCollection = out.m_bone_weight_data;
size_t weightOffset = 0u; size_t weightOffset = 0u;
@ -423,7 +291,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
handledVertices += vertList.vertCount; handledVertices += vertList.vertCount;
} }
@ -441,7 +309,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 1; vertsBlendOffset += 1;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
// 2 bone weights // 2 bone weights
@ -458,7 +326,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 3; vertsBlendOffset += 3;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
} }
// 3 bone weights // 3 bone weights
@ -478,7 +346,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 5; vertsBlendOffset += 5;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
} }
// 4 bone weights // 4 bone weights
@ -501,28 +369,31 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 7; vertsBlendOffset += 7;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
} }
handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; handledVertices +=
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
} }
for (; handledVertices < surface.vertCount; handledVertices++) for (; handledVertices < surface.vertCount; handledVertices++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); out.m_vertex_bone_weights.emplace_back(nullptr, 0);
}
} }
} }
}
void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfIndex = model->lodInfo[lod].surfIndex;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
auto& object = out.m_objects[surfIndex];
object.m_faces.reserve(surface.triCount);
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
{ {
const auto& tri = surface.triIndices[triIndex]; const auto& tri = surface.triIndices[triIndex];
@ -531,63 +402,125 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const Disti
face.vertexIndex[0] = tri[0] + surface.baseVertIndex; face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
face.vertexIndex[1] = tri[1] + surface.baseVertIndex; face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
face.vertexIndex[2] = tri[2] + surface.baseVertIndex; face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
face.objectIndex = static_cast<int>(surfIndex); object.m_faces.emplace_back(face);
face.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); }
writer.AddFace(face);
} }
} }
}
void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod) void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
{ {
DistinctMapper<Material*> materialMapper(model->numsurfs);
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
out.m_name = std::format("{}_lod{}", model->name, lod);
AddXModelBones(out, context, model);
AddXModelMaterials(out, materialMapper, model);
AddXModelObjects(out, model, lod, materialMapper);
AddXModelVertices(out, model, lod);
AddXModelVertexBoneWeights(out, model, lod);
AddXModelFaces(out, model, lod);
}
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
std::ostringstream ss; if (!mtlFile)
ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; return;
const auto assetFile = context.OpenAssetFile(ss.str()); const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs);
writer->Write(common);
}
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
{
const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
if (!assetFile) if (!assetFile)
return; return;
const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); const auto writer =
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs); DistinctMapper<Material*> materialMapper(model->numsurfs);
XModelVertexBoneWeightCollection boneWeightCollection;
AllocateXModelBoneWeights(model, lod, boneWeightCollection);
AddXModelBones(context, *writer, model); writer->Write(common);
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 DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
} {
void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
if (!assetFile)
return;
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
template<typename T>
void DumpGltfLod(
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* 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<T>(*assetFile);
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
{ {
DumpXModelExportLod(context, asset, currentLod); XModelCommon common;
} PopulateXModelWriter(common, context, currentLod, asset->Asset());
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
switch (ObjWriting::Configuration.ModelOutputFormat) switch (ObjWriting::Configuration.ModelOutputFormat)
{ {
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
DumpObj(context, asset); DumpObjLod(common, context, asset, currentLod);
if (currentLod == 0u)
DumpObjMtl(common, context, asset);
break; break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
DumpXModelExport(context, asset); DumpXModelExportLod(common, context, asset, currentLod);
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
break; break;
default: default:
assert(false); assert(false);
break; break;
} }
}
}
} // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpXModelSurfs(context, asset);
} }

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h" #include "Dumping/AbstractAssetDumper.h"
#include "Game/IW3/IW3.h" #include "Game/IW3/IW3.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW3 namespace IW3
{ {
class AssetDumperXModel final : public AbstractAssetDumper<XModel> class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{ {
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& 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<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected: protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override; bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -2,40 +2,26 @@
#include "Game/IW4/CommonIW4.h" #include "Game/IW4/CommonIW4.h"
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Model/XModel/XModelExportWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h"
#include "Utils/HalfFloat.h" #include "Utils/HalfFloat.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.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 "XModel/XModelWriter.h"
#include <cassert> #include <cassert>
#include <format>
using namespace IW4; using namespace IW4;
namespace IW4 namespace
{ {
class SurfsDumpingZoneState final : public IZoneAssetDumperState GfxImage* GetMaterialColorMap(const Material* material)
{ {
std::set<const XModelSurfs*> m_dumped_surfs;
public:
bool ShouldDumpTechnique(const XModelSurfs* surfs)
{
if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end())
return false;
m_dumped_surfs.emplace(surfs);
return true;
}
};
} // namespace IW4
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
{
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -58,10 +44,10 @@ GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) GfxImage* GetMaterialNormalMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -84,10 +70,10 @@ GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) GfxImage* GetMaterialSpecularMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -110,163 +96,10 @@ GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
}
void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex) void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
{
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
ObjObject object;
object.name = "surf" + std::to_string(surfIndex);
object.materialIndex = static_cast<int>(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<int>(surfIndex), objVertex);
writer.AddNormal(static_cast<int>(surfIndex), objNormal);
writer.AddUv(static_cast<int>(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<int>(surfIndex), face);
}
}
}
void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> materialMapper(model->numsurfs);
AddObjMaterials(writer, materialMapper, model);
writer.WriteMtl(*matFile);
}
void AssetDumperXModel::DumpObjLod(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> 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<XModel>* asset)
{
const auto* model = asset->Asset();
auto* surfZoneState = context.GetZoneAssetDumperState<SurfsDumpingZoneState>();
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++) for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
{ {
XModelBone bone; XModelBone bone;
@ -287,8 +120,8 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
bone.globalRotation = bone.globalRotation = Quaternion32(
Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]);
if (boneNum < model->numRootBones) if (boneNum < model->numRootBones)
{ {
@ -308,12 +141,20 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]));
} }
writer.AddBone(std::move(bone)); out.m_bones.emplace_back(std::move(bone));
}
} }
}
void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model) const char* AssetName(const char* input)
{ {
if (input && input[0] == ',')
return &input[1];
return input;
}
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
{
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
{ {
Material* material = model->materialHandles[surfaceMaterialNum]; Material* material = model->materialHandles[surfaceMaterialNum];
@ -322,29 +163,38 @@ void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, Distinc
XModelMaterial xMaterial; XModelMaterial xMaterial;
xMaterial.ApplyDefaults(); xMaterial.ApplyDefaults();
xMaterial.name = material->info.name; xMaterial.name = AssetName(material->info.name);
const auto* colorMap = GetMaterialColorMap(material); const auto* colorMap = GetMaterialColorMap(material);
if (colorMap) if (colorMap)
xMaterial.colorMapName = std::string(colorMap->name); xMaterial.colorMapName = AssetName(colorMap->name);
writer.AddMaterial(std::move(xMaterial)); const auto* normalMap = GetMaterialNormalMap(material);
if (normalMap)
xMaterial.normalMapName = AssetName(normalMap->name);
const auto* specularMap = GetMaterialSpecularMap(material);
if (specularMap)
xMaterial.specularMapName = AssetName(specularMap->name);
out.m_materials.emplace_back(std::move(xMaterial));
}
} }
} }
}
void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) void AddXModelObjects(XModelCommon& out, const XModelSurfs* modelSurfs, const DistinctMapper<Material*>& materialMapper, const int baseSurfaceIndex)
{ {
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
XModelObject object; XModelObject object;
object.name = "surf" + std::to_string(surfIndex); object.name = std::format("surf{}", surfIndex);
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
writer.AddObject(std::move(object)); out.m_objects.emplace_back(std::move(object));
}
} }
}
void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) void AddXModelVertices(XModelCommon& out, const XModelSurfs* modelSurfs)
{ {
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
@ -374,39 +224,38 @@ void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XM
vertex.uv[0] = uv[0]; vertex.uv[0] = uv[0];
vertex.uv[1] = uv[1]; vertex.uv[1] = uv[1];
writer.AddVertex(vertex); out.m_vertices.emplace_back(vertex);
}
} }
} }
}
void AssetDumperXModel::AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection)
{ {
weightCollection.totalWeightCount = 0u; auto totalWeightCount = 0u;
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
if (surface.vertList) if (surface.vertList)
{ {
weightCollection.totalWeightCount += surface.vertListCount; totalWeightCount += surface.vertListCount;
} }
if (surface.vertInfo.vertsBlend) if (surface.vertInfo.vertsBlend)
{ {
weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; totalWeightCount += surface.vertInfo.vertCount[0] * 1;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; totalWeightCount += surface.vertInfo.vertCount[1] * 2;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; totalWeightCount += surface.vertInfo.vertCount[2] * 3;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; totalWeightCount += surface.vertInfo.vertCount[3] * 4;
} }
} }
weightCollection.weights = std::make_unique<XModelBoneWeight[]>(weightCollection.totalWeightCount); weightCollection.weights.resize(totalWeightCount);
} }
void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, void AddXModelVertexBoneWeights(XModelCommon& out, const XModelSurfs* modelSurfs)
const XModelSurfs* modelSurfs, {
XModelVertexBoneWeightCollection& weightCollection) auto& weightCollection = out.m_bone_weight_data;
{
size_t weightOffset = 0u; size_t weightOffset = 0u;
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
@ -425,7 +274,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
handledVertices += vertList.vertCount; handledVertices += vertList.vertCount;
} }
@ -443,7 +292,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 1; vertsBlendOffset += 1;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
// 2 bone weights // 2 bone weights
@ -460,7 +309,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 3; vertsBlendOffset += 3;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
} }
// 3 bone weights // 3 bone weights
@ -480,7 +329,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 5; vertsBlendOffset += 5;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
} }
// 4 bone weights // 4 bone weights
@ -503,27 +352,28 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 7; vertsBlendOffset += 7;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
} }
handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; handledVertices +=
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
} }
for (; handledVertices < surface.vertCount; handledVertices++) for (; handledVertices < surface.vertCount; handledVertices++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); out.m_vertex_bone_weights.emplace_back(nullptr, 0);
}
} }
} }
}
void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, void AddXModelFaces(XModelCommon& out, const XModelSurfs* modelSurfs)
const DistinctMapper<Material*>& materialMapper, {
const XModelSurfs* modelSurfs,
const int baseSurfaceIndex)
{
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
auto& object = out.m_objects[surfIndex];
object.m_faces.reserve(surface.triCount);
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
{ {
const auto& tri = surface.triIndices[triIndex]; const auto& tri = surface.triIndices[triIndex];
@ -532,67 +382,134 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer,
face.vertexIndex[0] = tri[0] + surface.baseVertIndex; face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
face.vertexIndex[1] = tri[1] + surface.baseVertIndex; face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
face.vertexIndex[2] = tri[2] + surface.baseVertIndex; face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
face.objectIndex = static_cast<int>(surfIndex); object.m_faces.emplace_back(face);
face.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); }
writer.AddFace(face);
} }
} }
}
void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod) void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
{ {
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
DistinctMapper<Material*> materialMapper(model->numsurfs);
AllocateXModelBoneWeights(modelSurfs, out.m_bone_weight_data);
out.m_name = modelSurfs->name;
AddXModelBones(out, context, model);
AddXModelMaterials(out, materialMapper, model);
AddXModelObjects(out, modelSurfs, materialMapper, model->lodInfo[lod].surfIndex);
AddXModelVertices(out, modelSurfs);
AddXModelVertexBoneWeights(out, modelSurfs);
AddXModelFaces(out, modelSurfs);
}
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
if (!mtlFile)
return;
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs);
writer->Write(common);
}
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto* modelSurfs = model->lodInfo[lod].modelSurfs; const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr)
return; return;
const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".XMODEL_EXPORT"); const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.obj", modelSurfs->name));
if (!assetFile) if (!assetFile)
return; return;
const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); const auto writer =
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs); DistinctMapper<Material*> materialMapper(model->numsurfs);
XModelVertexBoneWeightCollection boneWeightCollection;
AllocateXModelBoneWeights(modelSurfs, boneWeightCollection);
AddXModelBones(context, *writer, model); writer->Write(common);
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 DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
} {
void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
auto* surfZoneState = context.GetZoneAssetDumperState<SurfsDumpingZoneState>();
const auto* model = asset->Asset(); 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 = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
template<typename T>
void DumpGltfLod(
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* 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<T>(*assetFile);
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
{ {
if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) XModelCommon common;
continue; PopulateXModelWriter(common, context, currentLod, asset->Asset());
DumpXModelExportLod(context, asset, currentLod);
}
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
switch (ObjWriting::Configuration.ModelOutputFormat) switch (ObjWriting::Configuration.ModelOutputFormat)
{ {
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
DumpObj(context, asset); DumpObjLod(common, context, asset, currentLod);
if (currentLod == 0u)
DumpObjMtl(common, context, asset);
break; break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
DumpXModelExport(context, asset); DumpXModelExportLod(common, context, asset, currentLod);
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
break; break;
default: default:
assert(false); assert(false);
break; break;
} }
}
}
} // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpXModelSurfs(context, asset);
} }

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h" #include "Dumping/AbstractAssetDumper.h"
#include "Game/IW4/IW4.h" #include "Game/IW4/IW4.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW4 namespace IW4
{ {
class AssetDumperXModel final : public AbstractAssetDumper<XModel> class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{ {
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& 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<XModel>* asset, const unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected: protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override; bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -2,40 +2,26 @@
#include "Game/IW5/CommonIW5.h" #include "Game/IW5/CommonIW5.h"
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Model/XModel/XModelExportWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h"
#include "Utils/HalfFloat.h" #include "Utils/HalfFloat.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.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 "XModel/XModelWriter.h"
#include <cassert> #include <cassert>
#include <format>
using namespace IW5; using namespace IW5;
namespace IW5 namespace
{ {
class SurfsDumpingZoneState final : public IZoneAssetDumperState GfxImage* GetMaterialColorMap(const Material* material)
{ {
std::set<const XModelSurfs*> m_dumped_surfs;
public:
bool ShouldDumpTechnique(const XModelSurfs* surfs)
{
if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end())
return false;
m_dumped_surfs.emplace(surfs);
return true;
}
};
} // namespace IW5
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
{
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -58,10 +44,10 @@ GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) GfxImage* GetMaterialNormalMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -84,10 +70,10 @@ GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) GfxImage* GetMaterialSpecularMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -110,162 +96,10 @@ GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
}
void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex) void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
{
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
ObjObject object;
object.name = "surf" + std::to_string(surfIndex);
object.materialIndex = static_cast<int>(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<int>(surfIndex), objVertex);
writer.AddNormal(static_cast<int>(surfIndex), objNormal);
writer.AddUv(static_cast<int>(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<int>(surfIndex), face);
}
}
}
void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> materialMapper(model->numsurfs);
AddObjMaterials(writer, materialMapper, model);
writer.WriteMtl(*matFile);
}
void AssetDumperXModel::DumpObjLod(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> 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<XModel>* asset)
{
const auto* model = asset->Asset();
auto* surfZoneState = context.GetZoneAssetDumperState<SurfsDumpingZoneState>();
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++) for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
{ {
XModelBone bone; XModelBone bone;
@ -286,8 +120,8 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
bone.globalRotation = bone.globalRotation = Quaternion32(
Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]);
if (boneNum < model->numRootBones) if (boneNum < model->numRootBones)
{ {
@ -307,12 +141,20 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]));
} }
writer.AddBone(std::move(bone)); out.m_bones.emplace_back(std::move(bone));
}
} }
}
void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model) const char* AssetName(const char* input)
{ {
if (input && input[0] == ',')
return &input[1];
return input;
}
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
{
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
{ {
Material* material = model->materialHandles[surfaceMaterialNum]; Material* material = model->materialHandles[surfaceMaterialNum];
@ -321,29 +163,38 @@ void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, Distinc
XModelMaterial xMaterial; XModelMaterial xMaterial;
xMaterial.ApplyDefaults(); xMaterial.ApplyDefaults();
xMaterial.name = material->info.name; xMaterial.name = AssetName(material->info.name);
const auto* colorMap = GetMaterialColorMap(material); const auto* colorMap = GetMaterialColorMap(material);
if (colorMap) if (colorMap)
xMaterial.colorMapName = std::string(colorMap->name); xMaterial.colorMapName = AssetName(colorMap->name);
writer.AddMaterial(std::move(xMaterial)); const auto* normalMap = GetMaterialNormalMap(material);
if (normalMap)
xMaterial.normalMapName = AssetName(normalMap->name);
const auto* specularMap = GetMaterialSpecularMap(material);
if (specularMap)
xMaterial.specularMapName = AssetName(specularMap->name);
out.m_materials.emplace_back(std::move(xMaterial));
}
} }
} }
}
void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) void AddXModelObjects(XModelCommon& out, const XModelSurfs* modelSurfs, const DistinctMapper<Material*>& materialMapper, const int baseSurfaceIndex)
{ {
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
XModelObject object; XModelObject object;
object.name = "surf" + std::to_string(surfIndex); object.name = std::format("surf{}", surfIndex);
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
writer.AddObject(std::move(object)); out.m_objects.emplace_back(std::move(object));
}
} }
}
void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) void AddXModelVertices(XModelCommon& out, const XModelSurfs* modelSurfs)
{ {
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
@ -373,39 +224,38 @@ void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XM
vertex.uv[0] = uv[0]; vertex.uv[0] = uv[0];
vertex.uv[1] = uv[1]; vertex.uv[1] = uv[1];
writer.AddVertex(vertex); out.m_vertices.emplace_back(vertex);
}
} }
} }
}
void AssetDumperXModel::AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection)
{ {
weightCollection.totalWeightCount = 0u; auto totalWeightCount = 0u;
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
if (surface.vertList) if (surface.vertList)
{ {
weightCollection.totalWeightCount += surface.vertListCount; totalWeightCount += surface.vertListCount;
} }
if (surface.vertInfo.vertsBlend) if (surface.vertInfo.vertsBlend)
{ {
weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; totalWeightCount += surface.vertInfo.vertCount[0] * 1;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; totalWeightCount += surface.vertInfo.vertCount[1] * 2;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; totalWeightCount += surface.vertInfo.vertCount[2] * 3;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; totalWeightCount += surface.vertInfo.vertCount[3] * 4;
} }
} }
weightCollection.weights = std::make_unique<XModelBoneWeight[]>(weightCollection.totalWeightCount); weightCollection.weights.resize(totalWeightCount);
} }
void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, void AddXModelVertexBoneWeights(XModelCommon& out, const XModelSurfs* modelSurfs)
const XModelSurfs* modelSurfs, {
XModelVertexBoneWeightCollection& weightCollection) auto& weightCollection = out.m_bone_weight_data;
{
size_t weightOffset = 0u; size_t weightOffset = 0u;
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
@ -424,7 +274,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
handledVertices += vertList.vertCount; handledVertices += vertList.vertCount;
} }
@ -442,7 +292,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 1; vertsBlendOffset += 1;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
// 2 bone weights // 2 bone weights
@ -459,7 +309,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 3; vertsBlendOffset += 3;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
} }
// 3 bone weights // 3 bone weights
@ -479,7 +329,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 5; vertsBlendOffset += 5;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
} }
// 4 bone weights // 4 bone weights
@ -502,27 +352,28 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 7; vertsBlendOffset += 7;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
} }
handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; handledVertices +=
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
} }
for (; handledVertices < surface.vertCount; handledVertices++) for (; handledVertices < surface.vertCount; handledVertices++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); out.m_vertex_bone_weights.emplace_back(nullptr, 0);
}
} }
} }
}
void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, void AddXModelFaces(XModelCommon& out, const XModelSurfs* modelSurfs)
const DistinctMapper<Material*>& materialMapper, {
const XModelSurfs* modelSurfs,
const int baseSurfaceIndex)
{
for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++)
{ {
const auto& surface = modelSurfs->surfs[surfIndex]; const auto& surface = modelSurfs->surfs[surfIndex];
auto& object = out.m_objects[surfIndex];
object.m_faces.reserve(surface.triCount);
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
{ {
const auto& tri = surface.triIndices[triIndex]; const auto& tri = surface.triIndices[triIndex];
@ -531,67 +382,134 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer,
face.vertexIndex[0] = tri[0] + surface.baseVertIndex; face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
face.vertexIndex[1] = tri[1] + surface.baseVertIndex; face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
face.vertexIndex[2] = tri[2] + surface.baseVertIndex; face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
face.objectIndex = static_cast<int>(surfIndex); object.m_faces.emplace_back(face);
face.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); }
writer.AddFace(face);
} }
} }
}
void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod) void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
{ {
const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
DistinctMapper<Material*> materialMapper(model->numsurfs);
AllocateXModelBoneWeights(modelSurfs, out.m_bone_weight_data);
out.m_name = modelSurfs->name;
AddXModelBones(out, context, model);
AddXModelMaterials(out, materialMapper, model);
AddXModelObjects(out, modelSurfs, materialMapper, model->lodInfo[lod].surfIndex);
AddXModelVertices(out, modelSurfs);
AddXModelVertexBoneWeights(out, modelSurfs);
AddXModelFaces(out, modelSurfs);
}
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
if (!mtlFile)
return;
const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs);
writer->Write(common);
}
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto* modelSurfs = model->lodInfo[lod].modelSurfs; const auto* modelSurfs = model->lodInfo[lod].modelSurfs;
if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr)
return; return;
const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".XMODEL_EXPORT"); const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.obj", modelSurfs->name));
if (!assetFile) if (!assetFile)
return; return;
const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); const auto writer =
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs); DistinctMapper<Material*> materialMapper(model->numsurfs);
XModelVertexBoneWeightCollection boneWeightCollection;
AllocateXModelBoneWeights(modelSurfs, boneWeightCollection);
AddXModelBones(context, *writer, model); writer->Write(common);
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 DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
} {
void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
auto* surfZoneState = context.GetZoneAssetDumperState<SurfsDumpingZoneState>();
const auto* model = asset->Asset(); 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 = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
template<typename T>
void DumpGltfLod(
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* 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<T>(*assetFile);
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
{ {
if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) XModelCommon common;
continue; PopulateXModelWriter(common, context, currentLod, asset->Asset());
DumpXModelExportLod(context, asset, currentLod);
}
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
switch (ObjWriting::Configuration.ModelOutputFormat) switch (ObjWriting::Configuration.ModelOutputFormat)
{ {
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
DumpObj(context, asset); DumpObjLod(common, context, asset, currentLod);
if (currentLod == 0u)
DumpObjMtl(common, context, asset);
break; break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
DumpXModelExport(context, asset); DumpXModelExportLod(common, context, asset, currentLod);
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
break; break;
default: default:
assert(false); assert(false);
break; break;
} }
}
}
} // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpXModelSurfs(context, asset);
} }

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h" #include "Dumping/AbstractAssetDumper.h"
#include "Game/IW5/IW5.h" #include "Game/IW5/IW5.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace IW5 namespace IW5
{ {
class AssetDumperXModel final : public AbstractAssetDumper<XModel> class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{ {
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& 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<XModel>* asset, const unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected: protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override; bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -2,23 +2,31 @@
#include "Game/T5/CommonT5.h" #include "Game/T5/CommonT5.h"
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Model/XModel/XModelExportWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h"
#include "Utils/HalfFloat.h" #include "Utils/HalfFloat.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.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 "XModel/XModelWriter.h"
#include <cassert> #include <cassert>
#include <sstream> #include <format>
using namespace T5; using namespace T5;
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset) namespace
{ {
return !asset->m_name.empty() && asset->m_name[0] != ','; std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
} {
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
}
GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) GfxImage* GetMaterialColorMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -41,10 +49,10 @@ GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) GfxImage* GetMaterialNormalMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -67,10 +75,10 @@ GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
} }
GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) GfxImage* GetMaterialSpecularMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -93,166 +101,10 @@ GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material)
} }
return potentialTextureDefs[0]->u.image; return potentialTextureDefs[0]->u.image;
}
void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
{
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<int>(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<int>(surfIndex), objVertex);
writer.AddNormal(static_cast<int>(surfIndex), objNormal);
writer.AddUv(static_cast<int>(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<int>(surfIndex), face);
}
}
}
void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> materialMapper(model->numsurfs);
AddObjMaterials(writer, materialMapper, model);
writer.WriteMtl(*matFile);
}
void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> 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<XModel>* 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++) for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
{ {
XModelBone bone; XModelBone bone;
@ -273,8 +125,8 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; bone.globalOffset[0] = model->baseMat[boneNum].trans[0];
bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; bone.globalOffset[1] = model->baseMat[boneNum].trans[1];
bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; bone.globalOffset[2] = model->baseMat[boneNum].trans[2];
bone.globalRotation = bone.globalRotation = Quaternion32(
Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]);
if (boneNum < model->numRootBones) if (boneNum < model->numRootBones)
{ {
@ -294,12 +146,20 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]));
} }
writer.AddBone(std::move(bone)); out.m_bones.emplace_back(std::move(bone));
}
} }
}
void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model) const char* AssetName(const char* input)
{ {
if (input && input[0] == ',')
return &input[1];
return input;
}
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
{
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
{ {
Material* material = model->materialHandles[surfaceMaterialNum]; Material* material = model->materialHandles[surfaceMaterialNum];
@ -308,31 +168,41 @@ void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, Distinc
XModelMaterial xMaterial; XModelMaterial xMaterial;
xMaterial.ApplyDefaults(); xMaterial.ApplyDefaults();
xMaterial.name = material->info.name; xMaterial.name = AssetName(material->info.name);
const auto* colorMap = GetMaterialColorMap(material); const auto* colorMap = GetMaterialColorMap(material);
if (colorMap) if (colorMap)
xMaterial.colorMapName = std::string(colorMap->name); xMaterial.colorMapName = AssetName(colorMap->name);
writer.AddMaterial(std::move(xMaterial)); const auto* normalMap = GetMaterialNormalMap(material);
if (normalMap)
xMaterial.normalMapName = AssetName(normalMap->name);
const auto* specularMap = GetMaterialSpecularMap(material);
if (specularMap)
xMaterial.specularMapName = AssetName(specularMap->name);
out.m_materials.emplace_back(std::move(xMaterial));
}
} }
} }
}
void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
{ {
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
XModelObject object; XModelObject object;
object.name = "surf" + std::to_string(surfIndex); object.name = std::format("surf{}", surfIndex);
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
writer.AddObject(std::move(object)); out.m_objects.emplace_back(std::move(object));
}
} }
}
void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
@ -365,45 +235,43 @@ void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XM
vertex.uv[0] = uv[0]; vertex.uv[0] = uv[0];
vertex.uv[1] = uv[1]; vertex.uv[1] = uv[1];
writer.AddVertex(vertex); out.m_vertices.emplace_back(vertex);
}
} }
} }
}
void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
weightCollection.totalWeightCount = 0u; auto totalWeightCount = 0u;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
if (surface.vertList) if (surface.vertList)
{ {
weightCollection.totalWeightCount += surface.vertListCount; totalWeightCount += surface.vertListCount;
} }
if (surface.vertInfo.vertsBlend) if (surface.vertInfo.vertsBlend)
{ {
weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; totalWeightCount += surface.vertInfo.vertCount[0] * 1;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; totalWeightCount += surface.vertInfo.vertCount[1] * 2;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; totalWeightCount += surface.vertInfo.vertCount[2] * 3;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; totalWeightCount += surface.vertInfo.vertCount[3] * 4;
} }
} }
weightCollection.weights = std::make_unique<XModelBoneWeight[]>(weightCollection.totalWeightCount); weightCollection.weights.resize(totalWeightCount);
} }
void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
const XModel* model, {
const unsigned lod,
XModelVertexBoneWeightCollection& weightCollection)
{
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
auto& weightCollection = out.m_bone_weight_data;
size_t weightOffset = 0u; size_t weightOffset = 0u;
@ -423,7 +291,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
handledVertices += vertList.vertCount; handledVertices += vertList.vertCount;
} }
@ -441,7 +309,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 1; vertsBlendOffset += 1;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
// 2 bone weights // 2 bone weights
@ -458,7 +326,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 3; vertsBlendOffset += 3;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
} }
// 3 bone weights // 3 bone weights
@ -478,7 +346,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 5; vertsBlendOffset += 5;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
} }
// 4 bone weights // 4 bone weights
@ -501,28 +369,31 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 7; vertsBlendOffset += 7;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
} }
handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; handledVertices +=
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
} }
for (; handledVertices < surface.vertCount; handledVertices++) for (; handledVertices < surface.vertCount; handledVertices++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); out.m_vertex_bone_weights.emplace_back(nullptr, 0);
}
} }
} }
}
void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfIndex = model->lodInfo[lod].surfIndex;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
auto& object = out.m_objects[surfIndex];
object.m_faces.reserve(surface.triCount);
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
{ {
const auto& tri = surface.triIndices[triIndex]; const auto& tri = surface.triIndices[triIndex];
@ -531,63 +402,125 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const Disti
face.vertexIndex[0] = tri[0] + surface.baseVertIndex; face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
face.vertexIndex[1] = tri[1] + surface.baseVertIndex; face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
face.vertexIndex[2] = tri[2] + surface.baseVertIndex; face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
face.objectIndex = static_cast<int>(surfIndex); object.m_faces.emplace_back(face);
face.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); }
writer.AddFace(face);
} }
} }
}
void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod) void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
{ {
DistinctMapper<Material*> materialMapper(model->numsurfs);
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
out.m_name = std::format("{}_lod{}", model->name, lod);
AddXModelBones(out, context, model);
AddXModelMaterials(out, materialMapper, model);
AddXModelObjects(out, model, lod, materialMapper);
AddXModelVertices(out, model, lod);
AddXModelVertexBoneWeights(out, model, lod);
AddXModelFaces(out, model, lod);
}
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
std::ostringstream ss; if (!mtlFile)
ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; return;
const auto assetFile = context.OpenAssetFile(ss.str()); const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs);
writer->Write(common);
}
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
{
const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
if (!assetFile) if (!assetFile)
return; return;
const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); const auto writer =
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs); DistinctMapper<Material*> materialMapper(model->numsurfs);
XModelVertexBoneWeightCollection boneWeightCollection;
AllocateXModelBoneWeights(model, lod, boneWeightCollection);
AddXModelBones(context, *writer, model); writer->Write(common);
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 DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
} {
void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
if (!assetFile)
return;
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
template<typename T>
void DumpGltfLod(
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* 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<T>(*assetFile);
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
{ {
DumpXModelExportLod(context, asset, currentLod); XModelCommon common;
} PopulateXModelWriter(common, context, currentLod, asset->Asset());
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
switch (ObjWriting::Configuration.ModelOutputFormat) switch (ObjWriting::Configuration.ModelOutputFormat)
{ {
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
DumpObj(context, asset); DumpObjLod(common, context, asset, currentLod);
if (currentLod == 0u)
DumpObjMtl(common, context, asset);
break; break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
DumpXModelExport(context, asset); DumpXModelExportLod(common, context, asset, currentLod);
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
break; break;
default: default:
assert(false); assert(false);
break; break;
} }
}
}
} // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpXModelSurfs(context, asset);
} }

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h" #include "Dumping/AbstractAssetDumper.h"
#include "Game/T5/T5.h" #include "Game/T5/T5.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace T5 namespace T5
{ {
class AssetDumperXModel final : public AbstractAssetDumper<XModel> class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{ {
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& 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<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected: protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override; bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -2,23 +2,31 @@
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
#include "Math/Quaternion.h" #include "Math/Quaternion.h"
#include "Model/XModel/XModelExportWriter.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/DistinctMapper.h"
#include "Utils/HalfFloat.h" #include "Utils/HalfFloat.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.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 "XModel/XModelWriter.h"
#include <cassert> #include <cassert>
#include <sstream> #include <format>
using namespace T6; using namespace T6;
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset) namespace
{ {
return !asset->m_name.empty() && asset->m_name[0] != ','; std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension)
} {
return std::format("model_export/{}_lod{}{}", modelName, lod, extension);
}
GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) GfxImage* GetMaterialColorMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -53,10 +61,10 @@ GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material)
} }
return potentialTextureDefs[0]->image; return potentialTextureDefs[0]->image;
} }
GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) GfxImage* GetMaterialNormalMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -79,10 +87,10 @@ GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material)
} }
return potentialTextureDefs[0]->image; return potentialTextureDefs[0]->image;
} }
GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) GfxImage* GetMaterialSpecularMap(const Material* material)
{ {
std::vector<MaterialTextureDef*> potentialTextureDefs; std::vector<MaterialTextureDef*> potentialTextureDefs;
for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++)
@ -105,166 +113,10 @@ GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material)
} }
return potentialTextureDefs[0]->image; return potentialTextureDefs[0]->image;
}
void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model)
{
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<int>(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<int>(surfIndex), objVertex);
writer.AddNormal(static_cast<int>(surfIndex), objNormal);
writer.AddUv(static_cast<int>(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<int>(surfIndex), face);
}
}
}
void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> materialMapper(model->numsurfs);
AddObjMaterials(writer, materialMapper, model);
writer.WriteMtl(*matFile);
}
void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo<XModel>* 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<Material*> 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<XModel>* 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++) for (auto boneNum = 0u; boneNum < model->numBones; boneNum++)
{ {
XModelBone bone; XModelBone bone;
@ -306,12 +158,20 @@ void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, Abstr
QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3]));
} }
writer.AddBone(std::move(bone)); out.m_bones.emplace_back(std::move(bone));
}
} }
}
void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model) const char* AssetName(const char* input)
{ {
if (input && input[0] == ',')
return &input[1];
return input;
}
void AddXModelMaterials(XModelCommon& out, DistinctMapper<Material*>& materialMapper, const XModel* model)
{
for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++)
{ {
Material* material = model->materialHandles[surfaceMaterialNum]; Material* material = model->materialHandles[surfaceMaterialNum];
@ -320,31 +180,41 @@ void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, Distinc
XModelMaterial xMaterial; XModelMaterial xMaterial;
xMaterial.ApplyDefaults(); xMaterial.ApplyDefaults();
xMaterial.name = material->info.name; xMaterial.name = AssetName(material->info.name);
const auto* colorMap = GetMaterialColorMap(material); const auto* colorMap = GetMaterialColorMap(material);
if (colorMap) if (colorMap)
xMaterial.colorMapName = std::string(colorMap->name); xMaterial.colorMapName = AssetName(colorMap->name);
writer.AddMaterial(std::move(xMaterial)); const auto* normalMap = GetMaterialNormalMap(material);
if (normalMap)
xMaterial.normalMapName = AssetName(normalMap->name);
const auto* specularMap = GetMaterialSpecularMap(material);
if (specularMap)
xMaterial.specularMapName = AssetName(specularMap->name);
out.m_materials.emplace_back(std::move(xMaterial));
}
} }
} }
}
void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper<Material*>& materialMapper)
{ {
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
XModelObject object; XModelObject object;
object.name = "surf" + std::to_string(surfIndex); object.name = std::format("surf{}", surfIndex);
object.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex));
writer.AddObject(std::move(object)); out.m_objects.emplace_back(std::move(object));
}
} }
}
void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
@ -380,48 +250,46 @@ void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XM
vertex.uv[0] = uv.x; vertex.uv[0] = uv.x;
vertex.uv[1] = uv.y; vertex.uv[1] = uv.y;
writer.AddVertex(vertex); out.m_vertices.emplace_back(vertex);
}
} }
} }
}
void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
if (!surfs) if (!surfs)
return; return;
weightCollection.totalWeightCount = 0u; auto totalWeightCount = 0u;
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
if (surface.vertList) if (surface.vertList)
{ {
weightCollection.totalWeightCount += surface.vertListCount; totalWeightCount += surface.vertListCount;
} }
if (surface.vertInfo.vertsBlend) if (surface.vertInfo.vertsBlend)
{ {
weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; totalWeightCount += surface.vertInfo.vertCount[0] * 1;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; totalWeightCount += surface.vertInfo.vertCount[1] * 2;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; totalWeightCount += surface.vertInfo.vertCount[2] * 3;
weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; totalWeightCount += surface.vertInfo.vertCount[3] * 4;
} }
} }
weightCollection.weights = std::make_unique<XModelBoneWeight[]>(weightCollection.totalWeightCount); weightCollection.weights.resize(totalWeightCount);
} }
void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod)
const XModel* model, {
const unsigned lod,
XModelVertexBoneWeightCollection& weightCollection)
{
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
auto& weightCollection = out.m_bone_weight_data;
if (!surfs) if (!surfs)
return; return;
@ -444,7 +312,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
handledVertices += vertList.vertCount; handledVertices += vertList.vertCount;
} }
@ -462,7 +330,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 1; vertsBlendOffset += 1;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1);
} }
// 2 bone weights // 2 bone weights
@ -479,7 +347,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 3; vertsBlendOffset += 3;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2);
} }
// 3 bone weights // 3 bone weights
@ -499,7 +367,7 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 5; vertsBlendOffset += 5;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3);
} }
// 4 bone weights // 4 bone weights
@ -522,24 +390,24 @@ void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer,
vertsBlendOffset += 7; vertsBlendOffset += 7;
writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 4);
} }
handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; handledVertices +=
surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3];
} }
for (; handledVertices < surface.vertCount; handledVertices++) for (; handledVertices < surface.vertCount; handledVertices++)
{ {
writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); out.m_vertex_bone_weights.emplace_back(nullptr, 0);
}
} }
} }
}
void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper<Material*>& materialMapper, const XModel* model, const unsigned lod) void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod)
{ {
const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex];
const auto surfCount = model->lodInfo[lod].numsurfs; const auto surfCount = model->lodInfo[lod].numsurfs;
const auto baseSurfIndex = model->lodInfo[lod].surfIndex;
if (!surfs) if (!surfs)
return; return;
@ -547,6 +415,9 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const Disti
for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++)
{ {
const auto& surface = surfs[surfIndex]; const auto& surface = surfs[surfIndex];
auto& object = out.m_objects[surfIndex];
object.m_faces.reserve(surface.triCount);
for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++)
{ {
const auto& tri = surface.triIndices[triIndex]; const auto& tri = surface.triIndices[triIndex];
@ -555,63 +426,125 @@ void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const Disti
face.vertexIndex[0] = tri[0] + surface.baseVertIndex; face.vertexIndex[0] = tri[0] + surface.baseVertIndex;
face.vertexIndex[1] = tri[1] + surface.baseVertIndex; face.vertexIndex[1] = tri[1] + surface.baseVertIndex;
face.vertexIndex[2] = tri[2] + surface.baseVertIndex; face.vertexIndex[2] = tri[2] + surface.baseVertIndex;
face.objectIndex = static_cast<int>(surfIndex); object.m_faces.emplace_back(face);
face.materialIndex = static_cast<int>(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); }
writer.AddFace(face);
} }
} }
}
void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, const unsigned lod) void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model)
{ {
DistinctMapper<Material*> materialMapper(model->numsurfs);
AllocateXModelBoneWeights(model, lod, out.m_bone_weight_data);
out.m_name = std::format("{}_lod{}", model->name, lod);
AddXModelBones(out, context, model);
AddXModelMaterials(out, materialMapper, model);
AddXModelObjects(out, model, lod, materialMapper);
AddXModelVertices(out, model, lod);
AddXModelVertexBoneWeights(out, model, lod);
AddXModelFaces(out, model, lod);
}
void DumpObjMtl(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto mtlFile = context.OpenAssetFile(std::format("model_export/{}.mtl", model->name));
std::ostringstream ss; if (!mtlFile)
ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; return;
const auto assetFile = context.OpenAssetFile(ss.str()); const auto writer = obj::CreateMtlWriter(*mtlFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs);
writer->Write(common);
}
void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
{
const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj"));
if (!assetFile) if (!assetFile)
return; return;
const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); const auto writer =
obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
DistinctMapper<Material*> materialMapper(model->numsurfs); DistinctMapper<Material*> materialMapper(model->numsurfs);
XModelVertexBoneWeightCollection boneWeightCollection;
AllocateXModelBoneWeights(model, lod, boneWeightCollection);
AddXModelBones(context, *writer, model); writer->Write(common);
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 DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* asset, const unsigned lod)
} {
void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset(); const auto* model = asset->Asset();
const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT"));
if (!assetFile)
return;
const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
template<typename T>
void DumpGltfLod(
const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo<XModel>* 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<T>(*assetFile);
const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name);
writer->Write(common);
}
void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo<XModel>* asset)
{
const auto* model = asset->Asset();
for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) for (auto currentLod = 0u; currentLod < model->numLods; currentLod++)
{ {
DumpXModelExportLod(context, asset, currentLod); XModelCommon common;
} PopulateXModelWriter(common, context, currentLod, asset->Asset());
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
switch (ObjWriting::Configuration.ModelOutputFormat) switch (ObjWriting::Configuration.ModelOutputFormat)
{ {
case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ:
DumpObj(context, asset); DumpObjLod(common, context, asset, currentLod);
if (currentLod == 0u)
DumpObjMtl(common, context, asset);
break; break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT:
DumpXModelExport(context, asset); DumpXModelExportLod(common, context, asset, currentLod);
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF:
DumpGltfLod<gltf::TextOutput>(common, context, asset, currentLod, ".gltf");
break;
case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB:
DumpGltfLod<gltf::BinOutput>(common, context, asset, currentLod, ".glb");
break; break;
default: default:
assert(false); assert(false);
break; break;
} }
}
}
} // namespace
bool AssetDumperXModel::ShouldDump(XAssetInfo<XModel>* asset)
{
return !asset->m_name.empty() && asset->m_name[0] != ',';
}
void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset)
{
DumpXModelSurfs(context, asset);
} }

View File

@ -2,37 +2,11 @@
#include "Dumping/AbstractAssetDumper.h" #include "Dumping/AbstractAssetDumper.h"
#include "Game/T6/T6.h" #include "Game/T6/T6.h"
#include "Model/Obj/ObjWriter.h"
#include "Model/XModel/AbstractXModelWriter.h"
#include "Utils/DistinctMapper.h"
namespace T6 namespace T6
{ {
class AssetDumperXModel final : public AbstractAssetDumper<XModel> class AssetDumperXModel final : public AbstractAssetDumper<XModel>
{ {
static GfxImage* GetMaterialColorMap(const Material* material);
static GfxImage* GetMaterialNormalMap(const Material* material);
static GfxImage* GetMaterialSpecularMap(const Material* material);
static void AddObjMaterials(ObjWriter& writer, DistinctMapper<Material*>& materialMapper, const XModel* model);
static void AddObjObjects(ObjWriter& writer, const DistinctMapper<Material*>& 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<XModel>* asset, unsigned lod);
static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void DumpObj(AssetDumpingContext& context, XAssetInfo<XModel>* asset);
static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model);
static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper<Material*>& 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<Material*>& materialMapper, const XModel* model, unsigned lod);
static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo<XModel>* asset, unsigned lod);
static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo<XModel>* asset);
protected: protected:
bool ShouldDump(XAssetInfo<XModel>* asset) override; bool ShouldDump(XAssetInfo<XModel>* asset) override;
void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo<XModel>* asset) override;

View File

@ -0,0 +1,71 @@
#include "JsonXModelWriter.h"
#include "Game/T6/CommonT6.h"
#include "Game/T6/Json/JsonXModel.h"
#include <iomanip>
#include <nlohmann/json.hpp>
using namespace nlohmann;
using namespace T6;
namespace
{
class JsonDumper
{
public:
JsonDumper(AssetDumpingContext& context, std::ostream& stream)
: m_stream(stream)
{
}
void Dump(const XModel* xmodel) const
{
JsonXModel jsonXModel;
CreateJsonXModel(jsonXModel, *xmodel);
json jRoot = jsonXModel;
jRoot["_type"] = "xmodel";
jRoot["_version"] = 1;
m_stream << std::setw(4) << jRoot << "\n";
}
private:
static const char* AssetName(const char* input)
{
if (input && input[0] == ',')
return &input[1];
return input;
}
void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) const
{
jXModel.collLod = xmodel.collLod;
if (xmodel.physPreset && xmodel.physPreset->name)
jXModel.physPreset = AssetName(xmodel.physPreset->name);
if (xmodel.physConstraints && xmodel.physConstraints->name)
jXModel.physConstraints = AssetName(xmodel.physConstraints->name);
jXModel.flags = xmodel.flags;
jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x;
jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y;
jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z;
jXModel.lightingOriginRange = xmodel.lightingOriginRange;
}
std::ostream& m_stream;
};
} // namespace
namespace T6
{
void DumpXModelAsJson(std::ostream& stream, const XModel* xmodel, AssetDumpingContext& context)
{
const JsonDumper dumper(context, stream);
dumper.Dump(xmodel);
}
} // namespace T6

View File

@ -0,0 +1,11 @@
#pragma once
#include "Dumping/AssetDumpingContext.h"
#include "Game/T6/T6.h"
#include <ostream>
namespace T6
{
void DumpXModelAsJson(std::ostream& stream, const XModel* xmodel, AssetDumpingContext& context);
} // namespace T6

View File

@ -1,157 +0,0 @@
#include "ObjWriter.h"
#include "Game/IW4/CommonIW4.h"
ObjWriter::ObjWriter(std::string gameName, std::string zoneName)
: m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void ObjWriter::AddObject(ObjObject object)
{
m_objects.emplace_back(std::move(object));
m_object_data.emplace_back();
}
void ObjWriter::AddMaterial(MtlMaterial material)
{
m_materials.emplace_back(std::move(material));
}
void ObjWriter::AddVertex(const int objectId, const ObjVertex vertex)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_vertices.Add(vertex);
}
void ObjWriter::AddNormal(const int objectId, const ObjNormal normal)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_normals.Add(normal);
}
void ObjWriter::AddUv(const int objectId, const ObjUv uv)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_uvs.Add(uv);
}
void ObjWriter::AddFace(const int objectId, const ObjFace face)
{
if (objectId < 0 || static_cast<unsigned>(objectId) >= m_object_data.size())
return;
m_object_data[objectId].m_faces.push_back(face);
}
void ObjWriter::GetObjObjectDataOffsets(std::vector<ObjObjectDataOffsets>& inputOffsets, std::vector<ObjObjectDataOffsets>& distinctOffsets)
{
ObjObjectDataOffsets currentInputOffsets{};
ObjObjectDataOffsets currentDistinctOffsets{};
for (const auto& objectData : m_object_data)
{
inputOffsets.push_back(currentInputOffsets);
distinctOffsets.push_back(currentDistinctOffsets);
currentInputOffsets.vertexOffset += objectData.m_vertices.GetInputValueCount();
currentInputOffsets.normalOffset += objectData.m_normals.GetInputValueCount();
currentInputOffsets.uvOffset += objectData.m_uvs.GetInputValueCount();
currentDistinctOffsets.vertexOffset += objectData.m_vertices.GetDistinctValueCount();
currentDistinctOffsets.normalOffset += objectData.m_normals.GetDistinctValueCount();
currentDistinctOffsets.uvOffset += objectData.m_uvs.GetDistinctValueCount();
}
}
void ObjWriter::WriteObj(std::ostream& stream)
{
WriteObj(stream, std::string());
}
void ObjWriter::WriteObj(std::ostream& stream, const std::string& mtlName)
{
stream << "# OpenAssetTools OBJ File ( " << m_game_name << ")\n";
stream << "# Game Origin: " << m_game_name << "\n";
stream << "# Zone Origin: " << m_zone_name << "\n";
if (!mtlName.empty())
stream << "mtllib " << mtlName << "\n";
std::vector<ObjObjectDataOffsets> inputOffsetsByObject;
std::vector<ObjObjectDataOffsets> distinctOffsetsByObject;
GetObjObjectDataOffsets(inputOffsetsByObject, distinctOffsetsByObject);
auto objectIndex = 0;
for (const auto& object : m_objects)
{
const auto& objectData = m_object_data[objectIndex];
stream << "o " << object.name << "\n";
for (const auto& v : objectData.m_vertices.GetDistinctValues())
stream << "v " << v.coordinates[0] << " " << v.coordinates[1] << " " << v.coordinates[2] << "\n";
for (const auto& uv : objectData.m_uvs.GetDistinctValues())
stream << "vt " << uv.uv[0] << " " << uv.uv[1] << "\n";
for (const auto& n : objectData.m_normals.GetDistinctValues())
stream << "vn " << n.normal[0] << " " << n.normal[1] << " " << n.normal[2] << "\n";
if (object.materialIndex >= 0 && static_cast<unsigned>(object.materialIndex) < m_materials.size())
stream << "usemtl " << m_materials[object.materialIndex].materialName << "\n";
for (const auto& f : objectData.m_faces)
{
const size_t v[3]{objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[0] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[1] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(f.vertexIndex[2] - inputOffsetsByObject[objectIndex].vertexOffset)
+ distinctOffsetsByObject[objectIndex].vertexOffset + 1};
const size_t n[3]{objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[0] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[1] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(f.normalIndex[2] - inputOffsetsByObject[objectIndex].normalOffset)
+ distinctOffsetsByObject[objectIndex].normalOffset + 1};
const size_t uv[3]{objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[0] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[1] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(f.uvIndex[2] - inputOffsetsByObject[objectIndex].uvOffset)
+ distinctOffsetsByObject[objectIndex].uvOffset + 1};
stream << "f " << v[0] << "/" << uv[0] << "/" << n[0] << " " << v[1] << "/" << uv[1] << "/" << n[1] << " " << v[2] << "/" << uv[2] << "/" << n[2]
<< "\n";
}
objectIndex++;
}
}
void ObjWriter::WriteMtl(std::ostream& stream)
{
stream << "# OpenAssetTools MAT File ( " << m_game_name << ")\n";
stream << "# Game Origin: " << m_game_name << "\n";
stream << "# Zone Origin: " << m_zone_name << "\n";
stream << "# Material count: " << m_materials.size() << "\n";
for (const auto& material : m_materials)
{
stream << "\n";
stream << "newmtl " << material.materialName << "\n";
if (!material.colorMapName.empty())
stream << "map_Kd ../images/" << material.colorMapName << ".dds\n";
if (!material.normalMapName.empty())
stream << "map_bump ../images/" << material.normalMapName << ".dds\n";
if (!material.specularMapName.empty())
stream << "map_Ks ../images/" << material.specularMapName << ".dds\n";
}
}

View File

@ -1,48 +0,0 @@
#pragma once
#include "Model/Obj/ObjCommon.h"
#include "Utils/DistinctMapper.h"
#include <ostream>
#include <vector>
class ObjWriter
{
protected:
struct ObjObjectData
{
DistinctMapper<ObjVertex> m_vertices;
DistinctMapper<ObjNormal> m_normals;
DistinctMapper<ObjUv> m_uvs;
std::vector<ObjFace> m_faces;
};
struct ObjObjectDataOffsets
{
size_t vertexOffset;
size_t normalOffset;
size_t uvOffset;
};
std::string m_game_name;
std::string m_zone_name;
std::vector<ObjObject> m_objects;
std::vector<ObjObjectData> m_object_data;
std::vector<MtlMaterial> m_materials;
void GetObjObjectDataOffsets(std::vector<ObjObjectDataOffsets>& inputOffsets, std::vector<ObjObjectDataOffsets>& distinctOffsets);
public:
ObjWriter(std::string gameName, std::string zoneName);
void AddObject(ObjObject object);
void AddMaterial(MtlMaterial material);
void AddVertex(int objectId, ObjVertex vertex);
void AddNormal(int objectId, ObjNormal normal);
void AddUv(int objectId, ObjUv uv);
void AddFace(int objectId, ObjFace face);
void WriteObj(std::ostream& stream);
void WriteObj(std::ostream& stream, const std::string& mtlName);
void WriteMtl(std::ostream& stream);
};

View File

@ -1,33 +0,0 @@
#include "AbstractXModelWriter.h"
AbstractXModelWriter::AbstractXModelWriter() = default;
void AbstractXModelWriter::AddObject(XModelObject object)
{
m_objects.emplace_back(std::move(object));
}
void AbstractXModelWriter::AddBone(XModelBone bone)
{
m_bones.emplace_back(std::move(bone));
}
void AbstractXModelWriter::AddMaterial(XModelMaterial material)
{
m_materials.emplace_back(std::move(material));
}
void AbstractXModelWriter::AddVertex(XModelVertex vertex)
{
m_vertices.emplace_back(vertex);
}
void AbstractXModelWriter::AddVertexBoneWeights(XModelVertexBoneWeights vertexBoneWeights)
{
m_vertex_bone_weights.emplace_back(vertexBoneWeights);
}
void AbstractXModelWriter::AddFace(XModelFace face)
{
m_faces.emplace_back(face);
}

View File

@ -1,26 +0,0 @@
#pragma once
#include "Model/XModel/XModelCommon.h"
#include <vector>
class AbstractXModelWriter
{
protected:
std::vector<XModelObject> m_objects;
std::vector<XModelBone> m_bones;
std::vector<XModelMaterial> m_materials;
std::vector<XModelVertex> m_vertices;
std::vector<XModelVertexBoneWeights> m_vertex_bone_weights;
std::vector<XModelFace> m_faces;
public:
AbstractXModelWriter();
void AddObject(XModelObject object);
void AddBone(XModelBone bone);
void AddMaterial(XModelMaterial material);
void AddVertex(XModelVertex vertex);
void AddVertexBoneWeights(XModelVertexBoneWeights vertexBoneWeights);
void AddFace(XModelFace face);
};

View File

@ -1,221 +0,0 @@
#include "XModelExportWriter.h"
#include "Math/Quaternion.h"
#include <iomanip>
#include <iostream>
class XModelExportWriterBase : public XModelExportWriter
{
protected:
std::string m_game_name;
std::string m_zone_name;
VertexMerger m_vertex_merger;
void PrepareVertexMerger()
{
m_vertex_merger = VertexMerger(m_vertices.size());
auto vertexOffset = 0u;
for (const auto& vertex : m_vertices)
{
XModelVertexBoneWeights weights{nullptr, 0};
if (vertexOffset < m_vertex_bone_weights.size())
weights = m_vertex_bone_weights[vertexOffset];
m_vertex_merger.Add(VertexMergerPos{vertex.coordinates[0], vertex.coordinates[1], vertex.coordinates[2], weights.weights, weights.weightCount});
vertexOffset++;
}
}
void WriteHeader(std::ostream& stream, const int version) const
{
stream << "// OpenAssetTools XMODEL_EXPORT File\n";
stream << "// Game Origin: " << m_game_name << "\n";
stream << "// Zone Origin: " << m_zone_name << "\n";
stream << "MODEL\n";
stream << "VERSION " << version << "\n";
stream << "\n";
}
void WriteBones(std::ostream& stream) const
{
stream << "NUMBONES " << m_bones.size() << "\n";
size_t boneNum = 0u;
for (const auto& bone : m_bones)
{
stream << "BONE " << boneNum << " ";
if (bone.parentIndex < 0)
stream << "-1";
else
stream << bone.parentIndex;
stream << " \"" << bone.name << "\"\n";
boneNum++;
}
stream << "\n";
boneNum = 0u;
for (const auto& bone : m_bones)
{
stream << "BONE " << boneNum << "\n";
stream << "OFFSET ";
stream << std::setprecision(6) << std::fixed << bone.globalOffset[0] << ", " << std::setprecision(6) << std::fixed << bone.globalOffset[1] << ", "
<< std::setprecision(6) << std::fixed << bone.globalOffset[2] << "\n";
stream << "SCALE ";
stream << std::setprecision(6) << std::fixed << bone.scale[0] << ", " << std::setprecision(6) << std::fixed << bone.scale[1] << ", "
<< std::setprecision(6) << std::fixed << bone.scale[2] << "\n";
const Matrix32 mat = bone.globalRotation.ToMatrix();
stream << "X " << std::setprecision(6) << std::fixed << mat.m_data[0][0] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][0] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][0] << "\n";
stream << "Y " << std::setprecision(6) << std::fixed << mat.m_data[0][1] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][1] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][1] << "\n";
stream << "Z " << std::setprecision(6) << std::fixed << mat.m_data[0][2] << ", " << std::setprecision(6) << std::fixed << mat.m_data[1][2] << ", "
<< std::setprecision(6) << std::fixed << mat.m_data[2][2] << "\n";
stream << "\n";
boneNum++;
}
}
XModelExportWriterBase(std::string gameName, std::string zoneName)
: m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
};
class XModelExportWriter6 final : public XModelExportWriterBase
{
void WriteVertices(std::ostream& stream) const
{
const auto& distinctVertexValues = m_vertex_merger.GetDistinctValues();
stream << "NUMVERTS " << distinctVertexValues.size() << "\n";
size_t vertexNum = 0u;
for (const auto& vertexPos : distinctVertexValues)
{
stream << "VERT " << vertexNum << "\n";
stream << "OFFSET ";
stream << std::setprecision(6) << std::fixed << vertexPos.x << ", " << std::setprecision(6) << std::fixed << vertexPos.y << ", "
<< std::setprecision(6) << std::fixed << vertexPos.z << "\n";
stream << "BONES " << vertexPos.weightCount << "\n";
for (auto weightIndex = 0u; weightIndex < vertexPos.weightCount; weightIndex++)
{
stream << "BONE " << vertexPos.weights[weightIndex].boneIndex << " " << std::setprecision(6) << std::fixed
<< vertexPos.weights[weightIndex].weight << "\n";
}
stream << "\n";
vertexNum++;
}
}
static void WriteFaceVertex(std::ostream& stream, const size_t index, const XModelVertex& vertex)
{
stream << "VERT " << index << "\n";
stream << "NORMAL " << std::setprecision(6) << std::fixed << vertex.normal[0] << " " << std::setprecision(6) << std::fixed << vertex.normal[1] << " "
<< std::setprecision(6) << std::fixed << vertex.normal[2] << "\n";
stream << "COLOR " << std::setprecision(6) << std::fixed << vertex.color[0] << " " << std::setprecision(6) << std::fixed << vertex.color[1] << " "
<< std::setprecision(6) << std::fixed << vertex.color[2] << " " << std::setprecision(6) << std::fixed << vertex.color[3] << "\n";
stream << "UV 1 " << std::setprecision(6) << std::fixed << vertex.uv[0] << " " << std::setprecision(6) << std::fixed << vertex.uv[1] << "\n";
}
void WriteFaces(std::ostream& stream) const
{
stream << "NUMFACES " << m_faces.size() << "\n";
for (const auto& face : m_faces)
{
const size_t distinctPositions[3]{
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[0]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[1]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[2]),
};
const XModelVertex& v0 = m_vertices[face.vertexIndex[0]];
const XModelVertex& v1 = m_vertices[face.vertexIndex[1]];
const XModelVertex& v2 = m_vertices[face.vertexIndex[2]];
stream << "TRI " << face.objectIndex << " " << face.materialIndex << " 0 0\n";
WriteFaceVertex(stream, distinctPositions[0], v0);
WriteFaceVertex(stream, distinctPositions[1], v1);
WriteFaceVertex(stream, distinctPositions[2], v2);
stream << "\n";
}
}
void WriteObjects(std::ostream& stream) const
{
stream << "NUMOBJECTS " << m_objects.size() << "\n";
size_t objectNum = 0u;
for (const auto& object : m_objects)
{
stream << "OBJECT " << objectNum << " \"" << object.name << "\"\n";
objectNum++;
}
stream << "\n";
}
void WriteMaterials(std::ostream& stream) const
{
stream << "NUMMATERIALS " << m_materials.size() << "\n";
size_t materialNum = 0u;
for (const auto& material : m_materials)
{
const auto colorMapPath = "../images/" + material.colorMapName + ".dds";
stream << "MATERIAL " << materialNum << " \"" << material.name << "\" \"" << material.materialTypeName << "\" \"" << colorMapPath << "\"\n";
stream << "COLOR " << std::setprecision(6) << std::fixed << material.color[0] << " " << std::setprecision(6) << std::fixed << material.color[1]
<< " " << std::setprecision(6) << std::fixed << material.color[2] << " " << std::setprecision(6) << std::fixed << material.color[3] << "\n";
stream << "TRANSPARENCY " << std::setprecision(6) << std::fixed << material.transparency[0] << " " << std::setprecision(6) << std::fixed
<< material.transparency[1] << " " << std::setprecision(6) << std::fixed << material.transparency[2] << " " << std::setprecision(6)
<< std::fixed << material.transparency[3] << "\n";
stream << "AMBIENTCOLOR " << std::setprecision(6) << std::fixed << material.ambientColor[0] << " " << std::setprecision(6) << std::fixed
<< material.ambientColor[1] << " " << std::setprecision(6) << std::fixed << material.ambientColor[2] << " " << std::setprecision(6)
<< std::fixed << material.ambientColor[3] << "\n";
stream << "INCANDESCENCE " << std::setprecision(6) << std::fixed << material.incandescence[0] << " " << std::setprecision(6) << std::fixed
<< material.incandescence[1] << " " << std::setprecision(6) << std::fixed << material.incandescence[2] << " " << std::setprecision(6)
<< std::fixed << material.incandescence[3] << "\n";
stream << "COEFFS " << std::setprecision(6) << std::fixed << material.coeffs[0] << " " << std::setprecision(6) << std::fixed << material.coeffs[1]
<< "\n";
stream << "GLOW " << std::setprecision(6) << std::fixed << material.glow.x << " " << material.glow.y << "\n";
stream << "REFRACTIVE " << material.refractive.x << " " << std::setprecision(6) << std::fixed << material.refractive.y << "\n";
stream << "SPECULARCOLOR " << std::setprecision(6) << std::fixed << material.specularColor[0] << " " << std::setprecision(6) << std::fixed
<< material.specularColor[1] << " " << std::setprecision(6) << std::fixed << material.specularColor[2] << " " << std::setprecision(6)
<< std::fixed << material.specularColor[3] << "\n";
stream << "REFLECTIVECOLOR " << std::setprecision(6) << std::fixed << material.reflectiveColor[0] << " " << std::setprecision(6) << std::fixed
<< material.reflectiveColor[1] << " " << std::setprecision(6) << std::fixed << material.reflectiveColor[2] << " " << std::setprecision(6)
<< std::fixed << material.reflectiveColor[3] << "\n";
stream << "REFLECTIVE " << material.reflective.x << " " << std::setprecision(6) << std::fixed << material.reflective.y << "\n";
stream << "BLINN " << std::setprecision(6) << std::fixed << material.blinn[0] << " " << std::setprecision(6) << std::fixed << material.blinn[1]
<< "\n";
stream << "PHONG " << std::setprecision(6) << std::fixed << material.phong << "\n";
stream << "\n";
materialNum++;
}
}
public:
XModelExportWriter6(std::string gameName, std::string zoneName)
: XModelExportWriterBase(std::move(gameName), std::move(zoneName))
{
}
void Write(std::ostream& stream) override
{
PrepareVertexMerger();
WriteHeader(stream, 6);
WriteBones(stream);
WriteVertices(stream);
WriteFaces(stream);
WriteObjects(stream);
WriteMaterials(stream);
}
};
std::unique_ptr<XModelExportWriter> XModelExportWriter::CreateWriterForVersion6(std::string gameName, std::string zoneName)
{
return std::make_unique<XModelExportWriter6>(std::move(gameName), std::move(zoneName));
}

View File

@ -1,21 +0,0 @@
#pragma once
#include "AbstractXModelWriter.h"
#include <memory>
#include <ostream>
class XModelExportWriter : public AbstractXModelWriter
{
public:
XModelExportWriter() = default;
virtual ~XModelExportWriter() = default;
XModelExportWriter(const XModelExportWriter& other) = default;
XModelExportWriter(XModelExportWriter&& other) noexcept = default;
XModelExportWriter& operator=(const XModelExportWriter& other) = default;
XModelExportWriter& operator=(XModelExportWriter&& other) noexcept = default;
virtual void Write(std::ostream& stream) = 0;
static std::unique_ptr<XModelExportWriter> CreateWriterForVersion6(std::string gameName, std::string zoneName);
};

View File

@ -20,14 +20,16 @@ public:
enum class ModelOutputFormat_e enum class ModelOutputFormat_e
{ {
XMODEL_EXPORT, XMODEL_EXPORT,
OBJ OBJ,
GLTF,
GLB
}; };
bool Verbose = false; bool Verbose = false;
std::vector<bool> AssetTypesToHandleBitfield; std::vector<bool> AssetTypesToHandleBitfield;
ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS;
ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT; ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::GLB;
bool MenuLegacyMode = false; bool MenuLegacyMode = false;
} Configuration; } Configuration;

View File

@ -0,0 +1,232 @@
#include "XModelExportWriter.h"
#include "Math/Quaternion.h"
#include <chrono>
#include <iomanip>
#include <iostream>
class XModelExportWriterBase : public XModelWriter
{
protected:
void PrepareVertexMerger(const XModelCommon& xmodel)
{
m_vertex_merger = VertexMerger(xmodel.m_vertices.size());
auto vertexOffset = 0u;
for (const auto& vertex : xmodel.m_vertices)
{
XModelVertexBoneWeights weights{nullptr, 0};
if (vertexOffset < xmodel.m_vertex_bone_weights.size())
weights = xmodel.m_vertex_bone_weights[vertexOffset];
m_vertex_merger.Add(VertexMergerPos{vertex.coordinates[0], vertex.coordinates[1], vertex.coordinates[2], weights.weights, weights.weightCount});
vertexOffset++;
}
}
void WriteHeader(const int version) const
{
m_stream << "// OpenAssetTools XMODEL_EXPORT File\n";
m_stream << "// Game Origin: " << m_game_name << "\n";
m_stream << "// Zone Origin: " << m_zone_name << "\n";
m_stream << "MODEL\n";
m_stream << "VERSION " << version << "\n";
m_stream << "\n";
}
void WriteBones(const XModelCommon& xmodel) const
{
m_stream << "NUMBONES " << xmodel.m_bones.size() << "\n";
size_t boneNum = 0u;
for (const auto& bone : xmodel.m_bones)
{
m_stream << "BONE " << boneNum << " ";
if (bone.parentIndex < 0)
m_stream << "-1";
else
m_stream << bone.parentIndex;
m_stream << " \"" << bone.name << "\"\n";
boneNum++;
}
m_stream << "\n";
boneNum = 0u;
for (const auto& bone : xmodel.m_bones)
{
m_stream << "BONE " << boneNum << "\n";
m_stream << std::format("OFFSET {:.6f}, {:.6f}, {:.6f}\n", bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]);
m_stream << std::format("SCALE {:.6f}, {:.6f}, {:.6f}\n", bone.scale[0], bone.scale[1], bone.scale[2]);
const Matrix32 mat = bone.globalRotation.ToMatrix();
m_stream << std::format("X {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][0], mat.m_data[1][0], mat.m_data[2][0]);
m_stream << std::format("Y {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][1], mat.m_data[1][1], mat.m_data[2][1]);
m_stream << std::format("Z {:.6f}, {:.6f}, {:.6f}\n", mat.m_data[0][2], mat.m_data[1][2], mat.m_data[2][2]);
m_stream << '\n';
boneNum++;
}
}
XModelExportWriterBase(std::ostream& stream, std::string gameName, std::string zoneName)
: m_stream(stream),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
std::ostream& m_stream;
std::string m_game_name;
std::string m_zone_name;
VertexMerger m_vertex_merger;
};
class XModelExportWriter6 final : public XModelExportWriterBase
{
void WriteVertices(const XModelCommon& xmodel) const
{
const auto& distinctVertexValues = m_vertex_merger.GetDistinctValues();
m_stream << "NUMVERTS " << distinctVertexValues.size() << "\n";
size_t vertexNum = 0u;
for (const auto& vertexPos : distinctVertexValues)
{
m_stream << "VERT " << vertexNum << "\n";
m_stream << std::format("OFFSET {:.6f}, {:.6f}, {:.6f}\n", vertexPos.x, vertexPos.y, vertexPos.z);
m_stream << "BONES " << vertexPos.weightCount << "\n";
for (auto weightIndex = 0u; weightIndex < vertexPos.weightCount; weightIndex++)
{
const auto& weight = vertexPos.weights[weightIndex];
m_stream << std::format("BONE {} {:.6f}\n", weight.boneIndex, weight.weight);
}
m_stream << "\n";
vertexNum++;
}
}
void WriteFaceVertex(const size_t index, const XModelVertex& vertex) const
{
m_stream << "VERT " << index << "\n";
m_stream << std::format("NORMAL {:.6f} {:.6f} {:.6f}\n", vertex.normal[0], vertex.normal[1], vertex.normal[2]);
m_stream << std::format("COLOR {:.6f} {:.6f} {:.6f} {:.6f}\n", vertex.color[0], vertex.color[1], vertex.color[2], vertex.color[3]);
m_stream << std::format("UV 1 {:.6f} {:.6f}\n", vertex.uv[0], vertex.uv[1]);
}
void WriteFaces(const XModelCommon& xmodel) const
{
auto totalFaceCount = 0u;
for (const auto& object : xmodel.m_objects)
totalFaceCount += object.m_faces.size();
m_stream << "NUMFACES " << totalFaceCount << "\n";
auto objectIndex = 0u;
for (const auto& object : xmodel.m_objects)
{
for (const auto& face : object.m_faces)
{
const size_t distinctPositions[3]{
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[0]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[1]),
m_vertex_merger.GetDistinctPositionByInputPosition(face.vertexIndex[2]),
};
const XModelVertex& v0 = xmodel.m_vertices[face.vertexIndex[0]];
const XModelVertex& v1 = xmodel.m_vertices[face.vertexIndex[1]];
const XModelVertex& v2 = xmodel.m_vertices[face.vertexIndex[2]];
m_stream << "TRI " << objectIndex << " " << object.materialIndex << " 0 0\n";
WriteFaceVertex(distinctPositions[0], v0);
WriteFaceVertex(distinctPositions[1], v1);
WriteFaceVertex(distinctPositions[2], v2);
m_stream << "\n";
}
objectIndex++;
}
}
void WriteObjects(const XModelCommon& xmodel) const
{
m_stream << "NUMOBJECTS " << xmodel.m_objects.size() << "\n";
size_t objectNum = 0u;
for (const auto& object : xmodel.m_objects)
{
m_stream << "OBJECT " << objectNum << " \"" << object.name << "\"\n";
objectNum++;
}
m_stream << "\n";
}
void WriteMaterials(const XModelCommon& xmodel) const
{
m_stream << "NUMMATERIALS " << xmodel.m_materials.size() << "\n";
size_t materialNum = 0u;
for (const auto& material : xmodel.m_materials)
{
const auto colorMapPath = "../images/" + material.colorMapName + ".dds";
m_stream << "MATERIAL " << materialNum << " \"" << material.name << "\" \"" << material.materialTypeName << "\" \"" << colorMapPath << "\"\n";
m_stream << std::format("COLOR {:.6f} {:.6f} {:.6f} {:.6f}\n", material.color[0], material.color[1], material.color[2], material.color[3]);
m_stream << std::format("TRANSPARENCY {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.transparency[0],
material.transparency[1],
material.transparency[2],
material.transparency[3]);
m_stream << std::format("AMBIENTCOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.ambientColor[0],
material.ambientColor[1],
material.ambientColor[2],
material.ambientColor[3]);
m_stream << std::format("INCANDESCENCE {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.incandescence[0],
material.incandescence[1],
material.incandescence[2],
material.incandescence[3]);
m_stream << std::format("COEFFS {:.6f} {:.6f}\n", material.coeffs[0], material.coeffs[1]);
m_stream << std::format("GLOW {:.6f} {}\n", material.glow.x, material.glow.y);
m_stream << std::format("REFRACTIVE {} {:.6f}\n", material.refractive.x, material.refractive.y);
m_stream << std::format("SPECULARCOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.specularColor[0],
material.specularColor[1],
material.specularColor[2],
material.specularColor[3]);
m_stream << std::format("REFLECTIVECOLOR {:.6f} {:.6f} {:.6f} {:.6f}\n",
material.reflectiveColor[0],
material.reflectiveColor[1],
material.reflectiveColor[2],
material.reflectiveColor[3]);
m_stream << std::format("REFLECTIVE {} {:.6f}\n", material.reflective.x, material.reflective.y);
m_stream << std::format("BLINN {:.6f} {:.6f}\n", material.blinn[0], material.blinn[1]);
m_stream << std::format("PHONG {:.6f}\n", material.phong);
m_stream << '\n';
materialNum++;
}
}
public:
XModelExportWriter6(std::ostream& stream, std::string gameName, std::string zoneName)
: XModelExportWriterBase(stream, std::move(gameName), std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
PrepareVertexMerger(xmodel);
WriteHeader(6);
WriteBones(xmodel);
WriteVertices(xmodel);
WriteFaces(xmodel);
WriteObjects(xmodel);
WriteMaterials(xmodel);
}
};
namespace xmodel_export
{
std::unique_ptr<XModelWriter> CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName)
{
return std::make_unique<XModelExportWriter6>(stream, std::move(gameName), std::move(zoneName));
}
} // namespace xmodel_export

View File

@ -0,0 +1,11 @@
#pragma once
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace xmodel_export
{
std::unique_ptr<XModelWriter> CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName);
}

View File

@ -0,0 +1,86 @@
#include "GltfBinOutput.h"
#include "Utils/Alignment.h"
#include "XModel/Gltf/GltfConstants.h"
#include <cstdint>
#include <nlohmann/json.hpp>
using namespace gltf;
BinOutput::BinOutput(std::ostream& stream)
: m_stream(stream)
{
}
void BinOutput::Write(const void* data, const size_t dataSize) const
{
m_stream.write(static_cast<const char*>(data), dataSize);
}
void BinOutput::AlignToFour(const char value) const
{
const auto offset = m_stream.tellp();
if (offset % 4 > 0)
{
const uint32_t alignmentValue = FileUtils::MakeMagic32(value, value, value, value);
Write(&alignmentValue, 4u - (offset % 4u));
}
}
std::optional<std::string> BinOutput::CreateBufferUri(const void* buffer, size_t bufferSize) const
{
return std::nullopt;
}
void BinOutput::EmitJson(const nlohmann::json& json) const
{
static constexpr uint32_t ZERO = 0u;
Write(&GLTF_MAGIC, sizeof(GLTF_MAGIC));
Write(&GLTF_VERSION, sizeof(GLTF_VERSION));
// File length will be filled later
Write(&ZERO, sizeof(ZERO));
// Chunk length will be filled after writing json
Write(&ZERO, sizeof(ZERO));
Write(&CHUNK_MAGIC_JSON, sizeof(CHUNK_MAGIC_JSON));
m_stream << json;
AlignToFour(' ');
const auto offsetAfterData = m_stream.tellp();
const auto jsonDataLength = static_cast<uint32_t>(static_cast<unsigned>(offsetAfterData) - GLTF_JSON_CHUNK_DATA_OFFSET);
// Fill chunk length now
m_stream.seekp(GLTF_JSON_CHUNK_LENGTH_OFFSET, std::ios::beg);
Write(&jsonDataLength, sizeof(jsonDataLength));
// Return to previous pos
m_stream.seekp(offsetAfterData, std::ios::beg);
}
void BinOutput::EmitBuffer(const void* buffer, const size_t bufferSize) const
{
const auto chunkLength = utils::Align<uint32_t>(bufferSize, 4u);
Write(&chunkLength, sizeof(chunkLength));
Write(&CHUNK_MAGIC_BIN, sizeof(CHUNK_MAGIC_BIN));
Write(buffer, bufferSize);
AlignToFour('\0');
}
void BinOutput::Finalize() const
{
const auto fileEndOffset = m_stream.tellp();
const auto fileSize = static_cast<uint32_t>(fileEndOffset);
// Fill chunk length now
m_stream.seekp(GLTF_LENGTH_OFFSET, std::ios::beg);
Write(&fileSize, sizeof(fileSize));
// Return to file end
m_stream.seekp(fileEndOffset, std::ios::beg);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "GltfOutput.h"
#include <ostream>
namespace gltf
{
class BinOutput final : public Output
{
public:
explicit BinOutput(std::ostream& stream);
std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const override;
void EmitJson(const nlohmann::json& json) const override;
void EmitBuffer(const void* buffer, size_t bufferSize) const override;
void Finalize() const override;
private:
void Write(const void* data, size_t dataSize) const;
void AlignToFour(char value) const;
std::ostream& m_stream;
};
} // namespace gltf

View File

@ -0,0 +1,25 @@
#pragma once
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <string>
namespace gltf
{
class Output
{
protected:
Output() = default;
virtual ~Output() = default;
Output(const Output& other) = default;
Output(Output&& other) noexcept = default;
Output& operator=(const Output& other) = default;
Output& operator=(Output&& other) noexcept = default;
public:
virtual std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const = 0;
virtual void EmitJson(const nlohmann::json& json) const = 0;
virtual void EmitBuffer(const void* buffer, size_t bufferSize) const = 0;
virtual void Finalize() const = 0;
};
} // namespace gltf

View File

@ -0,0 +1,51 @@
#include "GltfTextOutput.h"
#include "Utils/Alignment.h"
#include "XModel/Gltf/GltfConstants.h"
#include <iomanip>
#include <nlohmann/json.hpp>
#define LTC_NO_PROTOTYPES
#include <tomcrypt.h>
using namespace gltf;
TextOutput::TextOutput(std::ostream& stream)
: m_stream(stream)
{
}
std::optional<std::string> TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const
{
static constexpr auto URI_PREFIX_LENGTH = std::char_traits<char>::length(GLTF_DATA_URI_PREFIX);
const auto base64Length = 4u * ((bufferSize + 2u) / 3u);
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
std::string output(base64BufferSize, '\0');
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
unsigned long outLength = base64Length + 1u;
const auto result = base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
assert(result == CRYPT_OK);
assert(outLength == base64Length);
return output;
}
void TextOutput::EmitJson(const nlohmann::json& json) const
{
m_stream << std::setw(4) << json;
}
void TextOutput::EmitBuffer(const void* buffer, const size_t bufferSize) const
{
// Nothing to do
}
void TextOutput::Finalize() const
{
// Nothing to do
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "GltfOutput.h"
#include <ostream>
namespace gltf
{
class TextOutput final : public Output
{
public:
explicit TextOutput(std::ostream& stream);
std::optional<std::string> CreateBufferUri(const void* buffer, size_t bufferSize) const override;
void EmitJson(const nlohmann::json& json) const override;
void EmitBuffer(const void* buffer, size_t bufferSize) const override;
void Finalize() const override;
private:
std::ostream& m_stream;
};
} // namespace gltf

View File

@ -0,0 +1,621 @@
#include "GltfWriter.h"
#include "GitVersion.h"
#include "Math/Vector.h"
#include "XModel/Gltf/GltfConstants.h"
#include "XModel/Gltf/JsonGltf.h"
#include <format>
using namespace gltf;
using namespace nlohmann;
namespace
{
constexpr auto GLTF_GENERATOR = "OpenAssetTools " GIT_VERSION;
struct GltfVertex
{
float coordinates[3];
float normal[3];
float uv[2];
};
class GltfWriterImpl final : public gltf::Writer
{
public:
GltfWriterImpl(const Output* output, std::string gameName, std::string zoneName)
: m_output(output),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
JsonRoot gltf;
std::vector<uint8_t> bufferData;
CreateJsonAsset(gltf.asset);
CreateSkeletonNodes(gltf, xmodel);
CreateMeshNode(gltf, xmodel);
CreateRootNode(gltf, xmodel);
CreateMaterials(gltf, xmodel);
CreateBufferViews(gltf, xmodel);
CreateAccessors(gltf, xmodel);
CreateSkin(gltf, xmodel);
CreateMesh(gltf, xmodel);
CreateScene(gltf, xmodel);
FillBufferData(gltf, xmodel, bufferData);
CreateBuffer(gltf, xmodel, bufferData);
const json jRoot = gltf;
m_output->EmitJson(jRoot);
if (!bufferData.empty())
m_output->EmitBuffer(bufferData.data(), bufferData.size());
m_output->Finalize();
}
private:
static void CreateJsonAsset(JsonAsset& asset)
{
asset.version = GLTF_VERSION_STRING;
asset.generator = GLTF_GENERATOR;
}
void CreateMeshNode(JsonRoot& gltf, const XModelCommon& xmodel)
{
JsonNode meshNode;
if (!xmodel.m_name.empty())
meshNode.name = xmodel.m_name;
// We only have one mesh
meshNode.mesh = 0u;
// Only add skin if the model has bones
if (!xmodel.m_bones.empty())
{
// We only have one skin
meshNode.skin = 0u;
}
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
m_mesh_node = gltf.nodes->size();
gltf.nodes->emplace_back(std::move(meshNode));
}
void CreateRootNode(JsonRoot& gltf, const XModelCommon& xmodel)
{
JsonNode rootNode;
if (!xmodel.m_name.empty())
rootNode.name = std::format("{}_skel", xmodel.m_name);
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
rootNode.children.emplace();
rootNode.children->push_back(m_mesh_node);
if (!xmodel.m_bones.empty())
rootNode.children->push_back(m_first_bone_node);
m_root_node = gltf.nodes->size();
gltf.nodes->emplace_back(std::move(rootNode));
}
void CreateMesh(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.meshes.has_value())
gltf.meshes.emplace();
JsonMesh mesh;
const auto hasBoneWeightData = !xmodel.m_vertex_bone_weights.empty();
auto objectIndex = 0u;
for (const auto& object : xmodel.m_objects)
{
JsonMeshPrimitives primitives;
if (object.materialIndex >= 0)
primitives.material = static_cast<unsigned>(object.materialIndex);
primitives.attributes.POSITION = m_position_accessor;
primitives.attributes.NORMAL = m_normal_accessor;
primitives.attributes.TEXCOORD_0 = m_uv_accessor;
if (hasBoneWeightData)
{
primitives.attributes.JOINTS_0 = m_joints_accessor;
primitives.attributes.WEIGHTS_0 = m_weights_accessor;
}
primitives.mode = JsonMeshPrimitivesMode::TRIANGLES;
primitives.indices = m_first_index_accessor + objectIndex;
mesh.primitives.emplace_back(primitives);
objectIndex++;
}
gltf.meshes->emplace_back(std::move(mesh));
}
static void CreateMaterials(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.materials.has_value())
gltf.materials.emplace();
for (const auto& modelMaterial : xmodel.m_materials)
{
JsonMaterial material;
material.name = modelMaterial.name;
if (!modelMaterial.colorMapName.empty())
{
material.pbrMetallicRoughness.emplace();
material.pbrMetallicRoughness->baseColorTexture.emplace();
material.pbrMetallicRoughness->baseColorTexture->index = CreateTexture(gltf, modelMaterial.colorMapName);
material.pbrMetallicRoughness->metallicFactor = 0.0f;
}
if (!modelMaterial.normalMapName.empty())
material.normalTexture.emplace().index = CreateTexture(gltf, modelMaterial.normalMapName);
material.doubleSided = true;
gltf.materials->emplace_back(material);
}
}
static unsigned CreateTexture(JsonRoot& gltf, const std::string& textureName)
{
if (!gltf.textures.has_value())
gltf.textures.emplace();
if (!gltf.images.has_value())
gltf.images.emplace();
auto uri = std::format("../images/{}.dds", textureName);
auto existingTexIndex = 0u;
for (const auto& existingTex : gltf.textures.value())
{
const auto& existingImage = gltf.images.value()[existingTex.source];
if (existingImage.uri == uri)
return existingTexIndex;
existingTexIndex++;
}
JsonImage image;
image.uri = std::move(uri);
const auto imageIndex = gltf.images->size();
gltf.images->emplace_back(std::move(image));
JsonTexture texture;
texture.source = imageIndex;
const auto textureIndex = gltf.textures->size();
gltf.textures->emplace_back(texture);
return textureIndex;
}
void CreateSkeletonNodes(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (xmodel.m_bones.empty())
return;
if (!gltf.nodes.has_value())
gltf.nodes.emplace();
const auto boneCount = xmodel.m_bones.size();
m_first_bone_node = gltf.nodes->size();
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
{
JsonNode boneNode;
const auto& bone = xmodel.m_bones[boneIndex];
Vector3f translation(bone.globalOffset[0], bone.globalOffset[2], -bone.globalOffset[1]);
Quaternion32 rotation(bone.globalRotation.m_x, bone.globalRotation.m_z, -bone.globalRotation.m_y, bone.globalRotation.m_w);
if (bone.parentIndex >= 0)
{
const auto& parentBone = xmodel.m_bones[bone.parentIndex];
translation -= Vector3f(parentBone.globalOffset[0], parentBone.globalOffset[2], -parentBone.globalOffset[1]);
rotation /= Quaternion32(
parentBone.globalRotation.m_x, parentBone.globalRotation.m_z, -parentBone.globalRotation.m_y, parentBone.globalRotation.m_w);
}
rotation.Normalize();
boneNode.name = bone.name;
boneNode.translation = std::to_array({translation.x(), translation.y(), translation.z()});
boneNode.rotation = std::to_array({rotation.m_x, rotation.m_y, rotation.m_z, rotation.m_w});
std::vector<unsigned> children;
for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++)
{
if (xmodel.m_bones[maybeChildIndex].parentIndex == static_cast<int>(boneIndex))
children.emplace_back(maybeChildIndex + m_first_bone_node);
}
if (!children.empty())
boneNode.children = std::move(children);
gltf.nodes->emplace_back(std::move(boneNode));
}
}
void CreateSkin(JsonRoot& gltf, const XModelCommon& xmodel) const
{
if (xmodel.m_bones.empty())
return;
if (!gltf.skins.has_value())
gltf.skins.emplace();
JsonSkin skin;
const auto boneCount = xmodel.m_bones.size();
skin.joints.reserve(boneCount);
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
skin.joints.emplace_back(boneIndex + m_first_bone_node);
skin.inverseBindMatrices = m_inverse_bind_matrices_accessor;
gltf.skins->emplace_back(std::move(skin));
}
void CreateScene(JsonRoot& gltf, const XModelCommon& xmodel) const
{
JsonScene scene;
// Only add skin if the model has bones
scene.nodes.emplace_back(m_root_node);
if (!gltf.scenes.has_value())
gltf.scenes.emplace();
gltf.scenes->emplace_back(std::move(scene));
gltf.scene = 0u;
}
void CreateBufferViews(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.bufferViews.has_value())
gltf.bufferViews.emplace();
unsigned bufferOffset = 0u;
JsonBufferView vertexBufferView;
vertexBufferView.buffer = 0u;
vertexBufferView.byteOffset = bufferOffset;
vertexBufferView.byteStride = sizeof(GltfVertex);
vertexBufferView.byteLength = sizeof(GltfVertex) * xmodel.m_vertices.size();
vertexBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += vertexBufferView.byteLength;
m_vertex_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(vertexBufferView);
if (!xmodel.m_vertex_bone_weights.empty())
{
JsonBufferView jointsBufferView;
jointsBufferView.buffer = 0u;
jointsBufferView.byteOffset = bufferOffset;
jointsBufferView.byteLength = sizeof(uint8_t) * xmodel.m_vertices.size() * 4u;
jointsBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += jointsBufferView.byteLength;
m_joints_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(jointsBufferView);
JsonBufferView weightsBufferView;
weightsBufferView.buffer = 0u;
weightsBufferView.byteOffset = bufferOffset;
weightsBufferView.byteLength = sizeof(float) * xmodel.m_vertices.size() * 4u;
weightsBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += weightsBufferView.byteLength;
m_weights_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(weightsBufferView);
JsonBufferView inverseBindMatricesBufferView;
inverseBindMatricesBufferView.buffer = 0u;
inverseBindMatricesBufferView.byteOffset = bufferOffset;
inverseBindMatricesBufferView.byteLength = sizeof(float) * xmodel.m_bones.size() * 16u;
bufferOffset += inverseBindMatricesBufferView.byteLength;
m_inverse_bind_matrices_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(inverseBindMatricesBufferView);
}
m_first_index_buffer_view = gltf.bufferViews->size();
for (const auto& object : xmodel.m_objects)
{
JsonBufferView indicesBufferView;
indicesBufferView.buffer = 0u;
indicesBufferView.byteOffset = bufferOffset;
indicesBufferView.byteLength = sizeof(unsigned short) * object.m_faces.size() * 3u;
indicesBufferView.target = JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER;
bufferOffset += indicesBufferView.byteLength;
gltf.bufferViews->emplace_back(indicesBufferView);
}
}
void CreateAccessors(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.accessors.has_value())
gltf.accessors.emplace();
JsonAccessor positionAccessor;
positionAccessor.bufferView = m_vertex_buffer_view;
positionAccessor.byteOffset = offsetof(GltfVertex, coordinates);
positionAccessor.componentType = JsonAccessorComponentType::FLOAT;
positionAccessor.count = xmodel.m_vertices.size();
positionAccessor.type = JsonAccessorType::VEC3;
m_position_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(positionAccessor);
JsonAccessor normalAccessor;
normalAccessor.bufferView = m_vertex_buffer_view;
normalAccessor.byteOffset = offsetof(GltfVertex, normal);
normalAccessor.componentType = JsonAccessorComponentType::FLOAT;
normalAccessor.count = xmodel.m_vertices.size();
normalAccessor.type = JsonAccessorType::VEC3;
m_normal_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(normalAccessor);
JsonAccessor uvAccessor;
uvAccessor.bufferView = m_vertex_buffer_view;
uvAccessor.byteOffset = offsetof(GltfVertex, uv);
uvAccessor.componentType = JsonAccessorComponentType::FLOAT;
uvAccessor.count = xmodel.m_vertices.size();
uvAccessor.type = JsonAccessorType::VEC2;
m_uv_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(uvAccessor);
if (!xmodel.m_vertex_bone_weights.empty())
{
JsonAccessor jointsAccessor;
jointsAccessor.bufferView = m_joints_buffer_view;
jointsAccessor.componentType = JsonAccessorComponentType::UNSIGNED_BYTE;
jointsAccessor.count = xmodel.m_vertices.size();
jointsAccessor.type = JsonAccessorType::VEC4;
m_joints_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(jointsAccessor);
JsonAccessor weightsAccessor;
weightsAccessor.bufferView = m_weights_buffer_view;
weightsAccessor.componentType = JsonAccessorComponentType::FLOAT;
weightsAccessor.count = xmodel.m_vertices.size();
weightsAccessor.type = JsonAccessorType::VEC4;
m_weights_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(weightsAccessor);
JsonAccessor inverseBindMatricesAccessor;
inverseBindMatricesAccessor.bufferView = m_inverse_bind_matrices_buffer_view;
inverseBindMatricesAccessor.componentType = JsonAccessorComponentType::FLOAT;
inverseBindMatricesAccessor.count = xmodel.m_bones.size();
inverseBindMatricesAccessor.type = JsonAccessorType::MAT4;
m_inverse_bind_matrices_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(inverseBindMatricesAccessor);
}
m_first_index_accessor = gltf.accessors->size();
for (auto i = 0u; i < xmodel.m_objects.size(); i++)
{
const auto& object = xmodel.m_objects[i];
JsonAccessor indicesAccessor;
indicesAccessor.bufferView = m_first_index_buffer_view + i;
indicesAccessor.componentType = JsonAccessorComponentType::UNSIGNED_SHORT;
indicesAccessor.count = object.m_faces.size() * 3u;
indicesAccessor.type = JsonAccessorType::SCALAR;
gltf.accessors->emplace_back(indicesAccessor);
}
}
void FillBufferData(JsonRoot& gltf, const XModelCommon& xmodel, std::vector<uint8_t>& bufferData) const
{
const auto expectedBufferSize = GetExpectedBufferSize(xmodel);
bufferData.resize(expectedBufferSize);
auto currentBufferOffset = 0u;
float minPosition[3]{
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
};
float maxPosition[3]{
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
};
for (const auto& commonVertex : xmodel.m_vertices)
{
auto* vertex = reinterpret_cast<GltfVertex*>(&bufferData[currentBufferOffset]);
vertex->coordinates[0] = commonVertex.coordinates[0];
vertex->coordinates[1] = commonVertex.coordinates[2];
vertex->coordinates[2] = -commonVertex.coordinates[1];
if (minPosition[0] > vertex->coordinates[0])
minPosition[0] = vertex->coordinates[0];
if (minPosition[1] > vertex->coordinates[1])
minPosition[1] = vertex->coordinates[1];
if (minPosition[2] > vertex->coordinates[2])
minPosition[2] = vertex->coordinates[2];
if (maxPosition[0] < vertex->coordinates[0])
maxPosition[0] = vertex->coordinates[0];
if (maxPosition[1] < vertex->coordinates[1])
maxPosition[1] = vertex->coordinates[1];
if (maxPosition[2] < vertex->coordinates[2])
maxPosition[2] = vertex->coordinates[2];
vertex->normal[0] = commonVertex.normal[0];
vertex->normal[1] = commonVertex.normal[2];
vertex->normal[2] = -commonVertex.normal[1];
vertex->uv[0] = commonVertex.uv[0];
vertex->uv[1] = commonVertex.uv[1];
currentBufferOffset += sizeof(GltfVertex);
}
if (gltf.accessors.has_value())
{
gltf.accessors.value()[m_position_accessor].min = std::vector({minPosition[0], minPosition[1], minPosition[2]});
gltf.accessors.value()[m_position_accessor].max = std::vector({maxPosition[0], maxPosition[1], maxPosition[2]});
}
if (!xmodel.m_vertex_bone_weights.empty())
{
assert(xmodel.m_vertex_bone_weights.size() == xmodel.m_vertices.size());
auto* joints = reinterpret_cast<uint8_t*>(&bufferData[currentBufferOffset]);
auto* weights = reinterpret_cast<float*>(&bufferData[currentBufferOffset + sizeof(uint8_t) * 4u * xmodel.m_vertex_bone_weights.size()]);
for (const auto& commonVertexWeights : xmodel.m_vertex_bone_weights)
{
assert(commonVertexWeights.weights != nullptr);
assert(commonVertexWeights.weightCount <= 4u);
const auto commonVertexWeightCount = std::min(commonVertexWeights.weightCount, 4u);
for (auto i = 0u; i < commonVertexWeightCount; i++)
{
joints[i] = static_cast<unsigned char>(commonVertexWeights.weights[i].boneIndex);
weights[i] = commonVertexWeights.weights[i].weight;
}
for (auto i = commonVertexWeightCount; i < 4u; i++)
{
joints[i] = 0u;
weights[i] = 0.0f;
}
joints += 4u;
weights += 4u;
}
currentBufferOffset += (sizeof(uint8_t) + sizeof(float)) * 4u * xmodel.m_vertex_bone_weights.size();
auto* inverseBindMatrixData = reinterpret_cast<float*>(&bufferData[currentBufferOffset]);
for (const auto& bone : xmodel.m_bones)
{
Matrix32 inverseBindMatrix;
inverseBindMatrix.m_data[0][3] = -bone.globalOffset[0];
inverseBindMatrix.m_data[1][3] = -bone.globalOffset[2];
inverseBindMatrix.m_data[2][3] = bone.globalOffset[1];
// In-memory = row major
// gltf = column major
inverseBindMatrixData[0] = inverseBindMatrix.m_data[0][0];
inverseBindMatrixData[1] = inverseBindMatrix.m_data[1][0];
inverseBindMatrixData[2] = inverseBindMatrix.m_data[2][0];
inverseBindMatrixData[3] = inverseBindMatrix.m_data[3][0];
inverseBindMatrixData[4] = inverseBindMatrix.m_data[0][1];
inverseBindMatrixData[5] = inverseBindMatrix.m_data[1][1];
inverseBindMatrixData[6] = inverseBindMatrix.m_data[2][1];
inverseBindMatrixData[7] = inverseBindMatrix.m_data[3][1];
inverseBindMatrixData[8] = inverseBindMatrix.m_data[0][2];
inverseBindMatrixData[9] = inverseBindMatrix.m_data[1][2];
inverseBindMatrixData[10] = inverseBindMatrix.m_data[2][2];
inverseBindMatrixData[11] = inverseBindMatrix.m_data[3][2];
inverseBindMatrixData[12] = inverseBindMatrix.m_data[0][3];
inverseBindMatrixData[13] = inverseBindMatrix.m_data[1][3];
inverseBindMatrixData[14] = inverseBindMatrix.m_data[2][3];
inverseBindMatrixData[15] = inverseBindMatrix.m_data[3][3];
inverseBindMatrixData += 16u;
}
currentBufferOffset += sizeof(float) * 16u * xmodel.m_bones.size();
}
for (const auto& object : xmodel.m_objects)
{
for (const auto& face : object.m_faces)
{
auto* faceIndices = reinterpret_cast<unsigned short*>(&bufferData[currentBufferOffset]);
faceIndices[0] = static_cast<unsigned short>(face.vertexIndex[2]);
faceIndices[1] = static_cast<unsigned short>(face.vertexIndex[1]);
faceIndices[2] = static_cast<unsigned short>(face.vertexIndex[0]);
currentBufferOffset += sizeof(unsigned short) * 3u;
}
}
assert(expectedBufferSize == currentBufferOffset);
}
static size_t GetExpectedBufferSize(const XModelCommon& xmodel)
{
auto result = 0u;
result += xmodel.m_vertices.size() * sizeof(GltfVertex);
if (!xmodel.m_vertex_bone_weights.empty())
{
// Joints and weights
result += xmodel.m_vertices.size() * 4u * (sizeof(uint8_t) + sizeof(float));
// Inverse bind matrices
result += xmodel.m_bones.size() * 16u * sizeof(float);
}
for (const auto& object : xmodel.m_objects)
{
result += object.m_faces.size() * sizeof(unsigned short) * 3u;
}
return result;
}
void CreateBuffer(JsonRoot& gltf, const XModelCommon& xmodel, const std::vector<uint8_t>& bufferData) const
{
if (!gltf.buffers.has_value())
gltf.buffers.emplace();
JsonBuffer jsonBuffer;
jsonBuffer.byteLength = bufferData.size();
if (!bufferData.empty())
jsonBuffer.uri = m_output->CreateBufferUri(bufferData.data(), bufferData.size());
gltf.buffers->emplace_back(std::move(jsonBuffer));
}
unsigned m_mesh_node = 0u;
unsigned m_root_node = 0u;
unsigned m_first_bone_node = 0u;
unsigned m_position_accessor = 0u;
unsigned m_normal_accessor = 0u;
unsigned m_uv_accessor = 0u;
unsigned m_joints_accessor = 0u;
unsigned m_weights_accessor = 0u;
unsigned m_inverse_bind_matrices_accessor = 0u;
unsigned m_vertex_buffer_view = 0u;
unsigned m_joints_buffer_view = 0u;
unsigned m_weights_buffer_view = 0u;
unsigned m_inverse_bind_matrices_buffer_view = 0u;
unsigned m_first_index_buffer_view = 0u;
unsigned m_first_index_accessor = 0u;
const Output* m_output;
std::string m_game_name;
std::string m_zone_name;
};
} // namespace
std::unique_ptr<Writer> Writer::CreateWriter(const Output* output, std::string gameName, std::string zoneName)
{
return std::make_unique<GltfWriterImpl>(output, std::move(gameName), std::move(zoneName));
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "GltfOutput.h"
#include "XModel/Gltf/JsonGltf.h"
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace gltf
{
class Writer : public XModelWriter
{
public:
Writer() = default;
~Writer() override = default;
Writer(const Writer& other) = default;
Writer(Writer&& other) noexcept = default;
Writer& operator=(const Writer& other) = default;
Writer& operator=(Writer&& other) noexcept = default;
static std::unique_ptr<Writer> CreateWriter(const Output* output, std::string gameName, std::string zoneName);
};
} // namespace gltf

View File

@ -0,0 +1,227 @@
#include "ObjWriter.h"
#include "Utils/DistinctMapper.h"
#include "XModel/Obj/ObjCommon.h"
#include <format>
namespace
{
struct ObjObjectData
{
DistinctMapper<ObjVertex> m_vertices;
DistinctMapper<ObjNormal> m_normals;
DistinctMapper<ObjUv> m_uvs;
};
struct ObjObjectDataOffsets
{
size_t vertexOffset;
size_t normalOffset;
size_t uvOffset;
};
class ObjWriter final : public XModelWriter
{
public:
ObjWriter(std::ostream& stream, std::string gameName, std::string zoneName, std::string mtlName)
: m_stream(stream),
m_mtl_name(std::move(mtlName)),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
m_stream << "# OpenAssetTools OBJ File ( " << m_game_name << ")\n";
m_stream << "# Game Origin: " << m_game_name << "\n";
m_stream << "# Zone Origin: " << m_zone_name << "\n";
if (!m_mtl_name.empty())
m_stream << "mtllib " << m_mtl_name << "\n";
std::vector<ObjObjectDataOffsets> inputOffsetsByObject;
std::vector<ObjObjectDataOffsets> distinctOffsetsByObject;
GetObjObjectDataOffsets(xmodel, inputOffsetsByObject, distinctOffsetsByObject);
auto objectIndex = 0;
for (const auto& object : xmodel.m_objects)
{
const auto& objectData = m_object_data[objectIndex];
m_stream << "o " << object.name << "\n";
for (const auto& v : objectData.m_vertices.GetDistinctValues())
m_stream << "v " << v.coordinates[0] << " " << v.coordinates[1] << " " << v.coordinates[2] << "\n";
for (const auto& uv : objectData.m_uvs.GetDistinctValues())
m_stream << "vt " << uv.uv[0] << " " << uv.uv[1] << "\n";
for (const auto& n : objectData.m_normals.GetDistinctValues())
m_stream << "vn " << n.normal[0] << " " << n.normal[1] << " " << n.normal[2] << "\n";
if (object.materialIndex >= 0 && static_cast<unsigned>(object.materialIndex) < xmodel.m_materials.size())
m_stream << "usemtl " << xmodel.m_materials[object.materialIndex].name << "\n";
auto faceIndex = 0u;
for (const auto& f : object.m_faces)
{
const auto faceVertexOffset = 3u * faceIndex;
const size_t v[3]{
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
objectData.m_vertices.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].vertexOffset + 1,
};
const size_t n[3]{
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
objectData.m_normals.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].normalOffset + 1,
};
const size_t uv[3]{
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 0) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 1) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
objectData.m_uvs.GetDistinctPositionByInputPosition(faceVertexOffset + 2) + distinctOffsetsByObject[objectIndex].uvOffset + 1,
};
m_stream << std::format("f {}/{}/{} {}/{}/{} {}/{}/{}\n", v[0], uv[0], n[0], v[1], uv[1], n[1], v[2], uv[2], n[2]);
faceIndex++;
}
objectIndex++;
}
}
void GetObjObjectDataOffsets(const XModelCommon& xmodel,
std::vector<ObjObjectDataOffsets>& inputOffsets,
std::vector<ObjObjectDataOffsets>& distinctOffsets)
{
ObjObjectDataOffsets currentInputOffsets{};
ObjObjectDataOffsets currentDistinctOffsets{};
AddObjFaces(xmodel);
for (const auto& objectData : m_object_data)
{
inputOffsets.push_back(currentInputOffsets);
distinctOffsets.push_back(currentDistinctOffsets);
currentInputOffsets.vertexOffset += objectData.m_vertices.GetInputValueCount();
currentInputOffsets.normalOffset += objectData.m_normals.GetInputValueCount();
currentInputOffsets.uvOffset += objectData.m_uvs.GetInputValueCount();
currentDistinctOffsets.vertexOffset += objectData.m_vertices.GetDistinctValueCount();
currentDistinctOffsets.normalOffset += objectData.m_normals.GetDistinctValueCount();
currentDistinctOffsets.uvOffset += objectData.m_uvs.GetDistinctValueCount();
}
}
void AddObjFaces(const XModelCommon& common)
{
m_object_data.resize(common.m_objects.size());
auto objectIndex = 0u;
for (const auto& commonObject : common.m_objects)
{
auto& objectData = m_object_data[objectIndex];
for (const auto& commonFace : commonObject.m_faces)
{
ObjFace face{};
face.vertexIndex[0] = commonFace.vertexIndex[2];
face.vertexIndex[1] = commonFace.vertexIndex[1];
face.vertexIndex[2] = commonFace.vertexIndex[0];
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];
// Reverse order for OBJ
AddObjVertex(objectData, common, commonFace.vertexIndex[2]);
AddObjVertex(objectData, common, commonFace.vertexIndex[1]);
AddObjVertex(objectData, common, commonFace.vertexIndex[0]);
}
objectIndex++;
}
}
static void AddObjVertex(ObjObjectData& objectData, const XModelCommon& common, const int commonVertexIndex)
{
const auto& commonVertex = common.m_vertices[commonVertexIndex];
ObjVertex objVertex{};
objVertex.coordinates[0] = commonVertex.coordinates[0];
objVertex.coordinates[1] = commonVertex.coordinates[2];
objVertex.coordinates[2] = -commonVertex.coordinates[1];
objectData.m_vertices.Add(objVertex);
ObjNormal objNormal{};
objNormal.normal[0] = commonVertex.normal[0];
objNormal.normal[1] = commonVertex.normal[2];
objNormal.normal[2] = -commonVertex.normal[1];
objectData.m_normals.Add(objNormal);
ObjUv objUv{};
objUv.uv[0] = commonVertex.uv[0];
objUv.uv[1] = 1.0f - commonVertex.uv[1];
objectData.m_uvs.Add(objUv);
}
std::ostream& m_stream;
std::string m_mtl_name;
std::string m_game_name;
std::string m_zone_name;
std::vector<ObjObjectData> m_object_data;
};
class MtlWriter final : public XModelWriter
{
public:
MtlWriter(std::ostream& stream, std::string gameName, std::string zoneName)
: m_stream(stream),
m_game_name(std::move(gameName)),
m_zone_name(std::move(zoneName))
{
}
void Write(const XModelCommon& xmodel) override
{
m_stream << "# OpenAssetTools MAT File ( " << m_game_name << ")\n";
m_stream << "# Game Origin: " << m_game_name << "\n";
m_stream << "# Zone Origin: " << m_zone_name << "\n";
m_stream << "# Material count: " << xmodel.m_materials.size() << "\n";
for (const auto& material : xmodel.m_materials)
{
m_stream << "\n";
m_stream << "newmtl " << material.name << "\n";
if (!material.colorMapName.empty())
m_stream << "map_Kd ../images/" << material.colorMapName << ".dds\n";
if (!material.normalMapName.empty())
m_stream << "map_bump ../images/" << material.normalMapName << ".dds\n";
if (!material.specularMapName.empty())
m_stream << "map_Ks ../images/" << material.specularMapName << ".dds\n";
}
}
private:
std::ostream& m_stream;
std::string m_game_name;
std::string m_zone_name;
};
} // namespace
namespace obj
{
std::unique_ptr<XModelWriter> CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName)
{
return std::make_unique<ObjWriter>(stream, std::move(mtlName), std::move(gameName), std::move(zoneName));
}
std::unique_ptr<XModelWriter> CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName)
{
return std::make_unique<MtlWriter>(stream, std::move(gameName), std::move(zoneName));
}
} // namespace obj

View File

@ -0,0 +1,12 @@
#pragma once
#include "XModel/XModelWriter.h"
#include <memory>
#include <ostream>
namespace obj
{
std::unique_ptr<XModelWriter> CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName);
std::unique_ptr<XModelWriter> CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName);
} // namespace obj

View File

@ -0,0 +1,18 @@
#pragma once
#include "XModel/XModelCommon.h"
#include <vector>
class XModelWriter
{
public:
XModelWriter() = default;
virtual ~XModelWriter() = default;
XModelWriter(const XModelWriter& other) = default;
XModelWriter(XModelWriter&& other) noexcept = default;
XModelWriter& operator=(const XModelWriter& other) = default;
XModelWriter& operator=(XModelWriter&& other) noexcept = default;
virtual void Write(const XModelCommon& xmodel) = 0;
};

View File

@ -5,6 +5,7 @@
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/Arguments/UsageInformation.h" #include "Utils/Arguments/UsageInformation.h"
#include "Utils/FileUtils.h" #include "Utils/FileUtils.h"
#include "Utils/StringUtils.h"
#include <iostream> #include <iostream>
#include <regex> #include <regex>
@ -79,7 +80,7 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT =
const CommandLineOption* const OPTION_MODEL_FORMAT = const CommandLineOption* const OPTION_MODEL_FORMAT =
CommandLineOption::Builder::Create() CommandLineOption::Builder::Create()
.WithLongName("model-format") .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") .WithParameter("modelFormatValue")
.Build(); .Build();
@ -179,8 +180,7 @@ void UnlinkerArgs::SetVerbose(const bool isVerbose)
bool UnlinkerArgs::SetImageDumpingMode() bool UnlinkerArgs::SetImageDumpingMode()
{ {
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT); auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT);
for (auto& c : specifiedValue) utils::MakeStringLowerCase(specifiedValue);
c = static_cast<char>(tolower(c));
if (specifiedValue == "dds") if (specifiedValue == "dds")
{ {
@ -202,8 +202,7 @@ bool UnlinkerArgs::SetImageDumpingMode()
bool UnlinkerArgs::SetModelDumpingMode() bool UnlinkerArgs::SetModelDumpingMode()
{ {
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
for (auto& c : specifiedValue) utils::MakeStringLowerCase(specifiedValue);
c = static_cast<char>(tolower(c));
if (specifiedValue == "xmodel_export") if (specifiedValue == "xmodel_export")
{ {
@ -217,6 +216,18 @@ bool UnlinkerArgs::SetModelDumpingMode()
return true; 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); 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()); printf("Illegal value: \"%s\" is not a valid model output format. Use -? to see usage information.\n", originalValue.c_str());
return false; return false;
@ -238,8 +249,7 @@ void UnlinkerArgs::ParseCommaSeparatedAssetTypeString(const std::string& input)
size_t endPos; size_t endPos;
std::string lowerInput(input); std::string lowerInput(input);
for (auto& c : lowerInput) utils::MakeStringLowerCase(lowerInput);
c = static_cast<char>(tolower(c));
while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos) while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos)
{ {

View File

@ -51,6 +51,135 @@ public:
return Matrix<T>(m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, T(1.0)); return Matrix<T>(m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, T(1.0));
} }
static T dot(const Quaternion& q1, const Quaternion& q2)
{
return static_cast<T>((q1.m_x * q2.m_x) + (q1.m_y * q2.m_y) + (q1.m_z * q2.m_z) + (q1.m_w * q2.m_w));
}
static Quaternion& conj(Quaternion& result)
{
result.m_x = -result.m_x;
result.m_y = -result.m_y;
result.m_z = -result.m_z;
return result;
}
static Quaternion& invert(Quaternion& result)
{
// from game programming gems p198
// do result = conj( q ) / norm( q )
Quaternion::conj(result);
// return if norm() is near 0 (divide by 0 would result in NaN)
T l = result.lengthSquared();
if (l < static_cast<T>(0.0001))
{
return result;
}
T l_inv = static_cast<T>(1.0) / l;
result.m_x *= l_inv;
result.m_y *= l_inv;
result.m_z *= l_inv;
result.m_w *= l_inv;
return result;
}
T lengthSquared() const
{
return Quaternion::dot(*this, *this);
}
T length() const
{
return sqrt(lengthSquared());
}
void Normalize()
{
const auto l = length();
// return if no magnitude (already as normalized as possible)
if (l < static_cast<T>(0.0001))
return;
T inverseLength = static_cast<T>(1.0) / l;
m_x *= inverseLength;
m_y *= inverseLength;
m_z *= inverseLength;
m_w *= inverseLength;
}
friend Quaternion operator+(const Quaternion& lhs, const Quaternion& rhs)
{
return Quaternion(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y, lhs.m_z + rhs.m_z, lhs.m_w + rhs.m_w);
}
friend Quaternion operator-(const Quaternion& lhs, const Quaternion& rhs)
{
return Quaternion(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y, lhs.m_z - rhs.m_z, lhs.m_w - rhs.m_w);
}
friend Quaternion& operator+=(Quaternion& lhs, const Quaternion& rhs)
{
lhs.m_x += rhs.m_x;
lhs.m_y += rhs.m_y;
lhs.m_z += rhs.m_z;
lhs.m_w += rhs.m_w;
return lhs;
}
friend Quaternion& operator-=(Quaternion& lhs, const Quaternion& rhs)
{
lhs.m_x -= rhs.m_x;
lhs.m_y -= rhs.m_y;
lhs.m_z -= rhs.m_z;
lhs.m_w -= rhs.m_w;
return lhs;
}
friend Quaternion operator*(const Quaternion& lhs, const Quaternion& rhs)
{
const T x2 = lhs.m_w * rhs.m_x + lhs.m_x * rhs.m_w + lhs.m_y * rhs.m_z - lhs.m_z * rhs.m_y;
const T y2 = lhs.m_w * rhs.m_y + lhs.m_y * rhs.m_w + lhs.m_z * rhs.m_x - lhs.m_x * rhs.m_z;
const T z2 = lhs.m_w * rhs.m_z + lhs.m_z * rhs.m_w + lhs.m_x * rhs.m_y - lhs.m_y * rhs.m_x;
const T w2 = lhs.m_w * rhs.m_w - lhs.m_x * rhs.m_x - lhs.m_y * rhs.m_y - lhs.m_z * rhs.m_z;
return Quaternion(x2, y2, z2, w2);
}
friend Quaternion operator/(const Quaternion& lhs, const Quaternion& rhs)
{
Quaternion rhsInv = rhs;
Quaternion::invert(rhsInv);
return lhs * rhsInv;
}
friend Quaternion& operator*=(Quaternion& lhs, const Quaternion& rhs)
{
const T x2 = lhs.m_w * rhs.m_x + lhs.m_x * rhs.m_w + lhs.m_y * rhs.m_z - lhs.m_z * rhs.m_y;
const T y2 = lhs.m_w * rhs.m_y + lhs.m_y * rhs.m_w + lhs.m_z * rhs.m_x - lhs.m_x * rhs.m_z;
const T z2 = lhs.m_w * rhs.m_z + lhs.m_z * rhs.m_w + lhs.m_x * rhs.m_y - lhs.m_y * rhs.m_x;
const T w2 = lhs.m_w * rhs.m_w - lhs.m_x * rhs.m_x - lhs.m_y * rhs.m_y - lhs.m_z * rhs.m_z;
lhs.m_x = x2;
lhs.m_y = y2;
lhs.m_z = z2;
lhs.m_w = w2;
return lhs;
}
friend Quaternion& operator/=(Quaternion& lhs, const Quaternion& rhs)
{
Quaternion rhsInv = rhs;
Quaternion::invert(rhsInv);
lhs *= rhsInv;
return lhs;
}
}; };
typedef Quaternion<float> Quaternion32; typedef Quaternion<float> Quaternion32;

View File

@ -24,6 +24,12 @@ public:
{ {
} }
~Vector2() = default;
Vector2(const Vector2& other) = default;
Vector2(Vector2&& other) noexcept = default;
Vector2& operator=(const Vector2& other) = default;
Vector2& operator=(Vector2&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index) _NODISCARD T& operator()(const size_t index)
{ {
assert(index < 2); assert(index < 2);
@ -55,6 +61,32 @@ public:
{ {
return m_value[1]; return m_value[1];
} }
friend Vector2 operator+(const Vector2& lhs, const Vector2& rhs)
{
return Vector2(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1]);
}
friend Vector2 operator-(const Vector2& lhs, const Vector2& rhs)
{
return Vector2(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1]);
}
friend Vector2& operator+=(Vector2& lhs, const Vector2& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
return lhs;
}
friend Vector2& operator-=(Vector2& lhs, const Vector2& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
return lhs;
}
}; };
typedef Vector2<float> Vector2f; typedef Vector2<float> Vector2f;
@ -80,6 +112,12 @@ public:
{ {
} }
~Vector3() = default;
Vector3(const Vector3& other) = default;
Vector3(Vector3&& other) noexcept = default;
Vector3& operator=(const Vector3& other) = default;
Vector3& operator=(Vector3&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index) _NODISCARD T& operator()(const size_t index)
{ {
assert(index < 3); assert(index < 3);
@ -151,6 +189,34 @@ public:
{ {
return m_value[2]; return m_value[2];
} }
friend Vector3 operator+(const Vector3& lhs, const Vector3& rhs)
{
return Vector3(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1], lhs.m_value[2] + rhs.m_value[2]);
}
friend Vector3 operator-(const Vector3& lhs, const Vector3& rhs)
{
return Vector3(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1], lhs.m_value[2] - rhs.m_value[2]);
}
friend Vector3& operator+=(Vector3& lhs, const Vector3& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
lhs.m_value[2] += rhs.m_value[2];
return lhs;
}
friend Vector3& operator-=(Vector3& lhs, const Vector3& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
lhs.m_value[2] -= rhs.m_value[2];
return lhs;
}
}; };
typedef Vector3<float> Vector3f; typedef Vector3<float> Vector3f;
@ -176,6 +242,12 @@ public:
{ {
} }
~Vector4() = default;
Vector4(const Vector4& other) = default;
Vector4(Vector4&& other) noexcept = default;
Vector4& operator=(const Vector4& other) = default;
Vector4& operator=(Vector4&& other) noexcept = default;
_NODISCARD T& operator()(const size_t index) _NODISCARD T& operator()(const size_t index)
{ {
assert(index < 4); assert(index < 4);
@ -267,6 +339,36 @@ public:
{ {
return m_value[3]; return m_value[3];
} }
friend Vector4 operator+(const Vector4& lhs, const Vector4& rhs)
{
return Vector4(lhs.m_value[0] + rhs.m_value[0], lhs.m_value[1] + rhs.m_value[1], lhs.m_value[2] + rhs.m_value[2], lhs.m_value[3] + rhs.m_value[3]);
}
friend Vector4 operator-(const Vector4& lhs, const Vector4& rhs)
{
return Vector4(lhs.m_value[0] - rhs.m_value[0], lhs.m_value[1] - rhs.m_value[1], lhs.m_value[2] - rhs.m_value[2], lhs.m_value[3] - rhs.m_value[3]);
}
friend Vector4& operator+=(Vector4& lhs, const Vector4& rhs)
{
lhs.m_value[0] += rhs.m_value[0];
lhs.m_value[1] += rhs.m_value[1];
lhs.m_value[2] += rhs.m_value[2];
lhs.m_value[3] += rhs.m_value[3];
return lhs;
}
friend Vector4& operator-=(Vector4& lhs, const Vector4& rhs)
{
lhs.m_value[0] -= rhs.m_value[0];
lhs.m_value[1] -= rhs.m_value[1];
lhs.m_value[2] -= rhs.m_value[2];
lhs.m_value[3] -= rhs.m_value[3];
return lhs;
}
}; };
typedef Vector4<float> Vector4f; typedef Vector4<float> Vector4f;