diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 11e55fc0..581745fb 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -22,6 +22,8 @@ namespace T6 typedef tdef_align(128) float float_align128; + typedef uint16_t ScriptString; + struct dvar_t; struct MenuCell; struct cplane_s; @@ -591,14 +593,22 @@ namespace T6 int partBits[5]; }; + enum XModelLodRampType : unsigned char + { + XMODEL_LOD_RAMP_RIGID = 0x0, + XMODEL_LOD_RAMP_SKINNED = 0x1, + + XMODEL_LOD_RAMP_COUNT + }; + struct XModel { const char* name; unsigned char numBones; unsigned char numRootBones; unsigned char numsurfs; - char lodRampType; - uint16_t* boneNames; + XModelLodRampType lodRampType; + ScriptString* boneNames; unsigned char* parentList; uint16_t (*quats)[4]; float (*trans)[4]; @@ -618,7 +628,7 @@ namespace T6 uint16_t collLod; float* himipInvSqRadii; int memUsage; - int flags; + unsigned int flags; bool bad; PhysPreset* physPreset; unsigned char numCollmaps; diff --git a/src/ObjCommon/Game/T6/Json/JsonXModel.h b/src/ObjCommon/Game/T6/Json/JsonXModel.h new file mode 100644 index 00000000..28ac5563 --- /dev/null +++ b/src/ObjCommon/Game/T6/Json/JsonXModel.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Game/T6/T6.h" + +#include "Json/JsonCommon.h" +#include "Json/JsonExtension.h" +#include +#include +#include +#include +#include + +namespace T6 +{ + class JsonXModelLod + { + public: + std::string file; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file); + + class JsonXModel + { + public: + std::vector lods; + unsigned collLod; + std::optional physPreset; + std::optional physConstraints; + unsigned flags; + JsonVec3 lightingOriginOffset; + float lightingOriginRange; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physConstraints, flags, lightingOriginOffset, lightingOriginRange); +} // namespace T6 diff --git a/src/ObjCommon/XModel/Gltf/GltfConstants.h b/src/ObjCommon/XModel/Gltf/GltfConstants.h new file mode 100644 index 00000000..a83f4e2e --- /dev/null +++ b/src/ObjCommon/XModel/Gltf/GltfConstants.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Utils/FileUtils.h" + +#include + +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 diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h new file mode 100644 index 00000000..2f3a15d4 --- /dev/null +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -0,0 +1,343 @@ +#pragma once + +#include "Json/JsonExtension.h" +#include +#include +#include +#include +#include + +namespace gltf +{ + class JsonAsset + { + public: + std::string version; + std::optional generator; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAsset, version, generator); + + class JsonNode + { + public: + std::optional name; + std::optional> translation; + std::optional> rotation; + std::optional> scale; + std::optional> children; + std::optional skin; + std::optional mesh; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, children, skin, mesh); + + class JsonBuffer + { + public: + unsigned byteLength; + std::optional 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(JsonAccessorComponentType::SIGNED_BYTE) }, + {JsonAccessorComponentType::UNSIGNED_BYTE, static_cast(JsonAccessorComponentType::UNSIGNED_BYTE) }, + {JsonAccessorComponentType::SIGNED_SHORT, static_cast(JsonAccessorComponentType::SIGNED_SHORT) }, + {JsonAccessorComponentType::UNSIGNED_SHORT, static_cast(JsonAccessorComponentType::UNSIGNED_SHORT)}, + {JsonAccessorComponentType::UNSIGNED_INT, static_cast(JsonAccessorComponentType::UNSIGNED_INT) }, + {JsonAccessorComponentType::FLOAT, static_cast(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 bufferView; + std::optional byteOffset; + JsonAccessorComponentType componentType; + // std::optional normalized + unsigned count; + JsonAccessorType type; + std::optional> max; + std::optional> min; + // std::optional sparse; + // std::optional 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(JsonBufferViewTarget::ARRAY_BUFFER) }, + {JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER, static_cast(JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER)}, + }); + + class JsonBufferView + { + public: + unsigned buffer; + unsigned byteLength; + std::optional byteOffset; + std::optional byteStride; + std::optional 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 interpolation; + unsigned output; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationSampler, input, interpolation, output); + + class JsonAnimation + { + public: + std::vector channels; + std::vector samplers; + std::optional 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 baseColorTexture; + std::optional metallicFactor; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonPbrMetallicRoughness, baseColorTexture, metallicFactor); + + class JsonNormalTextureInfo + { + public: + unsigned index; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNormalTextureInfo, index); + + class JsonMaterial + { + public: + std::optional name; + std::optional pbrMetallicRoughness; + std::optional normalTexture; + std::optional 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(JsonMeshPrimitivesMode::POINTS) }, + {JsonMeshPrimitivesMode::LINES, static_cast(JsonMeshPrimitivesMode::LINES) }, + {JsonMeshPrimitivesMode::LINE_LOOP, static_cast(JsonMeshPrimitivesMode::LINE_LOOP) }, + {JsonMeshPrimitivesMode::LINE_STRIP, static_cast(JsonMeshPrimitivesMode::LINE_STRIP) }, + {JsonMeshPrimitivesMode::TRIANGLES, static_cast(JsonMeshPrimitivesMode::TRIANGLES) }, + {JsonMeshPrimitivesMode::TRIANGLES_STRIP, static_cast(JsonMeshPrimitivesMode::TRIANGLES_STRIP)}, + {JsonMeshPrimitivesMode::TRIANGLE_FAN, static_cast(JsonMeshPrimitivesMode::TRIANGLE_FAN) }, + }); + + // This should probably be a map, but the supported models do not have arbitrary primitives anyway + class JsonMeshPrimitivesAttributes + { + public: + std::optional POSITION; + std::optional NORMAL; + std::optional TEXCOORD_0; + std::optional JOINTS_0; + std::optional WEIGHTS_0; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitivesAttributes, POSITION, NORMAL, TEXCOORD_0, JOINTS_0, WEIGHTS_0); + + class JsonMeshPrimitives + { + public: + JsonMeshPrimitivesAttributes attributes; + std::optional indices; + std::optional material; + std::optional mode; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitives, attributes, indices, material, mode); + + class JsonMesh + { + public: + std::vector primitives; + std::optional> weights; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMesh, primitives, weights); + + class JsonSkin + { + public: + std::optional inverseBindMatrices; + std::optional skeleton; + std::vector joints; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSkin, inverseBindMatrices, skeleton, joints); + + class JsonScene + { + public: + std::vector nodes; + std::optional name; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonScene, nodes, name); + + class JsonTexture + { + public: + unsigned source; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTexture, source); + + class JsonImage + { + public: + std::optional uri; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonImage, uri); + + class JsonRoot + { + public: + std::optional> accessors; + std::optional> animations; + JsonAsset asset; + std::optional> buffers; + std::optional> bufferViews; + std::optional> images; + std::optional> materials; + std::optional> meshes; + std::optional> nodes; + std::optional> skins; + std::optional scene; + std::optional> scenes; + std::optional> textures; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION( + JsonRoot, accessors, animations, asset, buffers, bufferViews, images, materials, meshes, nodes, skins, scene, scenes, textures); +} // namespace gltf diff --git a/src/ObjCommon/Model/Obj/ObjCommon.cpp b/src/ObjCommon/XModel/Obj/ObjCommon.cpp similarity index 100% rename from src/ObjCommon/Model/Obj/ObjCommon.cpp rename to src/ObjCommon/XModel/Obj/ObjCommon.cpp diff --git a/src/ObjCommon/Model/Obj/ObjCommon.h b/src/ObjCommon/XModel/Obj/ObjCommon.h similarity index 100% rename from src/ObjCommon/Model/Obj/ObjCommon.h rename to src/ObjCommon/XModel/Obj/ObjCommon.h diff --git a/src/ObjCommon/Model/XModel/XModelCommon.cpp b/src/ObjCommon/XModel/XModelCommon.cpp similarity index 100% rename from src/ObjCommon/Model/XModel/XModelCommon.cpp rename to src/ObjCommon/XModel/XModelCommon.cpp diff --git a/src/ObjCommon/Model/XModel/XModelCommon.h b/src/ObjCommon/XModel/XModelCommon.h similarity index 77% rename from src/ObjCommon/Model/XModel/XModelCommon.h rename to src/ObjCommon/XModel/XModelCommon.h index 41f50f1b..82f6bbef 100644 --- a/src/ObjCommon/Model/XModel/XModelCommon.h +++ b/src/ObjCommon/XModel/XModelCommon.h @@ -3,13 +3,8 @@ #include "Math/Quaternion.h" #include "Utils/DistinctMapper.h" -#include #include - -struct XModelObject -{ - std::string name; -}; +#include struct XModelBone { @@ -30,8 +25,7 @@ struct XModelBoneWeight struct XModelVertexBoneWeightCollection { - std::unique_ptr weights; - size_t totalWeightCount; + std::vector weights; }; struct XModelVertexBoneWeights @@ -51,8 +45,6 @@ struct XModelVertex struct XModelFace { int vertexIndex[3]; - int objectIndex; - int materialIndex; }; struct XModelMaterial @@ -89,10 +81,30 @@ struct XModelMaterial float blinn[2]; float phong; std::string colorMapName; + std::string normalMapName; + std::string specularMapName; void ApplyDefaults(); }; +struct XModelObject +{ + std::string name; + int materialIndex; + std::vector m_faces; +}; + +struct XModelCommon +{ + std::string m_name; + std::vector m_objects; + std::vector m_bones; + std::vector m_materials; + std::vector m_vertices; + std::vector m_vertex_bone_weights; + XModelVertexBoneWeightCollection m_bone_weight_data; +}; + struct VertexMergerPos { float x; diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp new file mode 100644 index 00000000..2ee2dc36 --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp @@ -0,0 +1,44 @@ +#include "AssetLoaderXModel.h" + +#include "Game/T6/T6.h" +#include "Game/T6/XModel/JsonXModelLoader.h" +#include "Pool/GlobalAssetPool.h" + +#include +#include +#include + +using namespace T6; + +void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* xmodel = memory->Create(); + 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->name = memory->Dup(assetName.c_str()); + + std::vector dependencies; + if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies)) + manager->AddAsset(assetName, xmodel, std::move(dependencies)); + else + std::cerr << "Failed to load xmodel \"" << assetName << "\"\n"; + + return true; +} diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.h b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.h new file mode 100644 index 00000000..0e0115da --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.h @@ -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 + { + 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 diff --git a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp b/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp new file mode 100644 index 00000000..692a340f --- /dev/null +++ b/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp @@ -0,0 +1,117 @@ +#include "JsonXModelLoader.h" + +#include "Game/T6/CommonT6.h" +#include "Game/T6/Json/JsonXModel.h" + +#include +#include +#include +#include + +using namespace nlohmann; +using namespace T6; + +namespace +{ + class JsonLoader + { + public: + JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set& 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(); + 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(jXModel.collLod); + + if (jXModel.physPreset) + { + auto* physPreset = m_manager.LoadDependency(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(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& m_dependencies; + }; +} // namespace + +namespace T6 +{ + bool LoadXModelAsJson( + std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies) + { + std::set dependenciesSet; + const JsonLoader loader(stream, *memory, *manager, dependenciesSet); + + dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend()); + + return loader.Load(xmodel); + } +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h b/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h new file mode 100644 index 00000000..d7747287 --- /dev/null +++ b/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h @@ -0,0 +1,13 @@ +#pragma once + +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/T6/T6.h" +#include "Utils/MemoryManager.h" + +#include + +namespace T6 +{ + bool LoadXModelAsJson( + std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies); +} // namespace T6 diff --git a/src/ObjWriting.lua b/src/ObjWriting.lua index 8505cd8b..fa796ae8 100644 --- a/src/ObjWriting.lua +++ b/src/ObjWriting.lua @@ -19,6 +19,7 @@ function ObjWriting:link(links) links:linkto(ZoneCommon) links:linkto(minilzo) links:linkto(minizip) + links:linkto(libtomcrypt) end function ObjWriting:use() @@ -55,5 +56,6 @@ function ObjWriting:project() minilzo:include(includes) minizip:include(includes) json:include(includes) + libtomcrypt:include(includes) end diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp index 45d031fd..862b0470 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.cpp @@ -2,592 +2,525 @@ #include "Game/IW3/CommonIW3.h" #include "Math/Quaternion.h" -#include "Model/XModel/XModelExportWriter.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.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 -#include +#include using namespace IW3; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* GetMaterialColorMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_COLOR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'c' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialNormalMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialSpecularMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model) + { + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; + bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; + bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; + bone.globalRotation = Quaternion32( + model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + void AddXModelMaterials(XModelCommon& out, DistinctMapper& materialMapper, const XModel* model) + { + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = AssetName(material->info.name); + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = AssetName(colorMap->name); + + 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 AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper& materialMapper) + { + const auto surfCount = model->lodInfo[lod].numsurfs; + const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + XModelObject object; + object.name = std::format("surf{}", surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + out.m_objects.emplace_back(std::move(object)); + } + } + + void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + vec4_t color; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz[0]; + vertex.coordinates[1] = v.xyz[1]; + vertex.coordinates[2] = v.xyz[2]; + vertex.normal[0] = normalVec[0]; + vertex.normal[1] = normalVec[1]; + vertex.normal[2] = normalVec[2]; + vertex.color[0] = color[0]; + vertex.color[1] = color[1]; + vertex.color[2] = color[2]; + vertex.color[3] = color[3]; + vertex.uv[0] = uv[0]; + vertex.uv[1] = uv[1]; + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + auto totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertList) + { + totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + totalWeightCount += surface.vertInfo.vertCount[0] * 1; + totalWeightCount += surface.vertInfo.vertCount[1] * 2; + totalWeightCount += surface.vertInfo.vertCount[2] * 3; + totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights.resize(totalWeightCount); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + auto& weightCollection = out.m_bone_weight_data; + + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; + + vertsBlendOffset += 1; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + + vertsBlendOffset += 3; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + + vertsBlendOffset += 5; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; + + vertsBlendOffset += 7; + + 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]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + out.m_vertex_bone_weights.emplace_back(nullptr, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, 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]; + auto& object = out.m_objects[surfIndex]; + object.m_faces.reserve(surface.triCount); + + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) + { + DistinctMapper 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* 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 materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj")); + + if (!assetFile) + return; + + const auto writer = + obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + writer->Write(common); + } + + template + void DumpGltfLod( + const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + + writer->Write(common); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + XModelCommon common; + PopulateXModelWriter(common, context, currentLod, asset->Asset()); + + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(common, context, asset, currentLod); + if (currentLod == 0u) + DumpObjMtl(common, context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(common, context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(common, context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(common, context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; } -GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_COLOR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'c' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_NORMAL_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_SPECULAR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - if (!model->materialHandles) - return; - - for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) - { - Material* material = model->materialHandles[surfIndex]; - if (!materialMapper.Add(material)) - continue; - - MtlMaterial mtl; - mtl.materialName = std::string(material->info.name); - - GfxImage* colorMap = GetMaterialColorMap(material); - GfxImage* normalMap = GetMaterialNormalMap(material); - GfxImage* specularMap = GetMaterialSpecularMap(material); - - if (colorMap != nullptr) - mtl.colorMapName = colorMap->name; - if (normalMap != nullptr) - mtl.normalMapName = normalMap->name; - if (specularMap != nullptr) - mtl.specularMapName = specularMap->name; - - writer.AddMaterial(std::move(mtl)); - } -} - -void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - ObjObject object; - object.name = "surf" + std::to_string(surfIndex); - object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - - ObjVertex objVertex{}; - ObjNormal objNormal{}; - ObjUv objUv{}; - objVertex.coordinates[0] = v.xyz[0]; - objVertex.coordinates[1] = v.xyz[2]; - objVertex.coordinates[2] = -v.xyz[1]; - objNormal.normal[0] = normalVec[0]; - objNormal.normal[1] = normalVec[2]; - objNormal.normal[2] = -normalVec[1]; - objUv.uv[0] = uv[0]; - objUv.uv[1] = 1.0f - uv[1]; - - writer.AddVertex(static_cast(surfIndex), objVertex); - writer.AddNormal(static_cast(surfIndex), objNormal); - writer.AddUv(static_cast(surfIndex), objUv); - } - } -} - -void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - ObjFace face{}; - face.vertexIndex[0] = tri[2] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[0] + surface.baseVertIndex; - face.normalIndex[0] = face.vertexIndex[0]; - face.normalIndex[1] = face.vertexIndex[1]; - face.normalIndex[2] = face.vertexIndex[2]; - face.uvIndex[0] = face.vertexIndex[0]; - face.uvIndex[1] = face.vertexIndex[1]; - face.uvIndex[2] = face.vertexIndex[2]; - writer.AddFace(static_cast(surfIndex), face); - } - } -} - -void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); - - if (!matFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - writer.WriteMtl(*matFile); -} - -void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".OBJ"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - AddObjObjects(writer, materialMapper, model, lod); - AddObjVertices(writer, model, lod); - AddObjFaces(writer, model, lod); - - writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); -} - -void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpObjLod(context, asset, currentLod); - } -} - -void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) -{ - for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) - { - XModelBone bone; - if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) - bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; - else - bone.name = "INVALID_BONE_NAME"; - - if (boneNum < model->numRootBones) - bone.parentIndex = -1; - else - bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; - bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; - bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = Quaternion32(0, 0, 0, 1); - } - else - { - bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; - bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; - bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; - bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); - } - - writer.AddBone(std::move(bone)); - } -} - -void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) - { - Material* material = model->materialHandles[surfaceMaterialNum]; - if (materialMapper.Add(material)) - { - XModelMaterial xMaterial; - xMaterial.ApplyDefaults(); - - xMaterial.name = material->info.name; - const auto* colorMap = GetMaterialColorMap(material); - if (colorMap) - xMaterial.colorMapName = std::string(colorMap->name); - - writer.AddMaterial(std::move(xMaterial)); - } - } -} - -void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - XModelObject object; - object.name = "surf" + std::to_string(surfIndex); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - vec4_t color; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - Common::Vec4UnpackGfxColor(v.color, &color); - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - vertex.normal[0] = normalVec[0]; - vertex.normal[1] = normalVec[1]; - vertex.normal[2] = normalVec[2]; - vertex.color[0] = color[0]; - vertex.color[1] = color[1]; - vertex.color[2] = color[2]; - vertex.color[3] = color[3]; - vertex.uv[0] = uv[0]; - vertex.uv[1] = uv[1]; - - writer.AddVertex(vertex); - } - } -} - -void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - weightCollection.totalWeightCount = 0u; - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - if (surface.vertList) - { - weightCollection.totalWeightCount += surface.vertListCount; - } - - if (surface.vertInfo.vertsBlend) - { - weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; - } - } - - weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); -} - -void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, - const XModel* model, - const unsigned lod, - XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - size_t weightOffset = 0u; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - auto handledVertices = 0u; - - if (surface.vertList) - { - for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) - { - const auto& vertList = surface.vertList[vertListIndex]; - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; - - for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - handledVertices += vertList.vertCount; - } - } - - auto vertsBlendOffset = 0u; - if (surface.vertInfo.vertsBlend) - { - // 1 bone weight - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; - - vertsBlendOffset += 1; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - - // 2 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneWeight0 = 1.0f - boneWeight1; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - - vertsBlendOffset += 3; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); - } - - // 3 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - - vertsBlendOffset += 5; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); - } - - // 4 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); - const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; - - vertsBlendOffset += 7; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); - } - - handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; - } - - for (; handledVertices < surface.vertCount; handledVertices++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); - } - } -} - -void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - XModelFace face{}; - face.vertexIndex[0] = tri[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[2] + surface.baseVertIndex; - face.objectIndex = static_cast(surfIndex); - face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - writer.AddFace(face); - } - } -} - -void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - XModelVertexBoneWeightCollection boneWeightCollection; - AllocateXModelBoneWeights(model, lod, boneWeightCollection); - - AddXModelBones(context, *writer, model); - AddXModelMaterials(*writer, materialMapper, model); - AddXModelObjects(*writer, model, lod); - AddXModelVertices(*writer, model, lod); - AddXModelVertexBoneWeights(*writer, model, lod, boneWeightCollection); - AddXModelFaces(*writer, materialMapper, model, lod); - - writer->Write(*assetFile); -} - -void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpXModelExportLod(context, asset, currentLod); - } -} - void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - DumpObj(context, asset); - break; - - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - DumpXModelExport(context, asset); - break; - - default: - assert(false); - break; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h index 66569386..e8aa5df8 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW3/IW3.h" -#include "Model/Obj/ObjWriter.h" -#include "Model/XModel/AbstractXModelWriter.h" -#include "Utils/DistinctMapper.h" namespace IW3 { class AssetDumperXModel final : public AbstractAssetDumper { - static GfxImage* GetMaterialColorMap(const Material* material); - static GfxImage* GetMaterialNormalMap(const Material* material); - static GfxImage* GetMaterialSpecularMap(const Material* material); - - static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod); - static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod); - static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); - static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); - - static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); - static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void - AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp index 43f565cf..d8310cdd 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.cpp @@ -2,597 +2,514 @@ #include "Game/IW4/CommonIW4.h" #include "Math/Quaternion.h" -#include "Model/XModel/XModelExportWriter.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.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 +#include using namespace IW4; -namespace IW4 +namespace { - class SurfsDumpingZoneState final : public IZoneAssetDumperState + GfxImage* GetMaterialColorMap(const Material* material) { - std::set m_dumped_surfs; + std::vector potentialTextureDefs; - public: - bool ShouldDumpTechnique(const XModelSurfs* surfs) + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) { - if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end()) - return false; + MaterialTextureDef* def = &material->textureTable[textureIndex]; - m_dumped_surfs.emplace(surfs); - return true; + if (def->semantic == TS_COLOR_MAP) + potentialTextureDefs.push_back(def); } - }; -} // namespace IW4 + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'c' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialNormalMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialSpecularMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model) + { + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; + bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; + bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; + bone.globalRotation = Quaternion32( + model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + void AddXModelMaterials(XModelCommon& out, DistinctMapper& materialMapper, const XModel* model) + { + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = AssetName(material->info.name); + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = AssetName(colorMap->name); + + 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 AddXModelObjects(XModelCommon& out, const XModelSurfs* modelSurfs, const DistinctMapper& materialMapper, const int baseSurfaceIndex) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + XModelObject object; + object.name = std::format("surf{}", surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + out.m_objects.emplace_back(std::move(object)); + } + } + + void AddXModelVertices(XModelCommon& out, const XModelSurfs* modelSurfs) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + vec4_t color; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz[0]; + vertex.coordinates[1] = v.xyz[1]; + vertex.coordinates[2] = v.xyz[2]; + vertex.normal[0] = normalVec[0]; + vertex.normal[1] = normalVec[1]; + vertex.normal[2] = normalVec[2]; + vertex.color[0] = color[0]; + vertex.color[1] = color[1]; + vertex.color[2] = color[2]; + vertex.color[3] = color[3]; + vertex.uv[0] = uv[0]; + vertex.uv[1] = uv[1]; + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) + { + auto totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + if (surface.vertList) + { + totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + totalWeightCount += surface.vertInfo.vertCount[0] * 1; + totalWeightCount += surface.vertInfo.vertCount[1] * 2; + totalWeightCount += surface.vertInfo.vertCount[2] * 3; + totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights.resize(totalWeightCount); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModelSurfs* modelSurfs) + { + auto& weightCollection = out.m_bone_weight_data; + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; + + vertsBlendOffset += 1; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + + vertsBlendOffset += 3; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + + vertsBlendOffset += 5; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; + + vertsBlendOffset += 7; + + 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]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + out.m_vertex_bone_weights.emplace_back(nullptr, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, const XModelSurfs* modelSurfs) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; 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++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) + { + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + DistinctMapper 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* 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 materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) + return; + + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.obj", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = + obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.XMODEL_EXPORT", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + writer->Write(common); + } + + template + void DumpGltfLod( + const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}{}", modelSurfs->name, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + + writer->Write(common); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + XModelCommon common; + PopulateXModelWriter(common, context, currentLod, asset->Asset()); + + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(common, context, asset, currentLod); + if (currentLod == 0u) + DumpObjMtl(common, context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(common, context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(common, context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(common, context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; } -GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_COLOR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'c' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_NORMAL_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_SPECULAR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - if (!model->materialHandles) - return; - - for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) - { - Material* material = model->materialHandles[surfIndex]; - if (!materialMapper.Add(material)) - continue; - - MtlMaterial mtl; - mtl.materialName = std::string(material->info.name); - - GfxImage* colorMap = GetMaterialColorMap(material); - GfxImage* normalMap = GetMaterialNormalMap(material); - GfxImage* specularMap = GetMaterialSpecularMap(material); - - if (colorMap != nullptr) - mtl.colorMapName = colorMap->name; - if (normalMap != nullptr) - mtl.normalMapName = normalMap->name; - if (specularMap != nullptr) - mtl.specularMapName = specularMap->name; - - writer.AddMaterial(std::move(mtl)); - } -} - -void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - ObjObject object; - object.name = "surf" + std::to_string(surfIndex); - object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - - ObjVertex objVertex{}; - ObjNormal objNormal{}; - ObjUv objUv{}; - objVertex.coordinates[0] = v.xyz[0]; - objVertex.coordinates[1] = v.xyz[2]; - objVertex.coordinates[2] = -v.xyz[1]; - objNormal.normal[0] = normalVec[0]; - objNormal.normal[1] = normalVec[2]; - objNormal.normal[2] = -normalVec[1]; - objUv.uv[0] = uv[0]; - objUv.uv[1] = 1.0f - uv[1]; - - writer.AddVertex(static_cast(surfIndex), objVertex); - writer.AddNormal(static_cast(surfIndex), objNormal); - writer.AddUv(static_cast(surfIndex), objUv); - } - } -} - -void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - ObjFace face{}; - face.vertexIndex[0] = tri[2] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[0] + surface.baseVertIndex; - face.normalIndex[0] = face.vertexIndex[0]; - face.normalIndex[1] = face.vertexIndex[1]; - face.normalIndex[2] = face.vertexIndex[2]; - face.uvIndex[0] = face.vertexIndex[0]; - face.uvIndex[1] = face.vertexIndex[1]; - face.uvIndex[2] = face.vertexIndex[2]; - writer.AddFace(static_cast(surfIndex), face); - } - } -} - -void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); - - if (!matFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - writer.WriteMtl(*matFile); -} - -void AssetDumperXModel::DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - const auto* modelSurfs = model->lodInfo[lod].modelSurfs; - - if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) - return; - - const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".obj"); - - if (!assetFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - AddObjObjects(writer, materialMapper, modelSurfs, model->lodInfo[lod].surfIndex); - AddObjVertices(writer, modelSurfs); - AddObjFaces(writer, modelSurfs); - - writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); -} - -void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - auto* surfZoneState = context.GetZoneAssetDumperState(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - - DumpObjLod(context, asset, currentLod); - } -} - -void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) -{ - for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) - { - XModelBone bone; - if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) - bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; - else - bone.name = "INVALID_BONE_NAME"; - - if (boneNum < model->numRootBones) - bone.parentIndex = -1; - else - bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; - bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; - bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = Quaternion32(0, 0, 0, 1); - } - else - { - bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; - bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; - bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; - bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); - } - - writer.AddBone(std::move(bone)); - } -} - -void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) - { - Material* material = model->materialHandles[surfaceMaterialNum]; - if (materialMapper.Add(material)) - { - XModelMaterial xMaterial; - xMaterial.ApplyDefaults(); - - xMaterial.name = material->info.name; - const auto* colorMap = GetMaterialColorMap(material); - if (colorMap) - xMaterial.colorMapName = std::string(colorMap->name); - - writer.AddMaterial(std::move(xMaterial)); - } - } -} - -void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - XModelObject object; - object.name = "surf" + std::to_string(surfIndex); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - vec4_t color; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - Common::Vec4UnpackGfxColor(v.color, &color); - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - vertex.normal[0] = normalVec[0]; - vertex.normal[1] = normalVec[1]; - vertex.normal[2] = normalVec[2]; - vertex.color[0] = color[0]; - vertex.color[1] = color[1]; - vertex.color[2] = color[2]; - vertex.color[3] = color[3]; - vertex.uv[0] = uv[0]; - vertex.uv[1] = uv[1]; - - writer.AddVertex(vertex); - } - } -} - -void AssetDumperXModel::AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) -{ - weightCollection.totalWeightCount = 0u; - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - if (surface.vertList) - { - weightCollection.totalWeightCount += surface.vertListCount; - } - - if (surface.vertInfo.vertsBlend) - { - weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; - } - } - - weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); -} - -void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, - const XModelSurfs* modelSurfs, - XModelVertexBoneWeightCollection& weightCollection) -{ - size_t weightOffset = 0u; - - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - auto handledVertices = 0u; - - if (surface.vertList) - { - for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) - { - const auto& vertList = surface.vertList[vertListIndex]; - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; - - for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - handledVertices += vertList.vertCount; - } - } - - auto vertsBlendOffset = 0u; - if (surface.vertInfo.vertsBlend) - { - // 1 bone weight - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; - - vertsBlendOffset += 1; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - - // 2 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneWeight0 = 1.0f - boneWeight1; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - - vertsBlendOffset += 3; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); - } - - // 3 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - - vertsBlendOffset += 5; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); - } - - // 4 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); - const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; - - vertsBlendOffset += 7; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); - } - - handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; - } - - for (; handledVertices < surface.vertCount; handledVertices++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); - } - } -} - -void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, - const DistinctMapper& materialMapper, - const XModelSurfs* modelSurfs, - const int baseSurfaceIndex) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - XModelFace face{}; - face.vertexIndex[0] = tri[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[2] + surface.baseVertIndex; - face.objectIndex = static_cast(surfIndex); - face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); - writer.AddFace(face); - } - } -} - -void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - const auto* modelSurfs = model->lodInfo[lod].modelSurfs; - - if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) - return; - - const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".XMODEL_EXPORT"); - - if (!assetFile) - return; - - const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - XModelVertexBoneWeightCollection boneWeightCollection; - AllocateXModelBoneWeights(modelSurfs, boneWeightCollection); - - AddXModelBones(context, *writer, model); - AddXModelMaterials(*writer, materialMapper, model); - AddXModelObjects(*writer, modelSurfs); - AddXModelVertices(*writer, modelSurfs); - AddXModelVertexBoneWeights(*writer, modelSurfs, boneWeightCollection); - AddXModelFaces(*writer, materialMapper, modelSurfs, model->lodInfo[lod].surfIndex); - - writer->Write(*assetFile); -} - -void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo* asset) -{ - auto* surfZoneState = context.GetZoneAssetDumperState(); - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - DumpXModelExportLod(context, asset, currentLod); - } -} - void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - DumpObj(context, asset); - break; - - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - DumpXModelExport(context, asset); - break; - - default: - assert(false); - break; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h index 647ce28c..84be5262 100644 --- a/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW4/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW4/IW4.h" -#include "Model/Obj/ObjWriter.h" -#include "Model/XModel/AbstractXModelWriter.h" -#include "Utils/DistinctMapper.h" namespace IW4 { class AssetDumperXModel final : public AbstractAssetDumper { - static GfxImage* GetMaterialColorMap(const Material* material); - static GfxImage* GetMaterialNormalMap(const Material* material); - static GfxImage* GetMaterialSpecularMap(const Material* material); - - static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod); - static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); - static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); - - static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); - static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs); - static void AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs); - static void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); - static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); - static void - AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp index 865831ca..1cf707d5 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp @@ -2,596 +2,514 @@ #include "Game/IW5/CommonIW5.h" #include "Math/Quaternion.h" -#include "Model/XModel/XModelExportWriter.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.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 +#include using namespace IW5; -namespace IW5 +namespace { - class SurfsDumpingZoneState final : public IZoneAssetDumperState + GfxImage* GetMaterialColorMap(const Material* material) { - std::set m_dumped_surfs; + std::vector potentialTextureDefs; - public: - bool ShouldDumpTechnique(const XModelSurfs* surfs) + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) { - if (m_dumped_surfs.find(surfs) != m_dumped_surfs.end()) - return false; + MaterialTextureDef* def = &material->textureTable[textureIndex]; - m_dumped_surfs.emplace(surfs); - return true; + if (def->semantic == TS_COLOR_MAP) + potentialTextureDefs.push_back(def); } - }; -} // namespace IW5 + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'c' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialNormalMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialSpecularMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model) + { + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; + bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; + bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; + bone.globalRotation = Quaternion32( + model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + void AddXModelMaterials(XModelCommon& out, DistinctMapper& materialMapper, const XModel* model) + { + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = AssetName(material->info.name); + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = AssetName(colorMap->name); + + 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 AddXModelObjects(XModelCommon& out, const XModelSurfs* modelSurfs, const DistinctMapper& materialMapper, const int baseSurfaceIndex) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + XModelObject object; + object.name = std::format("surf{}", surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + out.m_objects.emplace_back(std::move(object)); + } + } + + void AddXModelVertices(XModelCommon& out, const XModelSurfs* modelSurfs) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0.packedVerts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + vec4_t color; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz[0]; + vertex.coordinates[1] = v.xyz[1]; + vertex.coordinates[2] = v.xyz[2]; + vertex.normal[0] = normalVec[0]; + vertex.normal[1] = normalVec[1]; + vertex.normal[2] = normalVec[2]; + vertex.color[0] = color[0]; + vertex.color[1] = color[1]; + vertex.color[2] = color[2]; + vertex.color[3] = color[3]; + vertex.uv[0] = uv[0]; + vertex.uv[1] = uv[1]; + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) + { + auto totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + + if (surface.vertList) + { + totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + totalWeightCount += surface.vertInfo.vertCount[0] * 1; + totalWeightCount += surface.vertInfo.vertCount[1] * 2; + totalWeightCount += surface.vertInfo.vertCount[2] * 3; + totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights.resize(totalWeightCount); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModelSurfs* modelSurfs) + { + auto& weightCollection = out.m_bone_weight_data; + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) + { + const auto& surface = modelSurfs->surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; + + vertsBlendOffset += 1; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + + vertsBlendOffset += 3; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + + vertsBlendOffset += 5; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; + + vertsBlendOffset += 7; + + 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]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + out.m_vertex_bone_weights.emplace_back(nullptr, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, const XModelSurfs* modelSurfs) + { + for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; 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++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) + { + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + DistinctMapper 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* 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 materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + + if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) + return; + + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.obj", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = + obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}.XMODEL_EXPORT", modelSurfs->name)); + + if (!assetFile) + return; + + const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + writer->Write(common); + } + + template + void DumpGltfLod( + const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto* modelSurfs = model->lodInfo[lod].modelSurfs; + const auto assetFile = context.OpenAssetFile(std::format("model_export/{}{}", modelSurfs->name, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + + writer->Write(common); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + XModelCommon common; + PopulateXModelWriter(common, context, currentLod, asset->Asset()); + + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(common, context, asset, currentLod); + if (currentLod == 0u) + DumpObjMtl(common, context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(common, context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(common, context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(common, context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; } -GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_COLOR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'c' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_NORMAL_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_SPECULAR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - if (!model->materialHandles) - return; - - for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) - { - Material* material = model->materialHandles[surfIndex]; - if (!materialMapper.Add(material)) - continue; - - MtlMaterial mtl; - mtl.materialName = std::string(material->info.name); - GfxImage* colorMap = GetMaterialColorMap(material); - GfxImage* normalMap = GetMaterialNormalMap(material); - GfxImage* specularMap = GetMaterialSpecularMap(material); - - if (colorMap != nullptr) - mtl.colorMapName = colorMap->name; - if (normalMap != nullptr) - mtl.normalMapName = normalMap->name; - if (specularMap != nullptr) - mtl.specularMapName = specularMap->name; - - writer.AddMaterial(std::move(mtl)); - } -} - -void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - ObjObject object; - object.name = "surf" + std::to_string(surfIndex); - object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0.packedVerts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - - ObjVertex objVertex{}; - ObjNormal objNormal{}; - ObjUv objUv{}; - objVertex.coordinates[0] = v.xyz[0]; - objVertex.coordinates[1] = v.xyz[2]; - objVertex.coordinates[2] = -v.xyz[1]; - objNormal.normal[0] = normalVec[0]; - objNormal.normal[1] = normalVec[2]; - objNormal.normal[2] = -normalVec[1]; - objUv.uv[0] = uv[0]; - objUv.uv[1] = 1.0f - uv[1]; - - writer.AddVertex(static_cast(surfIndex), objVertex); - writer.AddNormal(static_cast(surfIndex), objNormal); - writer.AddUv(static_cast(surfIndex), objUv); - } - } -} - -void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - ObjFace face{}; - face.vertexIndex[0] = tri[2] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[0] + surface.baseVertIndex; - face.normalIndex[0] = face.vertexIndex[0]; - face.normalIndex[1] = face.vertexIndex[1]; - face.normalIndex[2] = face.vertexIndex[2]; - face.uvIndex[0] = face.vertexIndex[0]; - face.uvIndex[1] = face.vertexIndex[1]; - face.uvIndex[2] = face.vertexIndex[2]; - writer.AddFace(static_cast(surfIndex), face); - } - } -} - -void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); - - if (!matFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - writer.WriteMtl(*matFile); -} - -void AssetDumperXModel::DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - const auto* modelSurfs = model->lodInfo[lod].modelSurfs; - - if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) - return; - - const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".obj"); - - if (!assetFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - AddObjObjects(writer, materialMapper, modelSurfs, model->lodInfo[lod].surfIndex); - AddObjVertices(writer, modelSurfs); - AddObjFaces(writer, modelSurfs); - - writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); -} - -void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - auto* surfZoneState = context.GetZoneAssetDumperState(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - - DumpObjLod(context, asset, currentLod); - } -} - -void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) -{ - for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) - { - XModelBone bone; - if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) - bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; - else - bone.name = "INVALID_BONE_NAME"; - - if (boneNum < model->numRootBones) - bone.parentIndex = -1; - else - bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; - bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; - bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = Quaternion32(0, 0, 0, 1); - } - else - { - bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; - bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; - bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; - bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); - } - - writer.AddBone(std::move(bone)); - } -} - -void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) - { - Material* material = model->materialHandles[surfaceMaterialNum]; - if (materialMapper.Add(material)) - { - XModelMaterial xMaterial; - xMaterial.ApplyDefaults(); - - xMaterial.name = material->info.name; - const auto* colorMap = GetMaterialColorMap(material); - if (colorMap) - xMaterial.colorMapName = std::string(colorMap->name); - - writer.AddMaterial(std::move(xMaterial)); - } - } -} - -void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - XModelObject object; - object.name = "surf" + std::to_string(surfIndex); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0.packedVerts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - vec4_t color; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - Common::Vec4UnpackGfxColor(v.color, &color); - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - vertex.normal[0] = normalVec[0]; - vertex.normal[1] = normalVec[1]; - vertex.normal[2] = normalVec[2]; - vertex.color[0] = color[0]; - vertex.color[1] = color[1]; - vertex.color[2] = color[2]; - vertex.color[3] = color[3]; - vertex.uv[0] = uv[0]; - vertex.uv[1] = uv[1]; - - writer.AddVertex(vertex); - } - } -} - -void AssetDumperXModel::AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection) -{ - weightCollection.totalWeightCount = 0u; - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - - if (surface.vertList) - { - weightCollection.totalWeightCount += surface.vertListCount; - } - - if (surface.vertInfo.vertsBlend) - { - weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; - } - } - - weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); -} - -void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, - const XModelSurfs* modelSurfs, - XModelVertexBoneWeightCollection& weightCollection) -{ - size_t weightOffset = 0u; - - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - auto handledVertices = 0u; - - if (surface.vertList) - { - for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) - { - const auto& vertList = surface.vertList[vertListIndex]; - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; - - for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - handledVertices += vertList.vertCount; - } - } - - auto vertsBlendOffset = 0u; - if (surface.vertInfo.vertsBlend) - { - // 1 bone weight - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; - - vertsBlendOffset += 1; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - - // 2 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneWeight0 = 1.0f - boneWeight1; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - - vertsBlendOffset += 3; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); - } - - // 3 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - - vertsBlendOffset += 5; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); - } - - // 4 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); - const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; - - vertsBlendOffset += 7; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); - } - - handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; - } - - for (; handledVertices < surface.vertCount; handledVertices++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); - } - } -} - -void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, - const DistinctMapper& materialMapper, - const XModelSurfs* modelSurfs, - const int baseSurfaceIndex) -{ - for (auto surfIndex = 0u; surfIndex < modelSurfs->numsurfs; surfIndex++) - { - const auto& surface = modelSurfs->surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - XModelFace face{}; - face.vertexIndex[0] = tri[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[2] + surface.baseVertIndex; - face.objectIndex = static_cast(surfIndex); - face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); - writer.AddFace(face); - } - } -} - -void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - const auto* modelSurfs = model->lodInfo[lod].modelSurfs; - - if (modelSurfs->name[0] == ',' || modelSurfs->surfs == nullptr) - return; - - const auto assetFile = context.OpenAssetFile("model_export/" + std::string(modelSurfs->name) + ".XMODEL_EXPORT"); - - if (!assetFile) - return; - - const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - XModelVertexBoneWeightCollection boneWeightCollection; - AllocateXModelBoneWeights(modelSurfs, boneWeightCollection); - - AddXModelBones(context, *writer, model); - AddXModelMaterials(*writer, materialMapper, model); - AddXModelObjects(*writer, modelSurfs); - AddXModelVertices(*writer, modelSurfs); - AddXModelVertexBoneWeights(*writer, modelSurfs, boneWeightCollection); - AddXModelFaces(*writer, materialMapper, modelSurfs, model->lodInfo[lod].surfIndex); - - writer->Write(*assetFile); -} - -void AssetDumperXModel::DumpXModelExport(AssetDumpingContext& context, XAssetInfo* asset) -{ - auto* surfZoneState = context.GetZoneAssetDumperState(); - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - if (!model->lodInfo[currentLod].modelSurfs || !surfZoneState->ShouldDumpTechnique(model->lodInfo[currentLod].modelSurfs)) - continue; - DumpXModelExportLod(context, asset, currentLod); - } -} - void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - DumpObj(context, asset); - break; - - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - DumpXModelExport(context, asset); - break; - - default: - assert(false); - break; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h index 458caba8..28880a59 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/IW5/IW5.h" -#include "Model/Obj/ObjWriter.h" -#include "Model/XModel/AbstractXModelWriter.h" -#include "Utils/DistinctMapper.h" namespace IW5 { class AssetDumperXModel final : public AbstractAssetDumper { - static GfxImage* GetMaterialColorMap(const Material* material); - static GfxImage* GetMaterialNormalMap(const Material* material); - static GfxImage* GetMaterialSpecularMap(const Material* material); - - static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void AddObjVertices(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void AddObjFaces(ObjWriter& writer, const XModelSurfs* modelSurfs); - static void DumpObjLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod); - static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); - static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); - - static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); - static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddXModelObjects(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs); - static void AddXModelVertices(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs); - static void AllocateXModelBoneWeights(const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); - static void AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModelSurfs* modelSurfs, XModelVertexBoneWeightCollection& weightCollection); - static void - AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModelSurfs* modelSurfs, int baseSurfaceIndex); - static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpXModelExport(AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp index 5997be6b..ed204379 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp @@ -2,592 +2,525 @@ #include "Game/T5/CommonT5.h" #include "Math/Quaternion.h" -#include "Model/XModel/XModelExportWriter.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.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 -#include +#include using namespace T5; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* GetMaterialColorMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'c' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialNormalMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + GfxImage* GetMaterialSpecularMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->u.image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->u.image; + } + + return potentialTextureDefs[0]->u.image; + } + + void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model) + { + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; + bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; + bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; + bone.globalRotation = Quaternion32( + model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + void AddXModelMaterials(XModelCommon& out, DistinctMapper& materialMapper, const XModel* model) + { + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = AssetName(material->info.name); + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = AssetName(colorMap->name); + + 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 AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper& materialMapper) + { + const auto surfCount = model->lodInfo[lod].numsurfs; + const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + XModelObject object; + object.name = std::format("surf{}", surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + out.m_objects.emplace_back(std::move(object)); + } + } + + void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv; + vec3_t normalVec; + vec4_t color; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz[0]; + vertex.coordinates[1] = v.xyz[1]; + vertex.coordinates[2] = v.xyz[2]; + vertex.normal[0] = normalVec[0]; + vertex.normal[1] = normalVec[1]; + vertex.normal[2] = normalVec[2]; + vertex.color[0] = color[0]; + vertex.color[1] = color[1]; + vertex.color[2] = color[2]; + vertex.color[3] = color[3]; + vertex.uv[0] = uv[0]; + vertex.uv[1] = uv[1]; + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + auto totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertList) + { + totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + totalWeightCount += surface.vertInfo.vertCount[0] * 1; + totalWeightCount += surface.vertInfo.vertCount[1] * 2; + totalWeightCount += surface.vertInfo.vertCount[2] * 3; + totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights.resize(totalWeightCount); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + auto& weightCollection = out.m_bone_weight_data; + + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; + + vertsBlendOffset += 1; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + + vertsBlendOffset += 3; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + + vertsBlendOffset += 5; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; + + vertsBlendOffset += 7; + + 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]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + out.m_vertex_bone_weights.emplace_back(nullptr, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, 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]; + auto& object = out.m_objects[surfIndex]; + object.m_faces.reserve(surface.triCount); + + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) + { + DistinctMapper 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* 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 materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj")); + + if (!assetFile) + return; + + const auto writer = + obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + writer->Write(common); + } + + template + void DumpGltfLod( + const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + + writer->Write(common); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + XModelCommon common; + PopulateXModelWriter(common, context, currentLod, asset->Asset()); + + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(common, context, asset, currentLod); + if (currentLod == 0u) + DumpObjMtl(common, context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(common, context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(common, context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(common, context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; } -GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'c' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_NORMAL_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_SPECULAR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->u.image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->u.image; - } - - return potentialTextureDefs[0]->u.image; -} - -void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - if (!model->materialHandles) - return; - - for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) - { - Material* material = model->materialHandles[surfIndex]; - if (!materialMapper.Add(material)) - continue; - - MtlMaterial mtl; - mtl.materialName = std::string(material->info.name); - - GfxImage* colorMap = GetMaterialColorMap(material); - GfxImage* normalMap = GetMaterialNormalMap(material); - GfxImage* specularMap = GetMaterialSpecularMap(material); - - if (colorMap != nullptr) - mtl.colorMapName = colorMap->name; - if (normalMap != nullptr) - mtl.normalMapName = normalMap->name; - if (specularMap != nullptr) - mtl.specularMapName = specularMap->name; - - writer.AddMaterial(std::move(mtl)); - } -} - -void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - ObjObject object; - object.name = "surf" + std::to_string(surfIndex); - object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - - ObjVertex objVertex{}; - ObjNormal objNormal{}; - ObjUv objUv{}; - objVertex.coordinates[0] = v.xyz[0]; - objVertex.coordinates[1] = v.xyz[2]; - objVertex.coordinates[2] = -v.xyz[1]; - objNormal.normal[0] = normalVec[0]; - objNormal.normal[1] = normalVec[2]; - objNormal.normal[2] = -normalVec[1]; - objUv.uv[0] = uv[0]; - objUv.uv[1] = 1.0f - uv[1]; - - writer.AddVertex(static_cast(surfIndex), objVertex); - writer.AddNormal(static_cast(surfIndex), objNormal); - writer.AddUv(static_cast(surfIndex), objUv); - } - } -} - -void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - ObjFace face{}; - face.vertexIndex[0] = tri[2] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[0] + surface.baseVertIndex; - face.normalIndex[0] = face.vertexIndex[0]; - face.normalIndex[1] = face.vertexIndex[1]; - face.normalIndex[2] = face.vertexIndex[2]; - face.uvIndex[0] = face.vertexIndex[0]; - face.uvIndex[1] = face.vertexIndex[1]; - face.uvIndex[2] = face.vertexIndex[2]; - writer.AddFace(static_cast(surfIndex), face); - } - } -} - -void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); - - if (!matFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - writer.WriteMtl(*matFile); -} - -void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".OBJ"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - AddObjObjects(writer, materialMapper, model, lod); - AddObjVertices(writer, model, lod); - AddObjFaces(writer, model, lod); - - writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); -} - -void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpObjLod(context, asset, currentLod); - } -} - -void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) -{ - for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) - { - XModelBone bone; - if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) - bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; - else - bone.name = "INVALID_BONE_NAME"; - - if (boneNum < model->numRootBones) - bone.parentIndex = -1; - else - bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - bone.globalOffset[0] = model->baseMat[boneNum].trans[0]; - bone.globalOffset[1] = model->baseMat[boneNum].trans[1]; - bone.globalOffset[2] = model->baseMat[boneNum].trans[2]; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat[0], model->baseMat[boneNum].quat[1], model->baseMat[boneNum].quat[2], model->baseMat[boneNum].quat[3]); - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = Quaternion32(0, 0, 0, 1); - } - else - { - bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; - bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; - bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; - bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); - } - - writer.AddBone(std::move(bone)); - } -} - -void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) - { - Material* material = model->materialHandles[surfaceMaterialNum]; - if (materialMapper.Add(material)) - { - XModelMaterial xMaterial; - xMaterial.ApplyDefaults(); - - xMaterial.name = material->info.name; - const auto* colorMap = GetMaterialColorMap(material); - if (colorMap) - xMaterial.colorMapName = std::string(colorMap->name); - - writer.AddMaterial(std::move(xMaterial)); - } - } -} - -void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - XModelObject object; - object.name = "surf" + std::to_string(surfIndex); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv; - vec3_t normalVec; - vec4_t color; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - Common::Vec4UnpackGfxColor(v.color, &color); - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - vertex.normal[0] = normalVec[0]; - vertex.normal[1] = normalVec[1]; - vertex.normal[2] = normalVec[2]; - vertex.color[0] = color[0]; - vertex.color[1] = color[1]; - vertex.color[2] = color[2]; - vertex.color[3] = color[3]; - vertex.uv[0] = uv[0]; - vertex.uv[1] = uv[1]; - - writer.AddVertex(vertex); - } - } -} - -void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - weightCollection.totalWeightCount = 0u; - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - if (surface.vertList) - { - weightCollection.totalWeightCount += surface.vertListCount; - } - - if (surface.vertInfo.vertsBlend) - { - weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; - } - } - - weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); -} - -void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, - const XModel* model, - const unsigned lod, - XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - size_t weightOffset = 0u; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - auto handledVertices = 0u; - - if (surface.vertList) - { - for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) - { - const auto& vertList = surface.vertList[vertListIndex]; - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; - - for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - handledVertices += vertList.vertCount; - } - } - - auto vertsBlendOffset = 0u; - if (surface.vertInfo.vertsBlend) - { - // 1 bone weight - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; - - vertsBlendOffset += 1; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - - // 2 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneWeight0 = 1.0f - boneWeight1; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - - vertsBlendOffset += 3; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); - } - - // 3 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - - vertsBlendOffset += 5; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); - } - - // 4 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); - const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; - - vertsBlendOffset += 7; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); - } - - handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; - } - - for (; handledVertices < surface.vertCount; handledVertices++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); - } - } -} - -void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - XModelFace face{}; - face.vertexIndex[0] = tri[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[2] + surface.baseVertIndex; - face.objectIndex = static_cast(surfIndex); - face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - writer.AddFace(face); - } - } -} - -void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - XModelVertexBoneWeightCollection boneWeightCollection; - AllocateXModelBoneWeights(model, lod, boneWeightCollection); - - AddXModelBones(context, *writer, model); - AddXModelMaterials(*writer, materialMapper, model); - AddXModelObjects(*writer, model, lod); - AddXModelVertices(*writer, model, lod); - AddXModelVertexBoneWeights(*writer, model, lod, boneWeightCollection); - AddXModelFaces(*writer, materialMapper, model, lod); - - writer->Write(*assetFile); -} - -void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpXModelExportLod(context, asset, currentLod); - } -} - void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - DumpObj(context, asset); - break; - - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - DumpXModelExport(context, asset); - break; - - default: - assert(false); - break; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h index bd96b00e..08a1a274 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/T5/T5.h" -#include "Model/Obj/ObjWriter.h" -#include "Model/XModel/AbstractXModelWriter.h" -#include "Utils/DistinctMapper.h" namespace T5 { class AssetDumperXModel final : public AbstractAssetDumper { - static GfxImage* GetMaterialColorMap(const Material* material); - static GfxImage* GetMaterialNormalMap(const Material* material); - static GfxImage* GetMaterialSpecularMap(const Material* material); - - static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod); - static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod); - static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); - static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); - - static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); - static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void - AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp index 03884831..0a136a96 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp @@ -2,616 +2,549 @@ #include "Game/T6/CommonT6.h" #include "Math/Quaternion.h" -#include "Model/XModel/XModelExportWriter.h" #include "ObjWriting.h" +#include "Utils/DistinctMapper.h" #include "Utils/HalfFloat.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 -#include +#include using namespace T6; +namespace +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* GetMaterialColorMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->image; + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') + return def->image; + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') + return def->image; + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + GfxImage* GetMaterialNormalMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_NORMAL_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + GfxImage* GetMaterialSpecularMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + + if (def->semantic == TS_SPECULAR_MAP) + potentialTextureDefs.push_back(def); + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return potentialTextureDefs[0]->image; + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return def->image; + } + + return potentialTextureDefs[0]->image; + } + + void AddXModelBones(XModelCommon& out, const AssetDumpingContext& context, const XModel* model) + { + for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) + { + XModelBone bone; + if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) + bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; + else + bone.name = "INVALID_BONE_NAME"; + + if (boneNum < model->numRootBones) + bone.parentIndex = -1; + else + bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + bone.globalOffset[0] = model->baseMat[boneNum].trans.x; + bone.globalOffset[1] = model->baseMat[boneNum].trans.y; + bone.globalOffset[2] = model->baseMat[boneNum].trans.z; + bone.globalRotation = + Quaternion32(model->baseMat[boneNum].quat.x, model->baseMat[boneNum].quat.y, model->baseMat[boneNum].quat.z, model->baseMat[boneNum].quat.w); + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = Quaternion32(0, 0, 0, 1); + } + else + { + bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; + bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; + bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; + bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), + QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + void AddXModelMaterials(XModelCommon& out, DistinctMapper& materialMapper, const XModel* model) + { + for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) + { + Material* material = model->materialHandles[surfaceMaterialNum]; + if (materialMapper.Add(material)) + { + XModelMaterial xMaterial; + xMaterial.ApplyDefaults(); + + xMaterial.name = AssetName(material->info.name); + const auto* colorMap = GetMaterialColorMap(material); + if (colorMap) + xMaterial.colorMapName = AssetName(colorMap->name); + + 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 AddXModelObjects(XModelCommon& out, const XModel* model, const unsigned lod, const DistinctMapper& materialMapper) + { + const auto surfCount = model->lodInfo[lod].numsurfs; + const auto baseSurfaceIndex = model->lodInfo[lod].surfIndex; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + XModelObject object; + object.name = std::format("surf{}", surfIndex); + object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfaceIndex)); + + out.m_objects.emplace_back(std::move(object)); + } + } + + void AddXModelVertices(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + if (!surfs) + return; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) + { + const auto& v = surface.verts0[vertexIndex]; + vec2_t uv{}; + vec3_t normalVec{}; + vec4_t color{}; + + Common::Vec2UnpackTexCoords(v.texCoord, &uv); + Common::Vec3UnpackUnitVec(v.normal, &normalVec); + Common::Vec4UnpackGfxColor(v.color, &color); + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz.x; + vertex.coordinates[1] = v.xyz.y; + vertex.coordinates[2] = v.xyz.z; + vertex.normal[0] = normalVec.x; + vertex.normal[1] = normalVec.y; + vertex.normal[2] = normalVec.z; + vertex.color[0] = color.x; + vertex.color[1] = color.y; + vertex.color[2] = color.z; + vertex.color[3] = color.w; + vertex.uv[0] = uv.x; + vertex.uv[1] = uv.y; + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + if (!surfs) + return; + + auto totalWeightCount = 0u; + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertList) + { + totalWeightCount += surface.vertListCount; + } + + if (surface.vertInfo.vertsBlend) + { + totalWeightCount += surface.vertInfo.vertCount[0] * 1; + totalWeightCount += surface.vertInfo.vertCount[1] * 2; + totalWeightCount += surface.vertInfo.vertCount[2] * 3; + totalWeightCount += surface.vertInfo.vertCount[3] * 4; + } + } + + weightCollection.weights.resize(totalWeightCount); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + auto& weightCollection = out.m_bone_weight_data; + + if (!surfs) + return; + + size_t weightOffset = 0u; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + auto handledVertices = 0u; + + if (surface.vertList) + { + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) + { + const auto& vertList = surface.vertList[vertListIndex]; + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; + + for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) + { + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + handledVertices += vertList.vertCount; + } + } + + auto vertsBlendOffset = 0u; + if (surface.vertInfo.vertsBlend) + { + // 1 bone weight + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; + + vertsBlendOffset += 1; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 1); + } + + // 2 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneWeight0 = 1.0f - boneWeight1; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + + vertsBlendOffset += 3; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 2); + } + + // 3 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + + vertsBlendOffset += 5; + + out.m_vertex_bone_weights.emplace_back(boneWeightOffset, 3); + } + + // 4 bone weights + for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) + { + const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; + const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); + const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); + const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); + const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); + const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); + const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; + weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; + + vertsBlendOffset += 7; + + 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]; + } + + for (; handledVertices < surface.vertCount; handledVertices++) + { + out.m_vertex_bone_weights.emplace_back(nullptr, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod) + { + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + if (!surfs) + return; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + auto& object = out.m_objects[surfIndex]; + object.m_faces.reserve(surface.triCount); + + for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) + { + const auto& tri = surface.triIndices[triIndex]; + + XModelFace face{}; + face.vertexIndex[0] = tri[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) + { + DistinctMapper 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* 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 materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpObjLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".obj")); + + if (!assetFile) + return; + + const auto writer = + obj::CreateObjWriter(*assetFile, std::format("{}.mtl", model->name), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + DistinctMapper materialMapper(model->numsurfs); + + writer->Write(common); + } + + void DumpXModelExportLod(const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, ".XMODEL_EXPORT")); + + if (!assetFile) + return; + + const auto writer = xmodel_export::CreateWriterForVersion6(*assetFile, context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + writer->Write(common); + } + + template + void DumpGltfLod( + const XModelCommon& common, const AssetDumpingContext& context, const XAssetInfo* asset, const unsigned lod, const std::string& extension) + { + const auto* model = asset->Asset(); + const auto assetFile = context.OpenAssetFile(GetFileNameForLod(model->name, lod, extension)); + + if (!assetFile) + return; + + const auto output = std::make_unique(*assetFile); + const auto writer = gltf::Writer::CreateWriter(output.get(), context.m_zone->m_game->GetShortName(), context.m_zone->m_name); + + writer->Write(common); + } + + void DumpXModelSurfs(const AssetDumpingContext& context, const XAssetInfo* asset) + { + const auto* model = asset->Asset(); + + for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) + { + XModelCommon common; + PopulateXModelWriter(common, context, currentLod, asset->Asset()); + + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + DumpObjLod(common, context, asset, currentLod); + if (currentLod == 0u) + DumpObjMtl(common, context, asset); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + DumpXModelExportLod(common, context, asset, currentLod); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + DumpGltfLod(common, context, asset, currentLod, ".gltf"); + break; + + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + DumpGltfLod(common, context, asset, currentLod, ".glb"); + break; + + default: + assert(false); + break; + } + } + } +} // namespace + bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; } -GfxImage* AssetDumperXModel::GetMaterialColorMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->image; - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') - return def->image; - } - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') - return def->image; - } - - for (const auto* def : potentialTextureDefs) - { - if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') - return def->image; - } - - return potentialTextureDefs[0]->image; -} - -GfxImage* AssetDumperXModel::GetMaterialNormalMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_NORMAL_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 'n' && def->nameEnd == 'p') - return def->image; - } - - return potentialTextureDefs[0]->image; -} - -GfxImage* AssetDumperXModel::GetMaterialSpecularMap(const Material* material) -{ - std::vector potentialTextureDefs; - - for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) - { - MaterialTextureDef* def = &material->textureTable[textureIndex]; - - if (def->semantic == TS_SPECULAR_MAP) - potentialTextureDefs.push_back(def); - } - - if (potentialTextureDefs.empty()) - return nullptr; - if (potentialTextureDefs.size() == 1) - return potentialTextureDefs[0]->image; - - for (const auto* def : potentialTextureDefs) - { - if (def->nameStart == 's' && def->nameEnd == 'p') - return def->image; - } - - return potentialTextureDefs[0]->image; -} - -void AssetDumperXModel::AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - if (!model->materialHandles) - return; - - for (auto surfIndex = 0u; surfIndex < model->numsurfs; surfIndex++) - { - Material* material = model->materialHandles[surfIndex]; - if (!materialMapper.Add(material)) - continue; - - MtlMaterial mtl; - mtl.materialName = std::string(material->info.name); - - GfxImage* colorMap = GetMaterialColorMap(material); - GfxImage* normalMap = GetMaterialNormalMap(material); - GfxImage* specularMap = GetMaterialSpecularMap(material); - - if (colorMap != nullptr) - mtl.colorMapName = colorMap->name; - if (normalMap != nullptr) - mtl.normalMapName = normalMap->name; - if (specularMap != nullptr) - mtl.specularMapName = specularMap->name; - - writer.AddMaterial(std::move(mtl)); - } -} - -void AssetDumperXModel::AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - ObjObject object; - object.name = "surf" + std::to_string(surfIndex); - object.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddObjVertices(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv{}; - vec3_t normalVec{}; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - - ObjVertex objVertex{}; - ObjNormal objNormal{}; - ObjUv objUv{}; - objVertex.coordinates[0] = v.xyz.x; - objVertex.coordinates[1] = v.xyz.z; - objVertex.coordinates[2] = -v.xyz.y; - objNormal.normal[0] = normalVec.x; - objNormal.normal[1] = normalVec.z; - objNormal.normal[2] = -normalVec.y; - objUv.uv[0] = uv.x; - objUv.uv[1] = 1.0f - uv.y; - - writer.AddVertex(static_cast(surfIndex), objVertex); - writer.AddNormal(static_cast(surfIndex), objNormal); - writer.AddUv(static_cast(surfIndex), objUv); - } - } -} - -void AssetDumperXModel::AddObjFaces(ObjWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - ObjFace face{}; - face.vertexIndex[0] = tri[2] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[0] + surface.baseVertIndex; - face.normalIndex[0] = face.vertexIndex[0]; - face.normalIndex[1] = face.vertexIndex[1]; - face.normalIndex[2] = face.vertexIndex[2]; - face.uvIndex[0] = face.vertexIndex[0]; - face.uvIndex[1] = face.vertexIndex[1]; - face.uvIndex[2] = face.vertexIndex[2]; - writer.AddFace(static_cast(surfIndex), face); - } - } -} - -void AssetDumperXModel::DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - const auto matFile = context.OpenAssetFile("model_export/" + std::string(model->name) + ".mtl"); - - if (!matFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - writer.WriteMtl(*matFile); -} - -void AssetDumperXModel::DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".OBJ"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - ObjWriter writer(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - - AddObjMaterials(writer, materialMapper, model); - AddObjObjects(writer, materialMapper, model, lod); - AddObjVertices(writer, model, lod); - AddObjFaces(writer, model, lod); - - writer.WriteObj(*assetFile, std::string(model->name) + ".mtl"); -} - -void AssetDumperXModel::DumpObj(AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - - DumpObjMat(context, asset); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpObjLod(context, asset, currentLod); - } -} - -void AssetDumperXModel::AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model) -{ - for (auto boneNum = 0u; boneNum < model->numBones; boneNum++) - { - XModelBone bone; - if (model->boneNames[boneNum] < context.m_zone->m_script_strings.Count()) - bone.name = context.m_zone->m_script_strings[model->boneNames[boneNum]]; - else - bone.name = "INVALID_BONE_NAME"; - - if (boneNum < model->numRootBones) - bone.parentIndex = -1; - else - bone.parentIndex = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - bone.globalOffset[0] = model->baseMat[boneNum].trans.x; - bone.globalOffset[1] = model->baseMat[boneNum].trans.y; - bone.globalOffset[2] = model->baseMat[boneNum].trans.z; - bone.globalRotation = - Quaternion32(model->baseMat[boneNum].quat.x, model->baseMat[boneNum].quat.y, model->baseMat[boneNum].quat.z, model->baseMat[boneNum].quat.w); - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = Quaternion32(0, 0, 0, 1); - } - else - { - bone.localOffset[0] = model->trans[boneNum - model->numRootBones][0]; - bone.localOffset[1] = model->trans[boneNum - model->numRootBones][1]; - bone.localOffset[2] = model->trans[boneNum - model->numRootBones][2]; - bone.localRotation = Quaternion32(QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][0]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][1]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][2]), - QuatInt16::ToFloat(model->quats[boneNum - model->numRootBones][3])); - } - - writer.AddBone(std::move(bone)); - } -} - -void AssetDumperXModel::AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model) -{ - for (auto surfaceMaterialNum = 0; surfaceMaterialNum < model->numsurfs; surfaceMaterialNum++) - { - Material* material = model->materialHandles[surfaceMaterialNum]; - if (materialMapper.Add(material)) - { - XModelMaterial xMaterial; - xMaterial.ApplyDefaults(); - - xMaterial.name = material->info.name; - const auto* colorMap = GetMaterialColorMap(material); - if (colorMap) - xMaterial.colorMapName = std::string(colorMap->name); - - writer.AddMaterial(std::move(xMaterial)); - } - } -} - -void AssetDumperXModel::AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto surfCount = model->lodInfo[lod].numsurfs; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - XModelObject object; - object.name = "surf" + std::to_string(surfIndex); - - writer.AddObject(std::move(object)); - } -} - -void AssetDumperXModel::AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - if (!surfs) - return; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - for (auto vertexIndex = 0u; vertexIndex < surface.vertCount; vertexIndex++) - { - const auto& v = surface.verts0[vertexIndex]; - vec2_t uv{}; - vec3_t normalVec{}; - vec4_t color{}; - - Common::Vec2UnpackTexCoords(v.texCoord, &uv); - Common::Vec3UnpackUnitVec(v.normal, &normalVec); - Common::Vec4UnpackGfxColor(v.color, &color); - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz.x; - vertex.coordinates[1] = v.xyz.y; - vertex.coordinates[2] = v.xyz.z; - vertex.normal[0] = normalVec.x; - vertex.normal[1] = normalVec.y; - vertex.normal[2] = normalVec.z; - vertex.color[0] = color.x; - vertex.color[1] = color.y; - vertex.color[2] = color.z; - vertex.color[3] = color.w; - vertex.uv[0] = uv.x; - vertex.uv[1] = uv.y; - - writer.AddVertex(vertex); - } - } -} - -void AssetDumperXModel::AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - if (!surfs) - return; - - weightCollection.totalWeightCount = 0u; - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - if (surface.vertList) - { - weightCollection.totalWeightCount += surface.vertListCount; - } - - if (surface.vertInfo.vertsBlend) - { - weightCollection.totalWeightCount += surface.vertInfo.vertCount[0] * 1; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[1] * 2; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[2] * 3; - weightCollection.totalWeightCount += surface.vertInfo.vertCount[3] * 4; - } - } - - weightCollection.weights = std::make_unique(weightCollection.totalWeightCount); -} - -void AssetDumperXModel::AddXModelVertexBoneWeights(AbstractXModelWriter& writer, - const XModel* model, - const unsigned lod, - XModelVertexBoneWeightCollection& weightCollection) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - if (!surfs) - return; - - size_t weightOffset = 0u; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - auto handledVertices = 0u; - - if (surface.vertList) - { - for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) - { - const auto& vertList = surface.vertList[vertListIndex]; - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{static_cast(vertList.boneOffset / sizeof(DObjSkelMat)), 1.0f}; - - for (auto vertListVertexOffset = 0u; vertListVertexOffset < vertList.vertCount; vertListVertexOffset++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - handledVertices += vertList.vertCount; - } - } - - auto vertsBlendOffset = 0u; - if (surface.vertInfo.vertsBlend) - { - // 1 bone weight - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, 1.0f}; - - vertsBlendOffset += 1; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 1}); - } - - // 2 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[1]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneWeight0 = 1.0f - boneWeight1; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - - vertsBlendOffset += 3; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 2}); - } - - // 3 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[2]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - - vertsBlendOffset += 5; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 3}); - } - - // 4 bone weights - for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[3]; vertIndex++) - { - const auto* boneWeightOffset = &weightCollection.weights[weightOffset]; - const auto boneIndex0 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat)); - const auto boneIndex1 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat)); - const auto boneWeight1 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat)); - const auto boneWeight2 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = static_cast(surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat)); - const auto boneWeight3 = HalfFloat::ToFloat(surface.vertInfo.vertsBlend[vertsBlendOffset + 6]); - const auto boneWeight0 = 1.0f - boneWeight1 - boneWeight2 - boneWeight3; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex0, boneWeight0}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex1, boneWeight1}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex2, boneWeight2}; - weightCollection.weights[weightOffset++] = XModelBoneWeight{boneIndex3, boneWeight3}; - - vertsBlendOffset += 7; - - writer.AddVertexBoneWeights(XModelVertexBoneWeights{boneWeightOffset, 4}); - } - - handledVertices += surface.vertInfo.vertCount[0] + surface.vertInfo.vertCount[1] + surface.vertInfo.vertCount[2] + surface.vertInfo.vertCount[3]; - } - - for (; handledVertices < surface.vertCount; handledVertices++) - { - writer.AddVertexBoneWeights(XModelVertexBoneWeights{nullptr, 0}); - } - } -} - -void AssetDumperXModel::AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, const unsigned lod) -{ - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - const auto baseSurfIndex = model->lodInfo[lod].surfIndex; - - if (!surfs) - return; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - for (auto triIndex = 0u; triIndex < surface.triCount; triIndex++) - { - const auto& tri = surface.triIndices[triIndex]; - - XModelFace face{}; - face.vertexIndex[0] = tri[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri[2] + surface.baseVertIndex; - face.objectIndex = static_cast(surfIndex); - face.materialIndex = static_cast(materialMapper.GetDistinctPositionByInputPosition(surfIndex + baseSurfIndex)); - writer.AddFace(face); - } - } -} - -void AssetDumperXModel::DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, const unsigned lod) -{ - const auto* model = asset->Asset(); - - std::ostringstream ss; - ss << "model_export/" << model->name << "_lod" << lod << ".XMODEL_EXPORT"; - - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - const auto writer = XModelExportWriter::CreateWriterForVersion6(context.m_zone->m_game->GetShortName(), context.m_zone->m_name); - DistinctMapper materialMapper(model->numsurfs); - XModelVertexBoneWeightCollection boneWeightCollection; - AllocateXModelBoneWeights(model, lod, boneWeightCollection); - - AddXModelBones(context, *writer, model); - AddXModelMaterials(*writer, materialMapper, model); - AddXModelObjects(*writer, model, lod); - AddXModelVertices(*writer, model, lod); - AddXModelVertexBoneWeights(*writer, model, lod, boneWeightCollection); - AddXModelFaces(*writer, materialMapper, model, lod); - - writer->Write(*assetFile); -} - -void AssetDumperXModel::DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset) -{ - const auto* model = asset->Asset(); - for (auto currentLod = 0u; currentLod < model->numLods; currentLod++) - { - DumpXModelExportLod(context, asset, currentLod); - } -} - void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - DumpObj(context, asset); - break; - - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - DumpXModelExport(context, asset); - break; - - default: - assert(false); - break; - } + DumpXModelSurfs(context, asset); } diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h index a39bdaff..75218729 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.h @@ -2,37 +2,11 @@ #include "Dumping/AbstractAssetDumper.h" #include "Game/T6/T6.h" -#include "Model/Obj/ObjWriter.h" -#include "Model/XModel/AbstractXModelWriter.h" -#include "Utils/DistinctMapper.h" namespace T6 { class AssetDumperXModel final : public AbstractAssetDumper { - static GfxImage* GetMaterialColorMap(const Material* material); - static GfxImage* GetMaterialNormalMap(const Material* material); - static GfxImage* GetMaterialSpecularMap(const Material* material); - - static void AddObjMaterials(ObjWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddObjObjects(ObjWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void AddObjVertices(ObjWriter& writer, const XModel* model, unsigned lod); - static void AddObjFaces(ObjWriter& writer, const XModel* model, unsigned lod); - static void DumpObjLod(AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpObjMat(const AssetDumpingContext& context, XAssetInfo* asset); - static void DumpObj(AssetDumpingContext& context, XAssetInfo* asset); - - static void AddXModelBones(const AssetDumpingContext& context, AbstractXModelWriter& writer, const XModel* model); - static void AddXModelMaterials(AbstractXModelWriter& writer, DistinctMapper& materialMapper, const XModel* model); - static void AddXModelObjects(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AddXModelVertices(AbstractXModelWriter& writer, const XModel* model, unsigned lod); - static void AllocateXModelBoneWeights(const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void - AddXModelVertexBoneWeights(AbstractXModelWriter& writer, const XModel* model, unsigned lod, XModelVertexBoneWeightCollection& weightCollection); - static void AddXModelFaces(AbstractXModelWriter& writer, const DistinctMapper& materialMapper, const XModel* model, unsigned lod); - static void DumpXModelExportLod(const AssetDumpingContext& context, XAssetInfo* asset, unsigned lod); - static void DumpXModelExport(const AssetDumpingContext& context, XAssetInfo* asset); - protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp b/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp new file mode 100644 index 00000000..5f05331a --- /dev/null +++ b/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp @@ -0,0 +1,71 @@ +#include "JsonXModelWriter.h" + +#include "Game/T6/CommonT6.h" +#include "Game/T6/Json/JsonXModel.h" + +#include +#include + +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 diff --git a/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.h b/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.h new file mode 100644 index 00000000..f40f008d --- /dev/null +++ b/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Dumping/AssetDumpingContext.h" +#include "Game/T6/T6.h" + +#include + +namespace T6 +{ + void DumpXModelAsJson(std::ostream& stream, const XModel* xmodel, AssetDumpingContext& context); +} // namespace T6 diff --git a/src/ObjWriting/Model/Obj/ObjWriter.cpp b/src/ObjWriting/Model/Obj/ObjWriter.cpp deleted file mode 100644 index 2c8ad908..00000000 --- a/src/ObjWriting/Model/Obj/ObjWriter.cpp +++ /dev/null @@ -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(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(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(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(objectId) >= m_object_data.size()) - return; - - m_object_data[objectId].m_faces.push_back(face); -} - -void ObjWriter::GetObjObjectDataOffsets(std::vector& inputOffsets, std::vector& 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 inputOffsetsByObject; - std::vector 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(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"; - } -} diff --git a/src/ObjWriting/Model/Obj/ObjWriter.h b/src/ObjWriting/Model/Obj/ObjWriter.h deleted file mode 100644 index 157ffc87..00000000 --- a/src/ObjWriting/Model/Obj/ObjWriter.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "Model/Obj/ObjCommon.h" -#include "Utils/DistinctMapper.h" - -#include -#include - -class ObjWriter -{ -protected: - struct ObjObjectData - { - DistinctMapper m_vertices; - DistinctMapper m_normals; - DistinctMapper m_uvs; - std::vector 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 m_objects; - std::vector m_object_data; - std::vector m_materials; - - void GetObjObjectDataOffsets(std::vector& inputOffsets, std::vector& 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); -}; diff --git a/src/ObjWriting/Model/XModel/AbstractXModelWriter.cpp b/src/ObjWriting/Model/XModel/AbstractXModelWriter.cpp deleted file mode 100644 index 726cde89..00000000 --- a/src/ObjWriting/Model/XModel/AbstractXModelWriter.cpp +++ /dev/null @@ -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); -} diff --git a/src/ObjWriting/Model/XModel/AbstractXModelWriter.h b/src/ObjWriting/Model/XModel/AbstractXModelWriter.h deleted file mode 100644 index 277b5282..00000000 --- a/src/ObjWriting/Model/XModel/AbstractXModelWriter.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "Model/XModel/XModelCommon.h" - -#include - -class AbstractXModelWriter -{ -protected: - std::vector m_objects; - std::vector m_bones; - std::vector m_materials; - std::vector m_vertices; - std::vector m_vertex_bone_weights; - std::vector 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); -}; diff --git a/src/ObjWriting/Model/XModel/XModelExportWriter.cpp b/src/ObjWriting/Model/XModel/XModelExportWriter.cpp deleted file mode 100644 index c8b96395..00000000 --- a/src/ObjWriting/Model/XModel/XModelExportWriter.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "XModelExportWriter.h" - -#include "Math/Quaternion.h" - -#include -#include - -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::CreateWriterForVersion6(std::string gameName, std::string zoneName) -{ - return std::make_unique(std::move(gameName), std::move(zoneName)); -} diff --git a/src/ObjWriting/Model/XModel/XModelExportWriter.h b/src/ObjWriting/Model/XModel/XModelExportWriter.h deleted file mode 100644 index 5535f579..00000000 --- a/src/ObjWriting/Model/XModel/XModelExportWriter.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "AbstractXModelWriter.h" - -#include -#include - -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 CreateWriterForVersion6(std::string gameName, std::string zoneName); -}; diff --git a/src/ObjWriting/ObjWriting.h b/src/ObjWriting/ObjWriting.h index e69903e2..e8796b2b 100644 --- a/src/ObjWriting/ObjWriting.h +++ b/src/ObjWriting/ObjWriting.h @@ -20,14 +20,16 @@ public: enum class ModelOutputFormat_e { XMODEL_EXPORT, - OBJ + OBJ, + GLTF, + GLB }; bool Verbose = false; std::vector AssetTypesToHandleBitfield; ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; - ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::XMODEL_EXPORT; + ModelOutputFormat_e ModelOutputFormat = ModelOutputFormat_e::GLB; bool MenuLegacyMode = false; } Configuration; diff --git a/src/ObjWriting/Model/XModel/XModelBinWriter.cpp b/src/ObjWriting/XModel/Export/XModelBinWriter.cpp similarity index 100% rename from src/ObjWriting/Model/XModel/XModelBinWriter.cpp rename to src/ObjWriting/XModel/Export/XModelBinWriter.cpp diff --git a/src/ObjWriting/Model/XModel/XModelBinWriter.h b/src/ObjWriting/XModel/Export/XModelBinWriter.h similarity index 100% rename from src/ObjWriting/Model/XModel/XModelBinWriter.h rename to src/ObjWriting/XModel/Export/XModelBinWriter.h diff --git a/src/ObjWriting/XModel/Export/XModelExportWriter.cpp b/src/ObjWriting/XModel/Export/XModelExportWriter.cpp new file mode 100644 index 00000000..f65bc559 --- /dev/null +++ b/src/ObjWriting/XModel/Export/XModelExportWriter.cpp @@ -0,0 +1,232 @@ +#include "XModelExportWriter.h" + +#include "Math/Quaternion.h" + +#include +#include +#include + +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 CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName) + { + return std::make_unique(stream, std::move(gameName), std::move(zoneName)); + } +} // namespace xmodel_export diff --git a/src/ObjWriting/XModel/Export/XModelExportWriter.h b/src/ObjWriting/XModel/Export/XModelExportWriter.h new file mode 100644 index 00000000..b0d808f4 --- /dev/null +++ b/src/ObjWriting/XModel/Export/XModelExportWriter.h @@ -0,0 +1,11 @@ +#pragma once + +#include "XModel/XModelWriter.h" + +#include +#include + +namespace xmodel_export +{ + std::unique_ptr CreateWriterForVersion6(std::ostream& stream, std::string gameName, std::string zoneName); +} diff --git a/src/ObjWriting/XModel/Gltf/GltfBinOutput.cpp b/src/ObjWriting/XModel/Gltf/GltfBinOutput.cpp new file mode 100644 index 00000000..7bce3561 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfBinOutput.cpp @@ -0,0 +1,86 @@ +#include "GltfBinOutput.h" + +#include "Utils/Alignment.h" +#include "XModel/Gltf/GltfConstants.h" + +#include +#include + +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(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 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(static_cast(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(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(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); +} diff --git a/src/ObjWriting/XModel/Gltf/GltfBinOutput.h b/src/ObjWriting/XModel/Gltf/GltfBinOutput.h new file mode 100644 index 00000000..91eacc75 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfBinOutput.h @@ -0,0 +1,25 @@ +#pragma once + +#include "GltfOutput.h" + +#include + +namespace gltf +{ + class BinOutput final : public Output + { + public: + explicit BinOutput(std::ostream& stream); + + std::optional 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 diff --git a/src/ObjWriting/XModel/Gltf/GltfOutput.h b/src/ObjWriting/XModel/Gltf/GltfOutput.h new file mode 100644 index 00000000..8cc7a3da --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfOutput.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +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 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 diff --git a/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp b/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp new file mode 100644 index 00000000..f04838a3 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp @@ -0,0 +1,51 @@ +#include "GltfTextOutput.h" + +#include "Utils/Alignment.h" +#include "XModel/Gltf/GltfConstants.h" + +#include +#include + +#define LTC_NO_PROTOTYPES +#include + +using namespace gltf; + +TextOutput::TextOutput(std::ostream& stream) + : m_stream(stream) +{ +} + +std::optional TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const +{ + static constexpr auto URI_PREFIX_LENGTH = std::char_traits::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(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 +} diff --git a/src/ObjWriting/XModel/Gltf/GltfTextOutput.h b/src/ObjWriting/XModel/Gltf/GltfTextOutput.h new file mode 100644 index 00000000..d6b9d7b9 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfTextOutput.h @@ -0,0 +1,22 @@ +#pragma once + +#include "GltfOutput.h" + +#include + +namespace gltf +{ + class TextOutput final : public Output + { + public: + explicit TextOutput(std::ostream& stream); + + std::optional 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 diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp new file mode 100644 index 00000000..a48ba3f8 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp @@ -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 + +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 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(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 children; + for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++) + { + if (xmodel.m_bones[maybeChildIndex].parentIndex == static_cast(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& bufferData) const + { + const auto expectedBufferSize = GetExpectedBufferSize(xmodel); + bufferData.resize(expectedBufferSize); + + auto currentBufferOffset = 0u; + + float minPosition[3]{ + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + }; + float maxPosition[3]{ + -std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max(), + }; + + for (const auto& commonVertex : xmodel.m_vertices) + { + auto* vertex = reinterpret_cast(&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(&bufferData[currentBufferOffset]); + auto* weights = reinterpret_cast(&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(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(&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(&bufferData[currentBufferOffset]); + faceIndices[0] = static_cast(face.vertexIndex[2]); + faceIndices[1] = static_cast(face.vertexIndex[1]); + faceIndices[2] = static_cast(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& 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::CreateWriter(const Output* output, std::string gameName, std::string zoneName) +{ + return std::make_unique(output, std::move(gameName), std::move(zoneName)); +} diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.h b/src/ObjWriting/XModel/Gltf/GltfWriter.h new file mode 100644 index 00000000..0da26d71 --- /dev/null +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.h @@ -0,0 +1,24 @@ +#pragma once + +#include "GltfOutput.h" +#include "XModel/Gltf/JsonGltf.h" +#include "XModel/XModelWriter.h" + +#include +#include + +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 CreateWriter(const Output* output, std::string gameName, std::string zoneName); + }; +} // namespace gltf diff --git a/src/ObjWriting/XModel/Obj/ObjWriter.cpp b/src/ObjWriting/XModel/Obj/ObjWriter.cpp new file mode 100644 index 00000000..a883997c --- /dev/null +++ b/src/ObjWriting/XModel/Obj/ObjWriter.cpp @@ -0,0 +1,227 @@ +#include "ObjWriter.h" + +#include "Utils/DistinctMapper.h" +#include "XModel/Obj/ObjCommon.h" + +#include + +namespace +{ + struct ObjObjectData + { + DistinctMapper m_vertices; + DistinctMapper m_normals; + DistinctMapper 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 inputOffsetsByObject; + std::vector 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(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& inputOffsets, + std::vector& 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 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 CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName) + { + return std::make_unique(stream, std::move(mtlName), std::move(gameName), std::move(zoneName)); + } + + std::unique_ptr CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName) + { + return std::make_unique(stream, std::move(gameName), std::move(zoneName)); + } +} // namespace obj diff --git a/src/ObjWriting/XModel/Obj/ObjWriter.h b/src/ObjWriting/XModel/Obj/ObjWriter.h new file mode 100644 index 00000000..ef2092b7 --- /dev/null +++ b/src/ObjWriting/XModel/Obj/ObjWriter.h @@ -0,0 +1,12 @@ +#pragma once + +#include "XModel/XModelWriter.h" + +#include +#include + +namespace obj +{ + std::unique_ptr CreateObjWriter(std::ostream& stream, std::string mtlName, std::string gameName, std::string zoneName); + std::unique_ptr CreateMtlWriter(std::ostream& stream, std::string gameName, std::string zoneName); +} // namespace obj diff --git a/src/ObjWriting/XModel/XModelWriter.h b/src/ObjWriting/XModel/XModelWriter.h new file mode 100644 index 00000000..80324ed9 --- /dev/null +++ b/src/ObjWriting/XModel/XModelWriter.h @@ -0,0 +1,18 @@ +#pragma once + +#include "XModel/XModelCommon.h" + +#include + +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; +}; diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinker/UnlinkerArgs.cpp index 29276c66..43aac8f7 100644 --- a/src/Unlinker/UnlinkerArgs.cpp +++ b/src/Unlinker/UnlinkerArgs.cpp @@ -5,6 +5,7 @@ #include "ObjWriting.h" #include "Utils/Arguments/UsageInformation.h" #include "Utils/FileUtils.h" +#include "Utils/StringUtils.h" #include #include @@ -79,7 +80,7 @@ const CommandLineOption* const OPTION_IMAGE_FORMAT = const CommandLineOption* const OPTION_MODEL_FORMAT = CommandLineOption::Builder::Create() .WithLongName("model-format") - .WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ") + .WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, OBJ, GLTF, GLB") .WithParameter("modelFormatValue") .Build(); @@ -179,8 +180,7 @@ void UnlinkerArgs::SetVerbose(const bool isVerbose) bool UnlinkerArgs::SetImageDumpingMode() { auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT); - for (auto& c : specifiedValue) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(specifiedValue); if (specifiedValue == "dds") { @@ -202,8 +202,7 @@ bool UnlinkerArgs::SetImageDumpingMode() bool UnlinkerArgs::SetModelDumpingMode() { auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); - for (auto& c : specifiedValue) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(specifiedValue); if (specifiedValue == "xmodel_export") { @@ -217,6 +216,18 @@ bool UnlinkerArgs::SetModelDumpingMode() return true; } + if (specifiedValue == "gltf") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF; + return true; + } + + if (specifiedValue == "glb") + { + ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLB; + return true; + } + const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT); printf("Illegal value: \"%s\" is not a valid model output format. Use -? to see usage information.\n", originalValue.c_str()); return false; @@ -238,8 +249,7 @@ void UnlinkerArgs::ParseCommaSeparatedAssetTypeString(const std::string& input) size_t endPos; std::string lowerInput(input); - for (auto& c : lowerInput) - c = static_cast(tolower(c)); + utils::MakeStringLowerCase(lowerInput); while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos) { diff --git a/src/Utils/Math/Quaternion.h b/src/Utils/Math/Quaternion.h index 1090f96e..b69cc414 100644 --- a/src/Utils/Math/Quaternion.h +++ b/src/Utils/Math/Quaternion.h @@ -51,6 +51,135 @@ public: return Matrix(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((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(0.0001)) + { + return result; + } + + T l_inv = static_cast(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(0.0001)) + return; + + T inverseLength = static_cast(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 Quaternion32; diff --git a/src/Utils/Math/Vector.h b/src/Utils/Math/Vector.h index 2a188a1d..60092681 100644 --- a/src/Utils/Math/Vector.h +++ b/src/Utils/Math/Vector.h @@ -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) { assert(index < 2); @@ -55,6 +61,32 @@ public: { 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 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) { assert(index < 3); @@ -151,6 +189,34 @@ public: { 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 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) { assert(index < 4); @@ -267,6 +339,36 @@ public: { 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 Vector4f;