diff --git a/premake5.lua b/premake5.lua index 9e850d851..0ac4a6ac4 100644 --- a/premake5.lua +++ b/premake5.lua @@ -4,6 +4,7 @@ include "tools/scripts/linking.lua" include "tools/scripts/options.lua" include "tools/scripts/platform.lua" include "tools/scripts/version.lua" +include "tools/scripts/source_templating.lua" -- ================== -- Workspace diff --git a/raw/iw5/partclassification.csv b/raw/iw5/partclassification.csv new file mode 100644 index 000000000..66ac687f3 --- /dev/null +++ b/raw/iw5/partclassification.csv @@ -0,0 +1,21 @@ +J_Hip_RI,right_leg_upper +J_Hip_LE,left_leg_upper +J_Knee_RI,right_leg_lower +J_SpineUpper,torso_upper +J_Knee_LE,left_leg_lower +J_Ankle_RI,right_foot +J_Ankle_LE,left_foot +J_Clavicle_RI,torso_upper +J_Clavicle_LE,torso_upper +J_Shoulder_RI,right_arm_upper +J_Shoulder_LE,left_arm_upper +J_Neck,neck +J_Head,head +J_Elbow_RI,right_arm_lower +J_Elbow_LE,left_arm_lower +J_Wrist_RI,right_hand +J_Wrist_LE,left_hand +J_MainRoot,torso_lower +TAG_WEAPON_LEFT,gun +TAG_WEAPON_RIGHT,gun +J_Helmet,helmet diff --git a/raw/iw5/partclassification_mp.csv b/raw/iw5/partclassification_mp.csv new file mode 100644 index 000000000..87ee201f7 --- /dev/null +++ b/raw/iw5/partclassification_mp.csv @@ -0,0 +1,19 @@ +J_Hip_RI,right_leg_upper +J_Hip_LE,left_leg_upper +J_Knee_RI,right_leg_lower +J_SpineUpper,torso_lower +J_SpineLower,torso_lower +J_MainRoot,torso_lower +J_Knee_LE,left_leg_lower +J_Ankle_RI,right_foot +J_Ankle_LE,left_foot +J_Clavicle_RI,torso_upper +J_Clavicle_LE,torso_upper +J_Shoulder_RI,right_arm_upper +J_Shoulder_LE,left_arm_upper +J_Neck,neck +J_Head,head +J_Elbow_RI,right_arm_lower +J_Elbow_LE,left_arm_lower +J_Wrist_RI,right_hand +J_Wrist_LE,left_hand diff --git a/src/Common/Game/IW5/CommonIW5.h b/src/Common/Game/IW5/CommonIW5.h index 8347674cf..33fac6140 100644 --- a/src/Common/Game/IW5/CommonIW5.h +++ b/src/Common/Game/IW5/CommonIW5.h @@ -9,6 +9,21 @@ namespace IW5 public: static int StringTable_HashString(const char* str); + static constexpr uint32_t R_HashString(const char* str, uint32_t hash) + { + for (const auto* pos = str; *pos; pos++) + { + hash = 33 * hash ^ (*pos | 0x20); + } + + return hash; + } + + static constexpr uint32_t R_HashString(const char* string) + { + return R_HashString(string, 0u); + } + static PackedTexCoords Vec2PackTexCoords(const float (&in)[2]); static PackedUnitVec Vec3PackUnitVec(const float (&in)[3]); static GfxColor Vec4PackGfxColor(const float (&in)[4]); diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 5c7e004c4..2d45909da 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -177,11 +177,50 @@ namespace IW5 void* data; }; - typedef float vec2_t[2]; - typedef float vec3_t[3]; - typedef float vec4_t[4]; + union vec2_t + { + float v[2]; + + struct + { + float x; + float y; + }; + }; + + union vec3_t + { + struct + { + float x; + float y; + float z; + }; + + float v[3]; + }; + + union vec4_t + { + float v[4]; + + struct + { + float x; + float y; + float z; + float w; + }; + + struct + { + float r; + float g; + float b; + float a; + }; + }; - typedef tdef_align(16) uint16_t r_index16_t; typedef tdef_align(16) char raw_byte16; typedef tdef_align(16) float raw_float16; typedef tdef_align(128) unsigned int raw_uint128; @@ -220,8 +259,8 @@ namespace IW5 struct Bounds { - float midPoint[3]; - float halfSize[3]; + vec3_t midPoint; + vec3_t halfSize; }; struct cplane_s @@ -446,34 +485,15 @@ namespace IW5 unsigned int packed; }; - struct GfxQuantizedNoColorVertex - { - short xyz[3]; - short binormalSign; - PackedUnitVec normal; - PackedUnitVec tangent; - PackedTexCoords texCoord; - }; - union GfxColor { unsigned int packed; unsigned char array[4]; }; - struct GfxQuantizedVertex - { - short xyz[3]; - short binormalSign; - PackedUnitVec normal; - PackedUnitVec tangent; - PackedTexCoords texCoord; - GfxColor color; - }; - struct type_align(16) GfxPackedVertex { - float xyz[3]; + vec3_t xyz; float binormalSign; GfxColor color; PackedTexCoords texCoord; @@ -481,14 +501,6 @@ namespace IW5 PackedUnitVec tangent; }; - union GfxVertexUnion0 - { - GfxQuantizedNoColorVertex* quantizedNoColorVerts0; - GfxQuantizedVertex* quantizedVerts0; - GfxPackedVertex* packedVerts0; - void* verts0; - }; - struct XSurfaceCollisionAabb { unsigned short mins[3]; @@ -526,6 +538,13 @@ namespace IW5 XSurfaceCollisionTree* collisionTree; }; + struct XSurfaceTri + { + uint16_t i[3]; + }; + + typedef tdef_align(16) XSurfaceTri XSurfaceTri16; + struct XSurface { unsigned char tileMode; @@ -536,9 +555,9 @@ namespace IW5 uint16_t baseTriIndex; uint16_t baseVertIndex; float quantizeScale; - r_index16_t (*triIndices)[3]; + XSurfaceTri16* triIndices; XSurfaceVertexInfo vertInfo; - GfxVertexUnion0 verts0; + GfxPackedVertex* verts0; unsigned int vertListCount; XRigidVertList* vertList; int partBits[6]; @@ -554,8 +573,8 @@ namespace IW5 struct DObjAnimMat { - float quat[4]; - float trans[3]; + vec4_t quat; + vec3_t trans; float transWeight; }; @@ -567,7 +586,7 @@ namespace IW5 XModelSurfs* modelSurfs; int partBits[6]; XSurface* surfs; - char lod; + unsigned char lod; char smcBaseIndexPlusOne; char smcSubIndexMask; char smcBucket; @@ -596,6 +615,11 @@ namespace IW5 float radiusSquared; }; + struct XModelQuat + { + int16_t v[4]; + }; + struct XModel { const char* name; @@ -606,15 +630,15 @@ namespace IW5 unsigned int noScalePartBits[6]; ScriptString* boneNames; unsigned char* parentList; - short (*quats)[4]; - float (*trans)[3]; + XModelQuat* quats; + float* trans; unsigned char* partClassification; DObjAnimMat* baseMat; Material** materialHandles; XModelLodInfo lodInfo[4]; char maxLoadedLod; unsigned char numLods; - unsigned char collLod; + char collLod; unsigned char flags; XModelCollSurf_s* collSurfs; int numCollSurfs; @@ -651,6 +675,23 @@ namespace IW5 gcc_align(8) uint64_t packed; }; + enum MaterialGameFlags + { + MTL_GAMEFLAG_1 = 0x1, + MTL_GAMEFLAG_2 = 0x2, + MTL_GAMEFLAG_4 = 0x4, + MTL_GAMEFLAG_8 = 0x8, + MTL_GAMEFLAG_10 = 0x10, + MTL_GAMEFLAG_20 = 0x20, + MTL_GAMEFLAG_40 = 0x40, + MTL_GAMEFLAG_80 = 0x80, + MTL_GAMEFLAG_100 = 0x100, + MTL_GAMEFLAG_200 = 0x200, + MTL_GAMEFLAG_400 = 0x400, + MTL_GAMEFLAG_800 = 0x800, + MTL_GAMEFLAG_1000 = 0x1000, + }; + struct MaterialInfo { const char* name; @@ -713,13 +754,71 @@ namespace IW5 water_t* water; }; + enum TextureFilter + { + TEXTURE_FILTER_DISABLED = 0x0, + TEXTURE_FILTER_NEAREST = 0x1, + TEXTURE_FILTER_LINEAR = 0x2, + TEXTURE_FILTER_ANISO2X = 0x3, + TEXTURE_FILTER_ANISO4X = 0x4, + + TEXTURE_FILTER_COUNT + }; + + enum SamplerStateBitsMipMap_e + { + SAMPLER_MIPMAP_ENUM_DISABLED, + SAMPLER_MIPMAP_ENUM_NEAREST, + SAMPLER_MIPMAP_ENUM_LINEAR, + + SAMPLER_MIPMAP_ENUM_COUNT + }; + + enum SamplerStateBits_e + { + SAMPLER_FILTER_SHIFT = 0x0, + SAMPLER_FILTER_NEAREST = 0x1, + SAMPLER_FILTER_LINEAR = 0x2, + SAMPLER_FILTER_ANISO2X = 0x3, + SAMPLER_FILTER_ANISO4X = 0x4, + SAMPLER_FILTER_MASK = 0x7, + + SAMPLER_MIPMAP_SHIFT = 0x3, + SAMPLER_MIPMAP_DISABLED = 0x0, + SAMPLER_MIPMAP_NEAREST = 0x8, + SAMPLER_MIPMAP_LINEAR = 0x10, + SAMPLER_MIPMAP_COUNT = 0x3, + SAMPLER_MIPMAP_MASK = 0x18, + + SAMPLER_CLAMP_U_SHIFT = 0x5, + SAMPLER_CLAMP_V_SHIFT = 0x6, + SAMPLER_CLAMP_W_SHIFT = 0x7, + SAMPLER_CLAMP_U = 0x20, + SAMPLER_CLAMP_V = 0x40, + SAMPLER_CLAMP_W = 0x80, + SAMPLER_CLAMP_MASK = 0xE0, + }; + + struct MaterialTextureDefSamplerState + { + unsigned char filter : 3; + unsigned char mipMap : 2; + unsigned char clampU : 1; + unsigned char clampV : 1; + unsigned char clampW : 1; + }; + +#ifndef __zonecodegenerator + static_assert(sizeof(MaterialTextureDefSamplerState) == 1u); +#endif + struct MaterialTextureDef { unsigned int nameHash; char nameStart; char nameEnd; - unsigned char samplerState; - unsigned char semantic; + MaterialTextureDefSamplerState samplerState; + unsigned char semantic; // TextureSemantic MaterialTextureDefInfo u; }; @@ -727,18 +826,161 @@ namespace IW5 { unsigned int nameHash; char name[12]; - float literal[4]; + vec4_t literal; + }; + + enum GfxBlend + { + GFXS_BLEND_DISABLED = 0x0, + GFXS_BLEND_ZERO = 0x1, + GFXS_BLEND_ONE = 0x2, + GFXS_BLEND_SRCCOLOR = 0x3, + GFXS_BLEND_INVSRCCOLOR = 0x4, + GFXS_BLEND_SRCALPHA = 0x5, + GFXS_BLEND_INVSRCALPHA = 0x6, + GFXS_BLEND_DESTALPHA = 0x7, + GFXS_BLEND_INVDESTALPHA = 0x8, + GFXS_BLEND_DESTCOLOR = 0x9, + GFXS_BLEND_INVDESTCOLOR = 0xA, + + GFXS_BLEND_COUNT + }; + + enum GfxBlendOp + { + GFXS_BLENDOP_DISABLED = 0x0, + GFXS_BLENDOP_ADD = 0x1, + GFXS_BLENDOP_SUBTRACT = 0x2, + GFXS_BLENDOP_REVSUBTRACT = 0x3, + GFXS_BLENDOP_MIN = 0x4, + GFXS_BLENDOP_MAX = 0x5, + + GFXS_BLENDOP_COUNT + }; + + enum GfxAlphaTest_e + { + GFXS_ALPHA_TEST_GT_0 = 1, + GFXS_ALPHA_TEST_LT_128 = 2, + GFXS_ALPHA_TEST_GE_128 = 3, + + GFXS_ALPHA_TEST_COUNT }; + enum GfxCullFace_e + { + GFXS_CULL_NONE = 1, + GFXS_CULL_BACK = 2, + GFXS_CULL_FRONT = 3, + }; + + enum GfxDepthTest_e + { + GFXS_DEPTHTEST_ALWAYS = 0, + GFXS_DEPTHTEST_LESS = 1, + GFXS_DEPTHTEST_EQUAL = 2, + GFXS_DEPTHTEST_LESSEQUAL = 3 + }; + + enum GfxPolygonOffset_e + { + GFXS_POLYGON_OFFSET_0 = 0, + GFXS_POLYGON_OFFSET_1 = 1, + GFXS_POLYGON_OFFSET_2 = 2, + GFXS_POLYGON_OFFSET_SHADOWMAP = 3 + }; + + enum GfxStencilOp + { + GFXS_STENCILOP_KEEP = 0x0, + GFXS_STENCILOP_ZERO = 0x1, + GFXS_STENCILOP_REPLACE = 0x2, + GFXS_STENCILOP_INCRSAT = 0x3, + GFXS_STENCILOP_DECRSAT = 0x4, + GFXS_STENCILOP_INVERT = 0x5, + GFXS_STENCILOP_INCR = 0x6, + GFXS_STENCILOP_DECR = 0x7 + }; + + enum GfxStencilFunc + { + GFXS_STENCILFUNC_NEVER = 0x0, + GFXS_STENCILFUNC_LESS = 0x1, + GFXS_STENCILFUNC_EQUAL = 0x2, + GFXS_STENCILFUNC_LESSEQUAL = 0x3, + GFXS_STENCILFUNC_GREATER = 0x4, + GFXS_STENCILFUNC_NOTEQUAL = 0x5, + GFXS_STENCILFUNC_GREATEREQUAL = 0x6, + GFXS_STENCILFUNC_ALWAYS = 0x7 + }; + + struct GfxStateBitsLoadBitsStructured + { + // Byte 0 + unsigned int srcBlendRgb : 4; // 0-3 + unsigned int dstBlendRgb : 4; // 4-7 + unsigned int blendOpRgb : 3; // 8-10 + unsigned int alphaTestDisabled : 1; // 11 + unsigned int alphaTest : 2; // 12-13 + unsigned int cullFace : 2; // 14-15 + unsigned int srcBlendAlpha : 4; // 16-19 + unsigned int dstBlendAlpha : 4; // 20-23 + unsigned int blendOpAlpha : 3; // 24-26 + unsigned int colorWriteRgb : 1; // 27 + unsigned int colorWriteAlpha : 1; // 28 + unsigned int unused0 : 1; // 29 + unsigned int gammaWrite : 1; // 30 + unsigned int polymodeLine : 1; // 31 + + // Byte 1 + unsigned int depthWrite : 1; // 0 + unsigned int depthTestDisabled : 1; // 1 + unsigned int depthTest : 2; // 2-3 + unsigned int polygonOffset : 2; // 4-5 + unsigned int stencilFrontEnabled : 1; // 6 + unsigned int stencilBackEnabled : 1; // 7 + unsigned int stencilFrontPass : 3; // 8-10 + unsigned int stencilFrontFail : 3; // 11-13 + unsigned int stencilFrontZFail : 3; // 14-16 + unsigned int stencilFrontFunc : 3; // 17-19 + unsigned int stencilBackPass : 3; // 20-22 + unsigned int stencilBackFail : 3; // 23-25 + unsigned int stencilBackZFail : 3; // 26-28 + unsigned int stencilBackFunc : 3; // 29-31 + }; + + union GfxStateBitsLoadBits + { + unsigned int raw[2]; + GfxStateBitsLoadBitsStructured structured; + }; + +#ifndef __zonecodegenerator + static_assert(sizeof(GfxStateBitsLoadBits) == 8); + static_assert(sizeof(GfxStateBitsLoadBitsStructured) == 8); +#endif + struct GfxStateBits { - unsigned int loadBits[2]; + GfxStateBitsLoadBits loadBits; + }; + + enum GfxCameraRegionType + { + CAMERA_REGION_LIT_OPAQUE = 0x0, + CAMERA_REGION_LIT_TRANS = 0x1, + CAMERA_REGION_EMISSIVE = 0x2, + CAMERA_REGION_DEPTH_HACK = 0x3, + CAMERA_REGION_LIGHT_MAP_OPAQUE = 0x4, + + CAMERA_REGION_COUNT, + CAMERA_REGION_NONE = CAMERA_REGION_COUNT, }; struct Material { MaterialInfo info; - unsigned char stateBitsEntry[54]; + char stateBitsEntry[54]; unsigned char textureCount; unsigned char constantCount; unsigned char stateBitsCount; diff --git a/src/Common/Game/T5/T5_Assets.h b/src/Common/Game/T5/T5_Assets.h index 4c6de8114..f055f58c9 100644 --- a/src/Common/Game/T5/T5_Assets.h +++ b/src/Common/Game/T5/T5_Assets.h @@ -208,6 +208,8 @@ namespace T5 typedef char cbrushedge_t; typedef tdef_align(128) unsigned int raw_uint128; + typedef uint16_t ScriptString; + struct PhysPreset { const char* name; @@ -459,8 +461,8 @@ namespace T5 struct DObjAnimMat { - float quat[4]; - float trans[3]; + vec4_t quat; + vec3_t trans; float transWeight; }; @@ -490,7 +492,7 @@ namespace T5 struct type_align(16) GfxPackedVertex { - float xyz[3]; + vec3_t xyz; float binormalSign; GfxColor color; PackedTexCoords texCoord; @@ -535,7 +537,12 @@ namespace T5 XSurfaceCollisionTree* collisionTree; }; - typedef tdef_align(16) uint16_t r_index16_t; + struct XSurfaceTri + { + uint16_t i[3]; + }; + + typedef tdef_align(16) XSurfaceTri XSurfaceTri16; struct XSurface { @@ -546,7 +553,7 @@ namespace T5 uint16_t triCount; uint16_t baseTriIndex; uint16_t baseVertIndex; - r_index16_t (*triIndices)[3]; + XSurfaceTri16* triIndices; XSurfaceVertexInfo vertInfo; GfxPackedVertex* verts0; void /*IDirect3DVertexBuffer9*/* vb0; @@ -587,8 +594,8 @@ namespace T5 struct XBoneInfo { - float bounds[2][3]; - float offset[3]; + vec3_t bounds[2]; + vec3_t offset; float radiusSquared; char collmap; }; @@ -657,6 +664,14 @@ namespace T5 PhysGeomList* geomList; }; + enum XModelLodRampType : unsigned char + { + XMODEL_LOD_RAMP_RIGID = 0x0, + XMODEL_LOD_RAMP_SKINNED = 0x1, + + XMODEL_LOD_RAMP_COUNT + }; + struct XModelQuat { int16_t v[4]; @@ -668,12 +683,12 @@ namespace T5 unsigned char numBones; unsigned char numRootBones; unsigned char numsurfs; - char lodRampType; - uint16_t* boneNames; - char* parentList; + XModelLodRampType lodRampType; + ScriptString* boneNames; + unsigned char* parentList; XModelQuat* quats; float* trans; - char* partClassification; + unsigned char* partClassification; DObjAnimMat* baseMat; XSurface* surfs; Material** materialHandles; @@ -684,13 +699,13 @@ namespace T5 int contents; XBoneInfo* boneInfo; float radius; - float mins[3]; - float maxs[3]; + vec3_t mins; + vec3_t maxs; uint16_t numLods; - uint16_t collLod; + int16_t collLod; XModelStreamInfo streamInfo; int memUsage; - int flags; + unsigned int flags; bool bad; PhysPreset* physPreset; unsigned char numCollmaps; @@ -775,7 +790,7 @@ namespace T5 char nameStart; char nameEnd; char samplerState; - char semantic; + unsigned char semantic; // TextureSemantic char isMatureContent; char pad[3]; MaterialTextureDefInfo u; diff --git a/src/Common/Game/T6/CommonT6.h b/src/Common/Game/T6/CommonT6.h index 98e0cc6e1..9f7fbb2b2 100644 --- a/src/Common/Game/T6/CommonT6.h +++ b/src/Common/Game/T6/CommonT6.h @@ -21,6 +21,11 @@ namespace T6 return hash; } + static constexpr uint32_t R_HashString(const char* string) + { + return R_HashString(string, 0u); + } + static constexpr uint32_t SND_HashName(const char* str) { if (!str || !*str) diff --git a/src/Common/Utils/Pack.cpp b/src/Common/Utils/Pack.cpp index 2d0f49204..dec622e9e 100644 --- a/src/Common/Utils/Pack.cpp +++ b/src/Common/Utils/Pack.cpp @@ -4,6 +4,7 @@ #include #include +#include #include union PackUtil32 @@ -17,6 +18,8 @@ union PackUtil32 namespace pack32 { + typedef float pvec3[3]; + uint32_t Vec2PackTexCoordsUV(const float (&in)[2]) { return static_cast(HalfFloat::ToHalf(in[1])) << 16 | HalfFloat::ToHalf(in[0]); @@ -27,11 +30,57 @@ namespace pack32 return static_cast(HalfFloat::ToHalf(in[0])) << 16 | HalfFloat::ToHalf(in[1]); } + float Vec3_Normalize(pvec3& vector) + { + float length = std::sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); + if (-length >= 0.0f) + length = 1.0f; + const auto lengthInv = 1.0f / length; + vector[0] = lengthInv * vector[0]; + vector[1] = lengthInv * vector[1]; + vector[2] = lengthInv * vector[2]; + return length; + } + uint32_t Vec3PackUnitVecScaleBased(const float (&in)[3]) { - // TODO: Implement - assert(false); - return 0; + PackUtil32 testEncoding{}; + float normalized[3]{in[0], in[1], in[2]}; + float decoded[3]; + + Vec3_Normalize(normalized); + uint32_t out = 0u; + auto bestDirError = 3.4028235e38f; + auto bestLenError = 3.4028235e38f; + testEncoding.uc[3] = 0u; + do + { + const auto encodeScale = 32385.0f / (static_cast(testEncoding.uc[3]) - -192.0f); + testEncoding.c[0] = static_cast(normalized[0] * encodeScale + 127.5f); + testEncoding.c[1] = static_cast(normalized[1] * encodeScale + 127.5f); + testEncoding.c[2] = static_cast(normalized[2] * encodeScale + 127.5f); + const auto decodeScale = (static_cast(testEncoding.uc[3]) - -192.0f) / 32385.0f; + decoded[0] = (static_cast(testEncoding.uc[0]) - 127.0f) * decodeScale; + decoded[1] = (static_cast(testEncoding.uc[1]) - 127.0f) * decodeScale; + decoded[2] = (static_cast(testEncoding.uc[2]) - 127.0f) * decodeScale; + const auto v2 = Vec3_Normalize(decoded) - 1.0f; + const auto lenError = std::abs(v2); + if (lenError < 0.001f) + { + const auto dirError = std::abs(decoded[0] * normalized[0] + decoded[1] * normalized[1] + decoded[2] * normalized[2] - 1.0f); + if (bestDirError > dirError || bestDirError <= dirError && bestLenError > lenError) + { + bestDirError = dirError; + bestLenError = lenError; + out = testEncoding.u; + if (lenError + dirError == 0.0f) + return out; + } + } + ++testEncoding.c[3]; + } while (testEncoding.c[3]); + + return out; } uint32_t Vec3PackUnitVecThirdBased(const float (&in)[3]) diff --git a/src/Crypto/Impl/Base64.cpp b/src/Crypto/Impl/Base64.cpp new file mode 100644 index 000000000..f4400d6ac --- /dev/null +++ b/src/Crypto/Impl/Base64.cpp @@ -0,0 +1,49 @@ +#include "Base64.h" + +#define LTC_NO_PROTOTYPES +#include + +namespace base64 +{ + std::string EncodeBase64(const void* inputData, const size_t inputLength) + { + const auto base64BufferSize = GetBase64EncodeOutputLength(inputLength); + + std::string output(base64BufferSize, '\0'); + const auto outLength = base64BufferSize + 1u; + + const auto result = EncodeBase64(inputData, inputLength, output.data(), outLength); + assert(result); + + return output; + } + + bool EncodeBase64(const void* inputData, const size_t inputLength, void* outputBuffer, const size_t outputBufferSize) + { + unsigned long outLength = outputBufferSize; + const auto result = base64_encode(static_cast(inputData), inputLength, static_cast(outputBuffer), &outLength); + return result == CRYPT_OK; + } + + size_t GetBase64EncodeOutputLength(const size_t inputLength) + { + return 4u * ((inputLength + 2u) / 3u); + } + + size_t DecodeBase64(const void* base64Data, const size_t inputLength, void* outputBuffer, const size_t outputBufferSize) + { + unsigned long outLength = GetBase64DecodeOutputLength(inputLength); + if (outLength > outputBufferSize) + return 0u; + + const auto result = base64_decode(static_cast(base64Data), inputLength, static_cast(outputBuffer), &outLength); + assert(result == CRYPT_OK); + + return static_cast(outLength); + } + + size_t GetBase64DecodeOutputLength(const size_t inputLength) + { + return inputLength / 4u; + } +} // namespace base64 diff --git a/src/Crypto/Impl/Base64.h b/src/Crypto/Impl/Base64.h new file mode 100644 index 000000000..1e1ec51da --- /dev/null +++ b/src/Crypto/Impl/Base64.h @@ -0,0 +1,12 @@ +#pragma once +#include + +namespace base64 +{ + std::string EncodeBase64(const void* inputData, size_t inputLength); + bool EncodeBase64(const void* inputData, size_t inputLength, void* outputBuffer, size_t outputBufferSize); + size_t GetBase64EncodeOutputLength(size_t inputLength); + + size_t DecodeBase64(const void* base64Data, size_t inputLength, void* outputBuffer, size_t outputBufferSize); + size_t GetBase64DecodeOutputLength(size_t inputLength); +} // namespace base64 diff --git a/src/ObjCommon/Game/IW5/Material/JsonMaterial.h b/src/ObjCommon/Game/IW5/Material/JsonMaterial.h new file mode 100644 index 000000000..b6dd0d25a --- /dev/null +++ b/src/ObjCommon/Game/IW5/Material/JsonMaterial.h @@ -0,0 +1,391 @@ +#pragma once + +#include "Game/IW5/IW5.h" + +#include "Json/JsonExtension.h" +#include +#include +#include +#include +#include + +namespace IW5 +{ + NLOHMANN_JSON_SERIALIZE_ENUM(GfxStencilOp, + { + {GFXS_STENCILOP_KEEP, "keep" }, + {GFXS_STENCILOP_ZERO, "zero" }, + {GFXS_STENCILOP_REPLACE, "replace"}, + {GFXS_STENCILOP_INCRSAT, "incrsat"}, + {GFXS_STENCILOP_DECRSAT, "decrsat"}, + {GFXS_STENCILOP_INVERT, "invert" }, + {GFXS_STENCILOP_INCR, "incr" }, + {GFXS_STENCILOP_DECR, "decr" }, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(GfxStencilFunc, + { + {GFXS_STENCILFUNC_NEVER, "never" }, + {GFXS_STENCILFUNC_LESS, "less" }, + {GFXS_STENCILFUNC_EQUAL, "equal" }, + {GFXS_STENCILFUNC_LESSEQUAL, "lessequal" }, + {GFXS_STENCILFUNC_GREATER, "greater" }, + {GFXS_STENCILFUNC_NOTEQUAL, "notequal" }, + {GFXS_STENCILFUNC_GREATEREQUAL, "greaterequal"}, + {GFXS_STENCILFUNC_ALWAYS, "always" }, + }); + + class JsonStencil + { + public: + GfxStencilOp pass; + GfxStencilOp fail; + GfxStencilOp zfail; + GfxStencilFunc func; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonStencil, pass, fail, zfail, func); + + enum class JsonAlphaTest + { + INVALID, + DISABLED, + GT0, + LT128, + GE128 + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(JsonAlphaTest, + { + {JsonAlphaTest::INVALID, nullptr }, + {JsonAlphaTest::DISABLED, "disabled"}, + {JsonAlphaTest::GT0, "gt0" }, + {JsonAlphaTest::LT128, "lt128" }, + {JsonAlphaTest::GE128, "ge128" } + }); + + enum class JsonCullFace + { + INVALID, + NONE, + BACK, + FRONT + }; + + NLOHMANN_JSON_SERIALIZE_ENUM( + JsonCullFace, + { + {JsonCullFace::INVALID, nullptr}, + {JsonCullFace::NONE, "none" }, + {JsonCullFace::BACK, "back" }, + {JsonCullFace::FRONT, "front"} + }); + + enum class JsonDepthTest + { + INVALID, + DISABLED, + ALWAYS, + LESS, + EQUAL, + LESS_EQUAL + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(JsonDepthTest, + { + {JsonDepthTest::INVALID, nullptr }, + {JsonDepthTest::DISABLED, "disabled" }, + {JsonDepthTest::ALWAYS, "always" }, + {JsonDepthTest::LESS, "less" }, + {JsonDepthTest::EQUAL, "equal" }, + {JsonDepthTest::LESS_EQUAL, "less_equal"} + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(GfxBlend, + { + {GFXS_BLEND_DISABLED, "disabled" }, + {GFXS_BLEND_ZERO, "zero" }, + {GFXS_BLEND_ONE, "one" }, + {GFXS_BLEND_SRCCOLOR, "srccolor" }, + {GFXS_BLEND_INVSRCCOLOR, "invsrccolor" }, + {GFXS_BLEND_SRCALPHA, "srcalpha" }, + {GFXS_BLEND_INVSRCALPHA, "invsrcalpha" }, + {GFXS_BLEND_DESTALPHA, "destalpha" }, + {GFXS_BLEND_INVDESTALPHA, "invdestalpha"}, + {GFXS_BLEND_DESTCOLOR, "destcolor" }, + {GFXS_BLEND_INVDESTCOLOR, "invdestcolor"}, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(GfxBlendOp, + { + {GFXS_BLENDOP_DISABLED, "disabled" }, + {GFXS_BLENDOP_ADD, "add" }, + {GFXS_BLENDOP_SUBTRACT, "subtract" }, + {GFXS_BLENDOP_REVSUBTRACT, "revsubtract"}, + {GFXS_BLENDOP_MIN, "min" }, + {GFXS_BLENDOP_MAX, "max" }, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(GfxPolygonOffset_e, + { + {GFXS_POLYGON_OFFSET_0, "offset0" }, + {GFXS_POLYGON_OFFSET_1, "offset1" }, + {GFXS_POLYGON_OFFSET_2, "offset2" }, + {GFXS_POLYGON_OFFSET_SHADOWMAP, "offsetShadowmap"}, + }); + + class JsonStateBitsTableEntry + { + public: + GfxBlend srcBlendRgb; + GfxBlend dstBlendRgb; + GfxBlendOp blendOpRgb; + JsonAlphaTest alphaTest; + JsonCullFace cullFace; + GfxBlend srcBlendAlpha; + GfxBlend dstBlendAlpha; + GfxBlendOp blendOpAlpha; + bool colorWriteRgb; + bool colorWriteAlpha; + bool gammaWrite; + bool polymodeLine; + bool depthWrite; + JsonDepthTest depthTest; + GfxPolygonOffset_e polygonOffset; + std::optional stencilFront; + std::optional stencilBack; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonStateBitsTableEntry, + srcBlendRgb, + dstBlendRgb, + blendOpRgb, + alphaTest, + cullFace, + srcBlendAlpha, + dstBlendAlpha, + blendOpAlpha, + colorWriteRgb, + colorWriteAlpha, + polymodeLine, + depthWrite, + depthWrite, + depthTest, + polygonOffset, + stencilFront, + stencilBack); + + class JsonConstant + { + public: + std::optional name; + std::optional nameFragment; + std::optional nameHash; + std::vector literal; + }; + + inline void to_json(nlohmann::json& out, const JsonConstant& in) + { + if (in.name.has_value()) + { + optional_to_json(out, "name", in.name); + } + else + { + optional_to_json(out, "nameFragment", in.nameFragment); + optional_to_json(out, "nameHash", in.nameHash); + } + + out["literal"] = in.literal; + } + + inline void from_json(const nlohmann::json& in, JsonConstant& out) + { + optional_from_json(in, "name", out.name); + optional_from_json(in, "nameFragment", out.nameFragment); + optional_from_json(in, "nameHash", out.nameHash); + in.at("literal").get_to(out.literal); + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(TextureFilter, + { + {TEXTURE_FILTER_DISABLED, "disabled"}, + {TEXTURE_FILTER_NEAREST, "nearest" }, + {TEXTURE_FILTER_LINEAR, "linear" }, + {TEXTURE_FILTER_ANISO2X, "aniso2x" }, + {TEXTURE_FILTER_ANISO4X, "aniso4x" }, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(SamplerStateBitsMipMap_e, + { + {SAMPLER_MIPMAP_ENUM_DISABLED, "disabled"}, + {SAMPLER_MIPMAP_ENUM_NEAREST, "nearest" }, + {SAMPLER_MIPMAP_ENUM_LINEAR, "linear" }, + }); + + class JsonSamplerState + { + public: + TextureFilter filter; + SamplerStateBitsMipMap_e mipMap; + bool clampU; + bool clampV; + bool clampW; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSamplerState, filter, mipMap, clampU, clampV, clampW); + + class JsonComplex + { + public: + float real; + float imag; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonComplex, real, imag); + + class JsonWater + { + public: + float floatTime; + int m; + int n; + std::string h0; + std::string wTerm; + float lx; + float lz; + float gravity; + float windvel; + std::array winddir; + float amplitude; + std::array codeConstant; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonWater, floatTime, m, n, h0, wTerm, lx, lz, gravity, windvel, winddir, amplitude, codeConstant); + + NLOHMANN_JSON_SERIALIZE_ENUM(TextureSemantic, + { + {TS_2D, "2D" }, + {TS_FUNCTION, "function" }, + {TS_COLOR_MAP, "colorMap" }, + {TS_DETAIL_MAP, "detailMap" }, + {TS_UNUSED_2, "unused2" }, + {TS_NORMAL_MAP, "normalMap" }, + {TS_UNUSED_3, "unused3" }, + {TS_UNUSED_4, "unused4" }, + {TS_SPECULAR_MAP, "specularMap" }, + {TS_UNUSED_5, "unused5" }, + {TS_UNUSED_6, "unused6" }, + {TS_WATER_MAP, "waterMap" }, + {TS_DISPLACEMENT_MAP, "displacementMap"}, + }); + + class JsonTexture + { + public: + std::optional name; + std::optional nameHash; + std::optional nameStart; + std::optional nameEnd; + TextureSemantic semantic; + JsonSamplerState samplerState; + std::string image; + std::optional water; + }; + + inline void to_json(nlohmann::json& out, const JsonTexture& in) + { + if (in.name.has_value()) + { + optional_to_json(out, "name", in.name); + } + else + { + optional_to_json(out, "nameHash", in.nameHash); + optional_to_json(out, "nameStart", in.nameStart); + optional_to_json(out, "nameEnd", in.nameEnd); + } + + out["semantic"] = in.semantic; + out["samplerState"] = in.samplerState; + out["image"] = in.image; + optional_to_json(out, "water", in.water); + } + + inline void from_json(const nlohmann::json& in, JsonTexture& out) + { + optional_from_json(in, "name", out.name); + optional_from_json(in, "nameHash", out.nameHash); + optional_from_json(in, "nameStart", out.nameStart); + optional_from_json(in, "nameEnd", out.nameEnd); + in.at("semantic").get_to(out.semantic); + in.at("samplerState").get_to(out.samplerState); + in.at("image").get_to(out.image); + optional_from_json(in, "water", out.water); + }; + + class JsonTextureAtlas + { + public: + uint8_t rows; + uint8_t columns; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonTextureAtlas, rows, columns); + + NLOHMANN_JSON_SERIALIZE_ENUM(MaterialGameFlags, + { + {MTL_GAMEFLAG_1, "1" }, + {MTL_GAMEFLAG_2, "2" }, + {MTL_GAMEFLAG_4, "4" }, + {MTL_GAMEFLAG_8, "8" }, + {MTL_GAMEFLAG_10, "10" }, + {MTL_GAMEFLAG_20, "20" }, + {MTL_GAMEFLAG_40, "40" }, + {MTL_GAMEFLAG_80, "80" }, + {MTL_GAMEFLAG_100, "100" }, + {MTL_GAMEFLAG_200, "200" }, + {MTL_GAMEFLAG_400, "400" }, + {MTL_GAMEFLAG_800, "800" }, + {MTL_GAMEFLAG_1000, "1000"}, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(GfxCameraRegionType, + { + {CAMERA_REGION_LIT_OPAQUE, "litOpaque" }, + {CAMERA_REGION_LIT_TRANS, "litTrans" }, + {CAMERA_REGION_EMISSIVE, "emissive" }, + {CAMERA_REGION_DEPTH_HACK, "depthHack" }, + {CAMERA_REGION_LIGHT_MAP_OPAQUE, "lightMapOpaque"}, + {CAMERA_REGION_NONE, "none" }, + }); + + class JsonMaterial + { + public: + std::vector gameFlags; + unsigned sortKey; + std::optional textureAtlas; + unsigned surfaceTypeBits; + std::vector stateBitsEntry; + unsigned stateFlags; + GfxCameraRegionType cameraRegion; + std::string techniqueSet; + std::vector textures; + std::vector constants; + std::vector stateBits; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMaterial, + gameFlags, + sortKey, + textureAtlas, + surfaceTypeBits, + stateBitsEntry, + stateFlags, + cameraRegion, + techniqueSet, + textures, + constants, + stateBits); +} // namespace IW5 diff --git a/src/ObjCommon/Game/IW5/XModel/JsonXModelIW5.h b/src/ObjCommon/Game/IW5/XModel/JsonXModelIW5.h new file mode 100644 index 000000000..ce7424fff --- /dev/null +++ b/src/ObjCommon/Game/IW5/XModel/JsonXModelIW5.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Json/JsonCommon.h" +#include +#include +#include +#include +#include + +namespace IW5 +{ + class JsonXModelLod + { + public: + std::string file; + float distance; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file, distance); + + class JsonXModel + { + public: + std::vector lods; + std::optional collLod; + std::optional physPreset; + std::optional physCollmap; + uint8_t flags; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physCollmap, flags); +} // namespace IW5 diff --git a/src/ObjCommon/Game/IW5/XModel/XModelConstantsIW5.h b/src/ObjCommon/Game/IW5/XModel/XModelConstantsIW5.h new file mode 100644 index 000000000..a0a129a3e --- /dev/null +++ b/src/ObjCommon/Game/IW5/XModel/XModelConstantsIW5.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Game/IW5/IW5.h" + +namespace IW5 +{ + inline const char* HITLOC_NAMES[]{ + // clang-format off + "none", + "helmet", + "head", + "neck", + "torso_upper", + "torso_lower", + "right_arm_upper", + "left_arm_upper", + "right_arm_lower", + "left_arm_lower", + "right_hand", + "left_hand", + "right_leg_upper", + "left_leg_upper", + "right_leg_lower", + "left_leg_lower", + "right_foot", + "left_foot", + "gun", + "shield", + // clang-format on + }; + static_assert(std::extent_v == HITLOC_COUNT); +} // namespace IW5 diff --git a/src/ObjCommon/Game/T5/XModel/JsonXModelT5.h b/src/ObjCommon/Game/T5/XModel/JsonXModelT5.h new file mode 100644 index 000000000..5b52ca9a1 --- /dev/null +++ b/src/ObjCommon/Game/T5/XModel/JsonXModelT5.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Json/JsonCommon.h" +#include +#include +#include +#include + +namespace T5 +{ + class JsonXModelLod + { + public: + std::string file; + float distance; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModelLod, file, distance); + + class JsonXModel + { + public: + std::vector lods; + std::optional collLod; + std::optional physPreset; + std::optional physConstraints; + unsigned flags; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonXModel, lods, collLod, physPreset, physConstraints, flags); +} // namespace T5 diff --git a/src/ObjCommon/Game/T5/XModel/XModelConstantsT5.h b/src/ObjCommon/Game/T5/XModel/XModelConstantsT5.h new file mode 100644 index 000000000..0481cd276 --- /dev/null +++ b/src/ObjCommon/Game/T5/XModel/XModelConstantsT5.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Game/T5/T5.h" + +namespace T5 +{ + inline const char* HITLOC_NAMES[]{ + // clang-format off + "none", + "helmet", + "head", + "neck", + "torso_upper", + "torso_lower", + "right_arm_upper", + "left_arm_upper", + "right_arm_lower", + "left_arm_lower", + "right_hand", + "left_hand", + "right_leg_upper", + "left_leg_upper", + "right_leg_lower", + "left_leg_lower", + "right_foot", + "left_foot", + "gun", + // clang-format on + }; + static_assert(std::extent_v == HITLOC_COUNT); +} // namespace T5 diff --git a/src/ObjCommon/Game/T6/Json/JsonXModel.h b/src/ObjCommon/Game/T6/XModel/JsonXModelT6.h similarity index 90% rename from src/ObjCommon/Game/T6/Json/JsonXModel.h rename to src/ObjCommon/Game/T6/XModel/JsonXModelT6.h index 26e0c2f57..79e6eb066 100644 --- a/src/ObjCommon/Game/T6/Json/JsonXModel.h +++ b/src/ObjCommon/Game/T6/XModel/JsonXModelT6.h @@ -1,11 +1,7 @@ #pragma once -#include "Game/T6/T6.h" - #include "Json/JsonCommon.h" -#include "Json/JsonExtension.h" #include -#include #include #include #include diff --git a/src/ObjCommon/Game/T6/XModel/XModelConstantsT6.h b/src/ObjCommon/Game/T6/XModel/XModelConstantsT6.h new file mode 100644 index 000000000..e7ae94cd4 --- /dev/null +++ b/src/ObjCommon/Game/T6/XModel/XModelConstantsT6.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Game/T6/T6.h" + +namespace T6 +{ + inline const char* HITLOC_NAMES[]{ + // clang-format off + "none", + "helmet", + "head", + "neck", + "torso_upper", + "torso_middle", + "torso_lower", + "right_arm_upper", + "left_arm_upper", + "right_arm_lower", + "left_arm_lower", + "right_hand", + "left_hand", + "right_leg_upper", + "left_leg_upper", + "right_leg_lower", + "left_leg_lower", + "right_foot", + "left_foot", + "gun", + "shield", + // clang-format on + }; + static_assert(std::extent_v == HITLOC_COUNT); +} // namespace T6 diff --git a/src/ObjCommon/Json/JsonCommon.h b/src/ObjCommon/Json/JsonCommon.h index f1a9291e3..6ac712352 100644 --- a/src/ObjCommon/Json/JsonCommon.h +++ b/src/ObjCommon/Json/JsonCommon.h @@ -1,7 +1,10 @@ #pragma once #include "Json/JsonExtension.h" + +#pragma warning(push, 0) #include +#pragma warning(pop) class JsonVec2 { diff --git a/src/ObjCommon/Shader/D3D9ShaderAnalyser.cpp b/src/ObjCommon/Shader/D3D9ShaderAnalyser.cpp index 918d3c16f..4872c82b8 100644 --- a/src/ObjCommon/Shader/D3D9ShaderAnalyser.cpp +++ b/src/ObjCommon/Shader/D3D9ShaderAnalyser.cpp @@ -61,12 +61,12 @@ namespace d3d9 uint32_t TypeInfo; }; - bool PopulateVersionInfo(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) + bool PopulateVersionInfo(ShaderInfo& shaderInfo, const void* shaderByteCode, const size_t shaderByteCodeSize) { if (shaderByteCodeSize < sizeof(uint32_t)) return false; - const auto version = *shaderByteCode; + const auto version = *static_cast(shaderByteCode); shaderInfo.m_version_minor = version & 0xFF; shaderInfo.m_version_major = (version & 0xFF00) >> 8; @@ -91,10 +91,10 @@ namespace d3d9 return false; } - bool FindComment(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize, const uint32_t magic, const char*& commentStart, size_t& commentSize) + bool FindComment(const uint8_t* shaderByteCode, const size_t shaderByteCodeSize, const uint32_t magic, const char*& commentStart, size_t& commentSize) { - const uint32_t* currentPos = shaderByteCode + 1; - size_t currentOffset = sizeof(uint32_t); + const auto* currentPos = reinterpret_cast(shaderByteCode + sizeof(uint32_t)); + auto currentOffset = sizeof(uint32_t); while (*currentPos != OPCODE_END && (currentOffset + sizeof(uint32_t) - 1) < shaderByteCodeSize) { const auto currentValue = *currentPos; @@ -215,7 +215,7 @@ namespace d3d9 return true; } - bool PopulateShaderInfoFromShaderByteCode(ShaderInfo& shaderInfo, const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) + bool PopulateShaderInfoFromShaderByteCode(ShaderInfo& shaderInfo, const uint8_t* shaderByteCode, const size_t shaderByteCodeSize) { if (!PopulateVersionInfo(shaderInfo, shaderByteCode, shaderByteCodeSize)) return false; @@ -236,14 +236,14 @@ namespace d3d9 } } // namespace d3d9 -std::unique_ptr ShaderAnalyser::GetShaderInfo(const uint32_t* shaderByteCode, const size_t shaderByteCodeSize) +std::unique_ptr ShaderAnalyser::GetShaderInfo(const void* shaderByteCode, const size_t shaderByteCodeSize) { if (shaderByteCode == nullptr || shaderByteCodeSize == 0) return nullptr; auto shaderInfo = std::make_unique(); - if (!PopulateShaderInfoFromShaderByteCode(*shaderInfo, shaderByteCode, shaderByteCodeSize)) + if (!PopulateShaderInfoFromShaderByteCode(*shaderInfo, static_cast(shaderByteCode), shaderByteCodeSize)) return nullptr; return shaderInfo; diff --git a/src/ObjCommon/Shader/D3D9ShaderAnalyser.h b/src/ObjCommon/Shader/D3D9ShaderAnalyser.h index e58f1202e..7e186c8dc 100644 --- a/src/ObjCommon/Shader/D3D9ShaderAnalyser.h +++ b/src/ObjCommon/Shader/D3D9ShaderAnalyser.h @@ -95,6 +95,6 @@ namespace d3d9 class ShaderAnalyser { public: - static std::unique_ptr GetShaderInfo(const uint32_t* shaderByteCode, size_t shaderByteCodeSize); + static std::unique_ptr GetShaderInfo(const void* shaderByteCode, size_t shaderByteCodeSize); }; } // namespace d3d9 diff --git a/src/ObjLoading.lua b/src/ObjLoading.lua index 9452e56eb..45f110c2c 100644 --- a/src/ObjLoading.lua +++ b/src/ObjLoading.lua @@ -49,6 +49,8 @@ function ObjLoading:project() path.join(folder, "ObjLoading") } } + + useSourceTemplating("ObjLoading") self:include(includes) Crypto:include(includes) diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.cpp index 42ea92ac3..174b568c1 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.cpp @@ -1,17 +1,62 @@ #include "AssetLoaderGfxImage.h" #include "Game/IW5/IW5.h" -#include "ObjLoading.h" +#include "Image/IwiLoader.h" #include "Pool/GlobalAssetPool.h" #include +#include +#include +#include using namespace IW5; void* AssetLoaderGfxImage::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) { + auto* asset = memory->Alloc(); + asset->name = memory->Dup(assetName.c_str()); + return asset; +} + +bool AssetLoaderGfxImage::CanLoadFromRaw() const +{ + return true; +} + +bool AssetLoaderGfxImage::LoadFromRaw( + const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto fileName = std::format("images/{}.iwi", assetName); + const auto file = searchPath->Open(fileName); + if (!file.IsOpen()) + return false; + + const auto fileSize = static_cast(file.m_length); + const auto fileData = std::make_unique(fileSize); + file.m_stream->read(fileData.get(), fileSize); + + MemoryManager tempMemory; + IwiLoader iwiLoader(&tempMemory); + std::istringstream ss(std::string(fileData.get(), fileSize)); + const auto texture = iwiLoader.LoadIwi(ss); + if (!texture) + { + std::cerr << std::format("Failed to load texture from: {}\n", fileName); + return false; + } + auto* image = memory->Create(); memset(image, 0, sizeof(GfxImage)); + image->name = memory->Dup(assetName.c_str()); - return image; + image->noPicmip = !texture->HasMipMaps(); + image->width = static_cast(texture->GetWidth()); + image->height = static_cast(texture->GetHeight()); + image->depth = static_cast(texture->GetDepth()); + + image->texture.loadDef = memory->Alloc(); + + manager->AddAsset(assetName, image); + + return true; } diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.h index d334ca35e..7f2bec24a 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.h +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderGfxImage.h @@ -1,6 +1,6 @@ #pragma once - #include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" #include "Game/IW5/IW5.h" #include "SearchPath/ISearchPath.h" @@ -10,5 +10,8 @@ namespace IW5 { 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 IW5 diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.cpp index 6075ad871..13170e20e 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.cpp @@ -1,18 +1,57 @@ #include "AssetLoaderMaterial.h" #include "Game/IW5/IW5.h" -#include "ObjLoading.h" +#include "Game/IW5/Material/JsonMaterialLoader.h" #include "Pool/GlobalAssetPool.h" #include +#include +#include using namespace IW5; void* AssetLoaderMaterial::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) { - auto* material = memory->Create(); - memset(material, 0, sizeof(Material)); + auto* asset = memory->Alloc(); + asset->info.name = memory->Dup(assetName.c_str()); + return asset; +} + +bool AssetLoaderMaterial::CanLoadFromRaw() const +{ + return true; +} + +std::string AssetLoaderMaterial::GetFileNameForAsset(const std::string& assetName) +{ + std::string sanitizedFileName(assetName); + if (sanitizedFileName[0] == '*') + { + std::ranges::replace(sanitizedFileName, '*', '_'); + const auto parenthesisPos = sanitizedFileName.find('('); + if (parenthesisPos != std::string::npos) + sanitizedFileName.erase(parenthesisPos); + sanitizedFileName = "generated/" + sanitizedFileName; + } + + return std::format("materials/{}.json", sanitizedFileName); +} + +bool AssetLoaderMaterial::LoadFromRaw( + const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto file = searchPath->Open(GetFileNameForAsset(assetName)); + if (!file.IsOpen()) + return false; + + auto* material = memory->Alloc(); material->info.name = memory->Dup(assetName.c_str()); - return material; + std::vector dependencies; + if (LoadMaterialAsJson(*file.m_stream, *material, memory, manager, dependencies)) + manager->AddAsset(assetName, material, std::move(dependencies)); + else + std::cerr << std::format("Failed to load material \"{}\"\n", assetName); + + return true; } diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.h index b61c89653..f9f45d71e 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.h +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderMaterial.h @@ -1,6 +1,6 @@ #pragma once - #include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" #include "Game/IW5/IW5.h" #include "SearchPath/ISearchPath.h" @@ -8,7 +8,12 @@ namespace IW5 { class AssetLoaderMaterial 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 IW5 diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp index 18b3007a9..19713878b 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp @@ -814,8 +814,8 @@ namespace for (auto i = 0u; i < originalGraphKnotCount; i++) { const auto& commonKnot = graph.knots[i]; - originalGraphKnots[i][0] = static_cast(commonKnot.x); - originalGraphKnots[i][1] = static_cast(commonKnot.y); + originalGraphKnots[i].x = static_cast(commonKnot.x); + originalGraphKnots[i].y = static_cast(commonKnot.y); } graphKnots = originalGraphKnots; diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.cpp index 2822c2574..4f7f96295 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.cpp @@ -1,17 +1,47 @@ #include "AssetLoaderXModel.h" #include "Game/IW5/IW5.h" -#include "ObjLoading.h" +#include "Game/IW5/XModel/XModelLoaderIW5.h" #include "Pool/GlobalAssetPool.h" #include +#include +#include using namespace IW5; void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) { - auto* model = memory->Create(); - memset(model, 0, sizeof(XModel)); - model->name = memory->Dup(assetName.c_str()); - return model; + auto* asset = memory->Alloc(); + asset->name = memory->Dup(assetName.c_str()); + return asset; +} + +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 (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies)) + { + manager->AddAsset(assetName, xmodel, std::move(dependencies)); + } + else + { + std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName); + return false; + } + + return true; } diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.h index 1ea4565d5..3bee159f0 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.h +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderXModel.h @@ -1,6 +1,6 @@ #pragma once - #include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" #include "Game/IW5/IW5.h" #include "SearchPath/ISearchPath.h" @@ -8,7 +8,12 @@ namespace IW5 { 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 IW5 diff --git a/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.cpp b/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.cpp new file mode 100644 index 000000000..a567e9dd3 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.cpp @@ -0,0 +1,447 @@ +#include "JsonMaterialLoader.h" + +#include "Game/IW5/CommonIW5.h" +#include "Game/IW5/Material/JsonMaterial.h" +#include "Impl/Base64.h" + +#include +#include +#include + +using namespace nlohmann; +using namespace IW5; + +namespace +{ + class JsonLoader + { + public: + JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::vector& dependencies) + : m_stream(stream), + m_memory(memory), + m_manager(manager), + m_dependencies(dependencies) + + { + } + + bool Load(Material& material) const + { + const auto jRoot = json::parse(m_stream); + std::string game; + std::string type; + unsigned version; + + jRoot.at("_type").get_to(type); + jRoot.at("_game").get_to(game); + jRoot.at("_version").get_to(version); + + if (type != "material" || version != 1u || game != "iw5") + { + std::cerr << std::format("Tried to load material \"{}\" but did not find expected type material of version 1\n", material.info.name); + return false; + } + + try + { + const auto jMaterial = jRoot.get(); + return CreateMaterialFromJson(jMaterial, material); + } + catch (const json::exception& e) + { + std::cerr << std::format("Failed to parse json of material: {}\n", e.what()); + } + + return false; + } + + private: + static void PrintError(const Material& material, const std::string& message) + { + std::cerr << std::format("Cannot load material \"{}\": {}\n", material.info.name, message); + } + + static bool CreateGameFlagsFromJson(const JsonMaterial& jMaterial, unsigned char& gameFlags) + { + for (const auto gameFlag : jMaterial.gameFlags) + gameFlags |= gameFlag; + + return true; + } + + static void CreateSamplerStateFromJson(const JsonSamplerState& jSamplerState, MaterialTextureDefSamplerState& samplerState) + { + samplerState.filter = jSamplerState.filter; + samplerState.mipMap = jSamplerState.mipMap; + samplerState.clampU = jSamplerState.clampU; + samplerState.clampV = jSamplerState.clampV; + samplerState.clampW = jSamplerState.clampW; + } + + bool CreateWaterFromJson(const JsonWater& jWater, water_t& water, const Material& material) const + { + water.writable.floatTime = jWater.floatTime; + water.M = jWater.m; + water.N = jWater.n; + water.Lx = jWater.lx; + water.Lz = jWater.lz; + water.gravity = jWater.gravity; + water.windvel = jWater.windvel; + water.winddir[0] = jWater.winddir[0]; + water.winddir[1] = jWater.winddir[1]; + water.amplitude = jWater.amplitude; + water.codeConstant[0] = jWater.codeConstant[0]; + water.codeConstant[1] = jWater.codeConstant[1]; + water.codeConstant[2] = jWater.codeConstant[2]; + water.codeConstant[3] = jWater.codeConstant[3]; + + const auto expectedH0Size = water.M * water.N * sizeof(complex_s); + if (expectedH0Size > 0) + { + water.H0 = m_memory.Alloc(water.M * water.N); + const auto h0Size = base64::DecodeBase64(jWater.h0.data(), jWater.h0.size(), water.H0, expectedH0Size); + if (h0Size != expectedH0Size) + { + PrintError(material, std::format("Water h0 size {} does not match expected {}", h0Size, expectedH0Size)); + return false; + } + } + + const auto expectedWTermSize = water.M * water.N * sizeof(float); + if (expectedWTermSize > 0) + { + water.wTerm = m_memory.Alloc(water.M * water.N); + auto wTermSize = base64::DecodeBase64(jWater.wTerm.data(), jWater.wTerm.size(), water.wTerm, expectedWTermSize); + if (wTermSize != expectedWTermSize) + { + PrintError(material, std::format("Water wTerm size {} does not match expected {}", wTermSize, expectedWTermSize)); + return false; + } + } + + return true; + } + + bool CreateTextureDefFromJson(const JsonTexture& jTexture, MaterialTextureDef& textureDef, const Material& material) const + { + if (jTexture.name) + { + if (jTexture.name->empty()) + { + PrintError(material, "textureDef name cannot be empty"); + return false; + } + + textureDef.nameStart = jTexture.name.value()[0]; + textureDef.nameEnd = jTexture.name.value()[jTexture.name->size() - 1]; + textureDef.nameHash = Common::R_HashString(jTexture.name.value().c_str(), 0); + } + else + { + if (!jTexture.nameStart || !jTexture.nameEnd || !jTexture.nameHash) + { + PrintError(material, "textureDefs without name must have nameStart, nameEnd and nameHash"); + return false; + } + + if (jTexture.nameStart->size() != 1 || jTexture.nameEnd->size() != 1) + { + PrintError(material, "nameStart and nameEnd must be a string of exactly one character"); + return false; + } + + textureDef.nameStart = jTexture.nameStart.value()[0]; + textureDef.nameEnd = jTexture.nameEnd.value()[0]; + textureDef.nameHash = jTexture.nameHash.value(); + } + + CreateSamplerStateFromJson(jTexture.samplerState, textureDef.samplerState); + + textureDef.semantic = jTexture.semantic; + + auto* imageAsset = m_manager.LoadDependency(jTexture.image); + if (!imageAsset) + { + PrintError(material, std::format("Could not find textureDef image: {}", jTexture.image)); + return false; + } + m_dependencies.push_back(imageAsset); + + if (jTexture.water) + { + if (jTexture.semantic != TS_WATER_MAP) + { + PrintError(material, "Only textureDefs with semantic waterMap can define water params"); + return false; + } + } + else + { + if (jTexture.semantic == TS_WATER_MAP) + { + PrintError(material, "TextureDefs with semantic waterMap must define water params"); + return false; + } + } + + if (jTexture.water) + { + auto* water = m_memory.Alloc(); + water->image = imageAsset->Asset(); + + if (!CreateWaterFromJson(*jTexture.water, *water, material)) + return false; + + textureDef.u.water = water; + } + else + textureDef.u.image = imageAsset->Asset(); + + return true; + } + + static bool CreateConstantDefFromJson(const JsonConstant& jConstant, MaterialConstantDef& constantDef, const Material& material) + { + if (jConstant.name) + { + const auto copyCount = std::min(jConstant.name->size() + 1, std::extent_v); + strncpy(constantDef.name, jConstant.name->c_str(), copyCount); + if (copyCount < std::extent_v) + memset(&constantDef.name[copyCount], 0, std::extent_v - copyCount); + constantDef.nameHash = Common::R_HashString(jConstant.name->c_str(), 0); + } + else + { + if (!jConstant.nameFragment || !jConstant.nameHash) + { + PrintError(material, "constantDefs without name must have nameFragment and nameHash"); + return false; + } + + const auto copyCount = std::min(jConstant.nameFragment->size() + 1, std::extent_v); + strncpy(constantDef.name, jConstant.nameFragment->c_str(), copyCount); + if (copyCount < std::extent_v) + memset(&constantDef.name[copyCount], 0, std::extent_v - copyCount); + constantDef.nameHash = jConstant.nameHash.value(); + } + + if (jConstant.literal.size() != 4) + { + PrintError(material, "constantDef literal must be array of size 4"); + return false; + } + + constantDef.literal.x = jConstant.literal[0]; + constantDef.literal.y = jConstant.literal[1]; + constantDef.literal.z = jConstant.literal[2]; + constantDef.literal.w = jConstant.literal[3]; + + return true; + } + + static bool + CreateStateBitsTableEntryFromJson(const JsonStateBitsTableEntry& jStateBitsTableEntry, GfxStateBits& stateBitsTableEntry, const Material& material) + { + auto& structured = stateBitsTableEntry.loadBits.structured; + + structured.srcBlendRgb = jStateBitsTableEntry.srcBlendRgb; + structured.dstBlendRgb = jStateBitsTableEntry.dstBlendRgb; + structured.blendOpRgb = jStateBitsTableEntry.blendOpRgb; + + if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::DISABLED) + { + structured.alphaTestDisabled = 1; + structured.alphaTest = 0; + } + else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GT0) + { + structured.alphaTestDisabled = 0; + structured.alphaTest = GFXS_ALPHA_TEST_GT_0; + } + else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::LT128) + { + structured.alphaTestDisabled = 0; + structured.alphaTest = GFXS_ALPHA_TEST_LT_128; + } + else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GE128) + { + structured.alphaTestDisabled = 0; + structured.alphaTest = GFXS_ALPHA_TEST_GE_128; + } + else + { + PrintError(material, "Invalid value for alphaTest"); + return false; + } + + if (jStateBitsTableEntry.cullFace == JsonCullFace::NONE) + structured.cullFace = GFXS_CULL_NONE; + else if (jStateBitsTableEntry.cullFace == JsonCullFace::BACK) + structured.cullFace = GFXS_CULL_BACK; + else if (jStateBitsTableEntry.cullFace == JsonCullFace::FRONT) + structured.cullFace = GFXS_CULL_FRONT; + else + { + PrintError(material, "Invalid value for cull face"); + return false; + } + + structured.srcBlendAlpha = jStateBitsTableEntry.srcBlendAlpha; + structured.dstBlendAlpha = jStateBitsTableEntry.dstBlendAlpha; + structured.blendOpAlpha = jStateBitsTableEntry.blendOpAlpha; + structured.colorWriteRgb = jStateBitsTableEntry.colorWriteRgb; + structured.colorWriteAlpha = jStateBitsTableEntry.colorWriteAlpha; + structured.gammaWrite = jStateBitsTableEntry.gammaWrite; + structured.polymodeLine = jStateBitsTableEntry.polymodeLine; + structured.depthWrite = jStateBitsTableEntry.depthWrite; + + if (jStateBitsTableEntry.depthTest == JsonDepthTest::DISABLED) + structured.depthTestDisabled = 1; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::ALWAYS) + structured.depthTest = GFXS_DEPTHTEST_ALWAYS; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS) + structured.depthTest = GFXS_DEPTHTEST_LESS; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::EQUAL) + structured.depthTest = GFXS_DEPTHTEST_EQUAL; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS_EQUAL) + structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + else + { + PrintError(material, "Invalid value for depth test"); + return false; + } + + structured.polygonOffset = jStateBitsTableEntry.polygonOffset; + + if (jStateBitsTableEntry.stencilFront) + { + structured.stencilFrontEnabled = 1; + structured.stencilFrontPass = jStateBitsTableEntry.stencilFront->pass; + structured.stencilFrontFail = jStateBitsTableEntry.stencilFront->fail; + structured.stencilFrontZFail = jStateBitsTableEntry.stencilFront->zfail; + structured.stencilFrontFunc = jStateBitsTableEntry.stencilFront->func; + } + + if (jStateBitsTableEntry.stencilBack) + { + structured.stencilBackEnabled = 1; + structured.stencilBackPass = jStateBitsTableEntry.stencilBack->pass; + structured.stencilBackFail = jStateBitsTableEntry.stencilBack->fail; + structured.stencilBackZFail = jStateBitsTableEntry.stencilBack->zfail; + structured.stencilBackFunc = jStateBitsTableEntry.stencilBack->func; + } + + return true; + } + + bool CreateMaterialFromJson(const JsonMaterial& jMaterial, Material& material) const + { + if (!CreateGameFlagsFromJson(jMaterial, material.info.gameFlags)) + return false; + + material.info.sortKey = static_cast(jMaterial.sortKey); + + if (jMaterial.textureAtlas) + { + material.info.textureAtlasRowCount = jMaterial.textureAtlas->rows; + material.info.textureAtlasColumnCount = jMaterial.textureAtlas->columns; + } + else + { + material.info.textureAtlasRowCount = 0; + material.info.textureAtlasColumnCount = 0; + } + + material.info.surfaceTypeBits = jMaterial.surfaceTypeBits; + + if (jMaterial.stateBitsEntry.size() != std::extent_v) + { + PrintError(material, std::format("StateBitsEntry size is not {}", std::extent_v)); + return false; + } + for (auto i = 0u; i < std::extent_v; i++) + material.stateBitsEntry[i] = jMaterial.stateBitsEntry[i]; + + material.stateFlags = static_cast(jMaterial.stateFlags); + material.cameraRegion = jMaterial.cameraRegion; + + auto* techniqueSet = m_manager.LoadDependency(jMaterial.techniqueSet); + if (!techniqueSet) + { + PrintError(material, "Could not find technique set"); + return false; + } + m_dependencies.push_back(techniqueSet); + material.techniqueSet = techniqueSet->Asset(); + + if (!jMaterial.textures.empty()) + { + material.textureCount = static_cast(jMaterial.textures.size()); + material.textureTable = m_memory.Alloc(material.textureCount); + + for (auto i = 0u; i < material.textureCount; i++) + { + if (!CreateTextureDefFromJson(jMaterial.textures[i], material.textureTable[i], material)) + return false; + } + } + else + { + material.textureCount = 0; + material.textureTable = nullptr; + } + + if (!jMaterial.constants.empty()) + { + material.constantCount = static_cast(jMaterial.constants.size()); + material.constantTable = m_memory.Alloc(material.constantCount); + + for (auto i = 0u; i < material.constantCount; i++) + { + if (!CreateConstantDefFromJson(jMaterial.constants[i], material.constantTable[i], material)) + return false; + } + } + else + { + material.constantCount = 0; + material.constantTable = nullptr; + } + + if (!jMaterial.stateBits.empty()) + { + material.stateBitsCount = static_cast(jMaterial.stateBits.size()); + material.stateBitsTable = m_memory.Alloc(material.stateBitsCount); + + for (auto i = 0u; i < material.stateBitsCount; i++) + { + if (!CreateStateBitsTableEntryFromJson(jMaterial.stateBits[i], material.stateBitsTable[i], material)) + return false; + } + } + else + { + material.stateBitsCount = 0; + material.stateBitsTable = nullptr; + } + + return true; + } + + std::istream& m_stream; + MemoryManager& m_memory; + IAssetLoadingManager& m_manager; + std::vector& m_dependencies; + }; +} // namespace + +namespace IW5 +{ + bool LoadMaterialAsJson( + std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies) + { + const JsonLoader loader(stream, *memory, *manager, dependencies); + + return loader.Load(material); + } +} // namespace IW5 diff --git a/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.h b/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.h new file mode 100644 index 000000000..b498852d8 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Material/JsonMaterialLoader.h @@ -0,0 +1,13 @@ +#pragma once + +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/IW5/IW5.h" +#include "Utils/MemoryManager.h" + +#include + +namespace IW5 +{ + bool LoadMaterialAsJson( + std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies); +} // namespace IW5 diff --git a/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.cpp b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.cpp new file mode 100644 index 000000000..22dee30ff --- /dev/null +++ b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.cpp @@ -0,0 +1,47 @@ +#include "AssetLoaderXModel.h" + +#include "Game/T5/T5.h" +#include "Game/T5/XModel/XModelLoaderT5.h" +#include "Pool/GlobalAssetPool.h" + +#include +#include +#include + +using namespace T5; + +void* AssetLoaderXModel::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* asset = memory->Alloc(); + asset->name = memory->Dup(assetName.c_str()); + return asset; +} + +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 (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies)) + { + manager->AddAsset(assetName, xmodel, std::move(dependencies)); + } + else + { + std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName); + return false; + } + + return true; +} diff --git a/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.h b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.h new file mode 100644 index 000000000..4a669630d --- /dev/null +++ b/src/ObjLoading/Game/T5/AssetLoaders/AssetLoaderXModel.h @@ -0,0 +1,19 @@ +#pragma once +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/T5/T5.h" +#include "SearchPath/ISearchPath.h" + +namespace T5 +{ + 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 T5 diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index 16b38fb2d..0b98c9593 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -3,6 +3,7 @@ #include "AssetLoaders/AssetLoaderLocalizeEntry.h" #include "AssetLoaders/AssetLoaderRawFile.h" #include "AssetLoaders/AssetLoaderStringTable.h" +#include "AssetLoaders/AssetLoaderXModel.h" #include "AssetLoading/AssetLoadingManager.h" #include "Game/T5/GameAssetPoolT5.h" #include "Game/T5/GameT5.h" @@ -27,7 +28,7 @@ ObjLoader::ObjLoader() REGISTER_ASSET_LOADER(BasicAssetLoader) REGISTER_ASSET_LOADER(BasicAssetLoader) REGISTER_ASSET_LOADER(BasicAssetLoader) - REGISTER_ASSET_LOADER(BasicAssetLoader) + REGISTER_ASSET_LOADER(AssetLoaderXModel) REGISTER_ASSET_LOADER(BasicAssetLoader) REGISTER_ASSET_LOADER(BasicAssetLoader) REGISTER_ASSET_LOADER(BasicAssetLoader) diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp index 8509d2d3f..28dfcc018 100644 --- a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderXModel.cpp @@ -1,7 +1,7 @@ #include "AssetLoaderXModel.h" #include "Game/T6/T6.h" -#include "Game/T6/XModel/JsonXModelLoader.h" +#include "Game/T6/XModel/XModelLoaderT6.h" #include "Pool/GlobalAssetPool.h" #include @@ -33,10 +33,15 @@ bool AssetLoaderXModel::LoadFromRaw( xmodel->name = memory->Dup(assetName.c_str()); std::vector dependencies; - if (LoadXModelAsJson(*file.m_stream, *xmodel, memory, manager, dependencies)) + if (LoadXModel(*file.m_stream, *xmodel, memory, manager, dependencies)) + { manager->AddAsset(assetName, xmodel, std::move(dependencies)); + } else + { std::cerr << std::format("Failed to load xmodel \"{}\"\n", assetName); + return false; + } return true; } diff --git a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h b/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h deleted file mode 100644 index d7747287c..000000000 --- a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.h +++ /dev/null @@ -1,13 +0,0 @@ -#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/ObjLoading/XModel/Gltf/GltfLoader.h b/src/ObjLoading/XModel/Gltf/GltfLoader.h index b12d8d988..872394e4c 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.h +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.h @@ -2,14 +2,14 @@ #include "GltfInput.h" #include "XModel/Gltf/JsonGltf.h" -#include "XModel/XModelLoader.h" +#include "XModel/XModelFileLoader.h" #include #include namespace gltf { - class Loader : public XModelLoader + class Loader : public XModelFileLoader { public: Loader() = default; diff --git a/src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp b/src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp index 95ec99a16..a0edf7f0a 100644 --- a/src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp +++ b/src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp @@ -1,14 +1,12 @@ #include "GltfBuffer.h" +#include "Impl/Base64.h" #include "XModel/Gltf/GltfConstants.h" #include #include #include -#define LTC_NO_PROTOTYPES -#include - using namespace gltf; EmbeddedBuffer::EmbeddedBuffer(const void* data, const size_t dataSize) @@ -51,15 +49,12 @@ bool DataUriBuffer::ReadDataFromUri(const std::string& uri) return false; const auto base64DataLength = uri.size() - URI_PREFIX_LENGTH; + m_data_size = base64::GetBase64DecodeOutputLength(base64DataLength); + m_data = std::make_unique(m_data_size); - unsigned long outLength = base64DataLength / 4u; - m_data = std::make_unique(outLength); - const auto result = base64_decode(&uri[URI_PREFIX_LENGTH], base64DataLength, m_data.get(), &outLength); - m_data_size = static_cast(outLength); - - assert(result == CRYPT_OK); + m_data_size = base64::DecodeBase64(&uri[URI_PREFIX_LENGTH], base64DataLength, m_data.get(), m_data_size); - return false; + return m_data_size > 0; } bool DataUriBuffer::ReadData(void* dest, const size_t offset, const size_t count) const diff --git a/src/ObjLoading/XModel/PartClassificationState.cpp b/src/ObjLoading/XModel/PartClassificationState.cpp new file mode 100644 index 000000000..a09e33741 --- /dev/null +++ b/src/ObjLoading/XModel/PartClassificationState.cpp @@ -0,0 +1,79 @@ +#include "PartClassificationState.h" + +#include "Csv/CsvStream.h" +#include "ObjLoading.h" +#include "Utils/StringUtils.h" + +#include +#include + +PartClassificationState::PartClassificationState() + : m_loaded(false) +{ +} + +bool PartClassificationState::Load(const char** hitLocNames, const size_t hitLocNameCount, const IAssetLoadingManager& manager) +{ + if (m_loaded) + return true; + + if (ObjLoading::Configuration.Verbose) + std::cout << "Loading part classification...\n"; + + const auto file = manager.GetAssetLoadingContext()->m_raw_search_path->Open(PART_CLASSIFICATION_FILE); + if (!file.IsOpen()) + { + std::cerr << std::format("Could not load part classification: Failed to open {}\n", PART_CLASSIFICATION_FILE); + return false; + } + + const auto hitLocStart = hitLocNames; + const auto hitLocEnd = &hitLocNames[hitLocNameCount]; + + const CsvInputStream csvStream(*file.m_stream); + std::vector row; + auto rowIndex = 0u; + while (csvStream.NextRow(row)) + { + if (!LoadRow(hitLocStart, hitLocEnd, rowIndex++, row)) + return false; + } + + m_loaded = true; + + return false; +} + +[[nodiscard]] unsigned PartClassificationState::GetPartClassificationForBoneName(const std::string& boneName) const +{ + const auto entry = m_part_classifications.find(boneName); + + return entry != m_part_classifications.end() ? entry->second : HITLOC_NONE; +} + +bool PartClassificationState::LoadRow(const char** hitLocStart, const char** hitLocEnd, const unsigned rowIndex, std::vector& row) +{ + if (row.empty()) + return true; + + if (row.size() != 2) + { + std::cerr << "Could not load part classification: Invalid row\n"; + return false; + } + + utils::MakeStringLowerCase(row[0]); + utils::MakeStringLowerCase(row[1]); + + const auto foundHitLoc = std::find(hitLocStart, hitLocEnd, row[1]); + if (foundHitLoc == hitLocEnd) + { + std::cerr << std::format("Invalid hitloc name in row {}: {}\n", rowIndex + 1, row[1]); + return false; + } + + const auto hitLocNum = std::distance(hitLocStart, foundHitLoc); + + m_part_classifications.emplace(row[0], hitLocNum); + return true; +} diff --git a/src/ObjLoading/XModel/PartClassificationState.h b/src/ObjLoading/XModel/PartClassificationState.h new file mode 100644 index 000000000..32bd4afca --- /dev/null +++ b/src/ObjLoading/XModel/PartClassificationState.h @@ -0,0 +1,24 @@ +#pragma once +#include "AssetLoading/IAssetLoadingManager.h" +#include "AssetLoading/IZoneAssetLoaderState.h" + +class PartClassificationState final : public IZoneAssetLoaderState +{ + // TODO: Use MP part classifications when building an mp fastfile + static constexpr auto PART_CLASSIFICATION_FILE = "partclassification.csv"; + + static constexpr auto HITLOC_NONE = 0u; + +public: + PartClassificationState(); + + bool Load(const char** hitLocNames, size_t hitLocNameCount, const IAssetLoadingManager& manager); + + [[nodiscard]] unsigned GetPartClassificationForBoneName(const std::string& boneName) const; + +private: + bool LoadRow(const char** hitLocStart, const char** hitLocEnd, unsigned rowIndex, std::vector& row); + + bool m_loaded; + std::unordered_map m_part_classifications; +}; diff --git a/src/ObjLoading/XModel/TangentData.cpp b/src/ObjLoading/XModel/TangentData.cpp new file mode 100644 index 000000000..a09c25f35 --- /dev/null +++ b/src/ObjLoading/XModel/TangentData.cpp @@ -0,0 +1,47 @@ +#include "TangentData.h" + +#include "Tangentspace.h" + +void TangentData::CreateTangentData(const XModelCommon& common) +{ + if (common.m_vertices.empty()) + return; + + const auto vertexCount = common.m_vertices.size(); + m_tangents.resize(vertexCount); + m_binormals.resize(vertexCount); + + auto triCount = 0u; + for (const auto& object : common.m_objects) + triCount += object.m_faces.size(); + + std::vector indices(triCount * 3u); + auto triOffset = 0u; + for (const auto& object : common.m_objects) + { + for (const auto& face : object.m_faces) + { + indices[triOffset++] = static_cast(face.vertexIndex[0]); + indices[triOffset++] = static_cast(face.vertexIndex[1]); + indices[triOffset++] = static_cast(face.vertexIndex[2]); + } + } + + const auto& firstVertex = common.m_vertices[0]; + + tangent_space::VertexData vertexData{ + firstVertex.coordinates, + sizeof(XModelVertex), + firstVertex.normal, + sizeof(XModelVertex), + firstVertex.uv, + sizeof(XModelVertex), + m_tangents.data(), + sizeof(float) * 3, + m_binormals.data(), + sizeof(float) * 3, + indices.data(), + }; + + tangent_space::CalculateTangentSpace(vertexData, triCount, vertexCount); +} diff --git a/src/ObjLoading/XModel/TangentData.h b/src/ObjLoading/XModel/TangentData.h new file mode 100644 index 000000000..87ae77a49 --- /dev/null +++ b/src/ObjLoading/XModel/TangentData.h @@ -0,0 +1,15 @@ +#pragma once + +#include "XModel/XModelCommon.h" + +#include +#include + +class TangentData +{ +public: + void CreateTangentData(const XModelCommon& common); + + std::vector> m_tangents; + std::vector> m_binormals; +}; diff --git a/src/ObjLoading/XModel/XModelFileLoader.h b/src/ObjLoading/XModel/XModelFileLoader.h new file mode 100644 index 000000000..ad5bd9184 --- /dev/null +++ b/src/ObjLoading/XModel/XModelFileLoader.h @@ -0,0 +1,18 @@ +#pragma once + +#include "XModel/XModelCommon.h" + +#include + +class XModelFileLoader +{ +public: + XModelFileLoader() = default; + virtual ~XModelFileLoader() = default; + XModelFileLoader(const XModelFileLoader& other) = default; + XModelFileLoader(XModelFileLoader&& other) noexcept = default; + XModelFileLoader& operator=(const XModelFileLoader& other) = default; + XModelFileLoader& operator=(XModelFileLoader&& other) noexcept = default; + + virtual std::unique_ptr Load() = 0; +}; diff --git a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp b/src/ObjLoading/XModel/XModelLoader.cpp.template similarity index 80% rename from src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp rename to src/ObjLoading/XModel/XModelLoader.cpp.template index bb8d40cd4..100acd527 100644 --- a/src/ObjLoading/Game/T6/XModel/JsonXModelLoader.cpp +++ b/src/ObjLoading/XModel/XModelLoader.cpp.template @@ -1,8 +1,26 @@ -#include "JsonXModelLoader.h" +#options GAME(IW5, T5, T6) + +#filename "Game/" + GAME + "/XModel/XModelLoader" + GAME + ".cpp" + +#set LOADER_HEADER "\"XModelLoader" + GAME + ".h\"" +#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\"" +#set CONSTANTS_HEADER "\"Game/" + GAME + "/XModel/XModelConstants" + GAME + ".h\"" +#set JSON_HEADER "\"Game/" + GAME + "/XModel/JsonXModel" + GAME + ".h\"" + +#if GAME == "IW5" +#define FEATURE_IW5 +#elif GAME == "T5" +#define FEATURE_T5 +#elif GAME == "T6" +#define FEATURE_T6 +#endif + +#include LOADER_HEADER + +#include COMMON_HEADER +#include CONSTANTS_HEADER +#include JSON_HEADER -#include "Csv/CsvStream.h" -#include "Game/T6/CommonT6.h" -#include "Game/T6/Json/JsonXModel.h" #include "ObjLoading.h" #include "Utils/QuatInt16.h" #include "Utils/StringUtils.h" @@ -13,186 +31,27 @@ #pragma warning(push, 0) #include +#include #pragma warning(pop) +#include "XModel/PartClassificationState.h" +#include "XModel/TangentData.h" #include "XModel/Tangentspace.h" #include #include #include #include -#include #include +#include #include -using namespace nlohmann; -using namespace T6; - -namespace fs = std::filesystem; - -namespace +namespace GAME { - const char* HITLOC_NAMES[]{ - // clang-format off - "none", - "helmet", - "head", - "neck", - "torso_upper", - "torso_middle", - "torso_lower", - "right_arm_upper", - "left_arm_upper", - "right_arm_lower", - "left_arm_lower", - "right_hand", - "left_hand", - "right_leg_upper", - "left_leg_upper", - "right_leg_lower", - "left_leg_lower", - "right_foot", - "left_foot", - "gun", - "shield", - // clang-format on - }; - static_assert(std::extent_v == HITLOC_COUNT); - - class PartClassificationState final : public IZoneAssetLoaderState + class XModelLoader { - // TODO: Use MP part classifications when building an mp fastfile - static constexpr auto PART_CLASSIFICATION_FILE = "partclassification.csv"; - public: - PartClassificationState() - : m_loaded(false) - { - } - - bool Load(const IAssetLoadingManager& manager) - { - if (m_loaded) - return true; - - if (ObjLoading::Configuration.Verbose) - std::cout << "Loading part classification...\n"; - - const auto file = manager.GetAssetLoadingContext()->m_raw_search_path->Open(PART_CLASSIFICATION_FILE); - if (!file.IsOpen()) - { - std::cerr << std::format("Could not load part classification: Failed to open {}\n", PART_CLASSIFICATION_FILE); - return false; - } - - CsvInputStream csvStream(*file.m_stream); - std::vector row; - auto rowIndex = 0u; - while (csvStream.NextRow(row)) - { - if (!LoadRow(rowIndex++, row)) - return false; - } - - m_loaded = true; - - return false; - } - - [[nodiscard]] unsigned GetPartClassificationForBoneName(const std::string& boneName) const - { - const auto entry = m_part_classifications.find(boneName); - - return entry != m_part_classifications.end() ? entry->second : HITLOC_NONE; - } - - private: - bool LoadRow(const unsigned rowIndex, std::vector& row) - { - if (row.empty()) - return true; - - if (row.size() != 2) - { - std::cerr << "Could not load part classification: Invalid row\n"; - return false; - } - - utils::MakeStringLowerCase(row[0]); - utils::MakeStringLowerCase(row[1]); - - const auto foundHitLoc = std::ranges::find(HITLOC_NAMES, row[1]); - if (foundHitLoc == std::end(HITLOC_NAMES)) - { - std::cerr << std::format("Invalid hitloc name in row {}: {}\n", rowIndex + 1, row[1]); - return false; - } - - const auto hitLocNum = std::distance(std::begin(HITLOC_NAMES), foundHitLoc); - - m_part_classifications.emplace(row[0], hitLocNum); - return true; - } - - bool m_loaded; - std::unordered_map m_part_classifications; - }; - - class TangentData - { - public: - void CreateTangentData(const XModelCommon& common) - { - if (common.m_vertices.empty()) - return; - - const auto vertexCount = common.m_vertices.size(); - m_tangents.resize(vertexCount); - m_binormals.resize(vertexCount); - - auto triCount = 0u; - for (const auto& object : common.m_objects) - triCount += object.m_faces.size(); - - std::vector indices(triCount * 3u); - auto triOffset = 0u; - for (const auto& object : common.m_objects) - { - for (const auto& face : object.m_faces) - { - indices[triOffset++] = static_cast(face.vertexIndex[0]); - indices[triOffset++] = static_cast(face.vertexIndex[1]); - indices[triOffset++] = static_cast(face.vertexIndex[2]); - } - } - - const auto& firstVertex = common.m_vertices[0]; - - tangent_space::VertexData vertexData{ - firstVertex.coordinates, - sizeof(XModelVertex), - firstVertex.normal, - sizeof(XModelVertex), - firstVertex.uv, - sizeof(XModelVertex), - m_tangents.data(), - sizeof(float) * 3, - m_binormals.data(), - sizeof(float) * 3, - indices.data(), - }; - - tangent_space::CalculateTangentSpace(vertexData, triCount, vertexCount); - } - - std::vector> m_tangents; - std::vector> m_binormals; - }; - - class JsonLoader - { - public: - JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set& dependencies) + XModelLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::set& dependencies) : m_stream(stream), m_memory(memory), m_script_strings(manager.GetAssetLoadingContext()->m_zone->m_script_strings), @@ -205,7 +64,7 @@ namespace bool Load(XModel& xmodel) { - const auto jRoot = json::parse(m_stream); + const auto jRoot = nlohmann::json::parse(m_stream); std::string type; unsigned version; @@ -223,7 +82,7 @@ namespace const auto jXModel = jRoot.get(); return CreateXModelFromJson(jXModel, xmodel); } - catch (const json::exception& e) + catch (const nlohmann::json::exception& e) { std::cerr << std::format("Failed to parse json of xmodel: {}\n", e.what()); } @@ -316,15 +175,24 @@ namespace if (common.m_bone_weight_data.weights.empty()) return; - info.bounds[0].x = 0.0f; - info.bounds[0].y = 0.0f; - info.bounds[0].z = 0.0f; - info.bounds[1].x = 0.0f; - info.bounds[1].y = 0.0f; - info.bounds[1].z = 0.0f; - info.offset.x = 0.0f; - info.offset.y = 0.0f; - info.offset.z = 0.0f; +#ifdef FEATURE_IW5 + vec3_t minCoordinate, maxCoordinate; + auto& offset = info.bounds.midPoint; +#else + auto& offset = info.offset; + auto& minCoordinate = info.bounds[0]; + auto& maxCoordinate = info.bounds[1]; +#endif + + minCoordinate.x = 0.0f; + minCoordinate.y = 0.0f; + minCoordinate.z = 0.0f; + maxCoordinate.x = 0.0f; + maxCoordinate.y = 0.0f; + maxCoordinate.z = 0.0f; + offset.x = 0.0f; + offset.y = 0.0f; + offset.z = 0.0f; info.radiusSquared = 0.0f; const auto vertexCount = common.m_vertex_bone_weights.size(); @@ -339,22 +207,31 @@ namespace if (weight.boneIndex != boneIndex) continue; - info.bounds[0].x = std::min(info.bounds[0].x, vertex.coordinates[0]); - info.bounds[0].y = std::min(info.bounds[0].y, vertex.coordinates[1]); - info.bounds[0].z = std::min(info.bounds[0].z, vertex.coordinates[2]); - info.bounds[1].x = std::max(info.bounds[1].x, vertex.coordinates[0]); - info.bounds[1].y = std::max(info.bounds[1].y, vertex.coordinates[1]); - info.bounds[1].z = std::max(info.bounds[1].z, vertex.coordinates[2]); + minCoordinate.x = std::min(minCoordinate.x, vertex.coordinates[0]); + minCoordinate.y = std::min(minCoordinate.y, vertex.coordinates[1]); + minCoordinate.z = std::min(minCoordinate.z, vertex.coordinates[2]); + maxCoordinate.x = std::max(maxCoordinate.x, vertex.coordinates[0]); + maxCoordinate.y = std::max(maxCoordinate.y, vertex.coordinates[1]); + maxCoordinate.z = std::max(maxCoordinate.z, vertex.coordinates[2]); } } - const Eigen::Vector3f minEigen(info.bounds[0].x, info.bounds[0].y, info.bounds[0].z); - const Eigen::Vector3f maxEigen(info.bounds[1].x, info.bounds[1].y, info.bounds[1].z); + const Eigen::Vector3f minEigen(minCoordinate.x, minCoordinate.y, minCoordinate.z); + const Eigen::Vector3f maxEigen(maxCoordinate.x, maxCoordinate.y, maxCoordinate.z); const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f; - info.offset.x = boundsCenter.x(); - info.offset.y = boundsCenter.y(); - info.offset.z = boundsCenter.z(); - info.radiusSquared = Eigen::Vector3f(maxEigen - boundsCenter).squaredNorm(); + const Eigen::Vector3f halfSizeEigen = maxEigen - boundsCenter; +#ifdef FEATURE_IW5 + + info.bounds.halfSize.x = halfSizeEigen.x(); + info.bounds.halfSize.y = halfSizeEigen.y(); + info.bounds.halfSize.z = halfSizeEigen.z(); +#endif + + offset.x = boundsCenter.x(); + offset.y = boundsCenter.y(); + offset.z = boundsCenter.z(); + + info.radiusSquared = halfSizeEigen.squaredNorm(); } bool ApplyCommonBonesToXModel(const JsonXModelLod& jLod, XModel& xmodel, unsigned lodNumber, const XModelCommon& common) const @@ -362,7 +239,7 @@ namespace if (common.m_bones.empty()) return true; - m_part_classification_state.Load(m_manager); + m_part_classification_state.Load(HITLOC_NAMES, std::extent_v, m_manager); const auto boneCount = common.m_bones.size(); constexpr auto maxBones = std::numeric_limits::max(); @@ -415,8 +292,10 @@ namespace ApplyBasePose(xmodel.baseMat[boneIndex], bone); CalculateBoneBounds(xmodel.boneInfo[boneIndex], boneIndex, common); +#if defined(FEATURE_T5) || defined(FEATURE_T6) // Other boneInfo data is filled when calculating bone bounds xmodel.boneInfo[boneIndex].collmap = -1; +#endif if (xmodel.numRootBones <= boneIndex) { @@ -744,7 +623,7 @@ namespace return false; } - auto extension = fs::path(jLod.file).extension().string(); + auto extension = std::filesystem::path(jLod.file).extension().string(); utils::MakeStringLowerCase(extension); const auto common = LoadModelByExtension(*file.m_stream, extension); @@ -821,17 +700,52 @@ namespace lodInfo.partBits[i] |= surface.partBits[i]; } +#ifdef FEATURE_IW5 + auto* modelSurfs = m_memory.Alloc(); + const auto modelSurfsName = std::format("{}_lod{}", xmodel.name, lodNumber); + modelSurfs->name = m_memory.Dup(modelSurfsName.c_str()); + + static_assert(std::extent_v == std::extent_v); + memcpy(modelSurfs->partBits, lodInfo.partBits, sizeof(XModelLodInfo::partBits)); + + modelSurfs->numsurfs = lodInfo.numsurfs; + modelSurfs->surfs = m_memory.Alloc(modelSurfs->numsurfs); + memcpy(modelSurfs->surfs, &m_surfaces[lodInfo.surfIndex], sizeof(XSurface) * modelSurfs->numsurfs); + + m_manager.AddAsset(modelSurfsName, modelSurfs); + + lodInfo.modelSurfs = modelSurfs; + lodInfo.surfs = modelSurfs->surfs; + + lodInfo.lod = static_cast(lodNumber); +#endif + return true; } static void CalculateModelBounds(XModel& xmodel) { +#ifdef FEATURE_IW5 + if (!xmodel.lodInfo[0].modelSurfs || !xmodel.lodInfo[0].modelSurfs->surfs) + return; + + const auto* surfs = xmodel.lodInfo[0].modelSurfs->surfs; + vec3_t minCoordinate, maxCoordinate; +#else if (!xmodel.surfs) return; + auto& minCoordinate = xmodel.mins; + auto& maxCoordinate = xmodel.maxs; +#endif + for (auto surfaceIndex = 0u; surfaceIndex < xmodel.lodInfo[0].numsurfs; surfaceIndex++) { +#ifdef FEATURE_IW5 + const auto& surface = surfs[surfaceIndex]; +#else const auto& surface = xmodel.surfs[surfaceIndex + xmodel.lodInfo[0].surfIndex]; +#endif if (!surface.verts0) continue; @@ -840,19 +754,35 @@ namespace { const auto& vertex = surface.verts0[vertIndex]; - xmodel.mins.x = std::min(xmodel.mins.x, vertex.xyz.v[0]); - xmodel.mins.y = std::min(xmodel.mins.y, vertex.xyz.v[1]); - xmodel.mins.z = std::min(xmodel.mins.z, vertex.xyz.v[2]); - xmodel.maxs.x = std::max(xmodel.maxs.x, vertex.xyz.v[0]); - xmodel.maxs.y = std::max(xmodel.maxs.y, vertex.xyz.v[1]); - xmodel.maxs.z = std::max(xmodel.maxs.z, vertex.xyz.v[2]); + minCoordinate.x = std::min(minCoordinate.x, vertex.xyz.v[0]); + minCoordinate.y = std::min(minCoordinate.y, vertex.xyz.v[1]); + minCoordinate.z = std::min(minCoordinate.z, vertex.xyz.v[2]); + maxCoordinate.x = std::max(maxCoordinate.x, vertex.xyz.v[0]); + maxCoordinate.y = std::max(maxCoordinate.y, vertex.xyz.v[1]); + maxCoordinate.z = std::max(maxCoordinate.z, vertex.xyz.v[2]); } } - const auto maxX = std::max(std::abs(xmodel.mins.x), std::abs(xmodel.maxs.x)); - const auto maxY = std::max(std::abs(xmodel.mins.y), std::abs(xmodel.maxs.y)); - const auto maxZ = std::max(std::abs(xmodel.mins.z), std::abs(xmodel.maxs.z)); +#ifdef FEATURE_IW5 + const Eigen::Vector3f minEigen(minCoordinate.x, minCoordinate.y, minCoordinate.z); + const Eigen::Vector3f maxEigen(maxCoordinate.x, maxCoordinate.y, maxCoordinate.z); + const Eigen::Vector3f boundsCenter = (minEigen + maxEigen) * 0.5f; + const Eigen::Vector3f halfSizeEigen = maxEigen - boundsCenter; + + xmodel.bounds.halfSize.x = halfSizeEigen.x(); + xmodel.bounds.halfSize.y = halfSizeEigen.y(); + xmodel.bounds.halfSize.z = halfSizeEigen.z(); + + xmodel.bounds.midPoint.x = boundsCenter.x(); + xmodel.bounds.midPoint.y = boundsCenter.y(); + xmodel.bounds.midPoint.z = boundsCenter.z(); + xmodel.radius = halfSizeEigen.norm(); +#else + const auto maxX = std::max(std::abs(minCoordinate.x), std::abs(maxCoordinate.x)); + const auto maxY = std::max(std::abs(minCoordinate.y), std::abs(maxCoordinate.y)); + const auto maxZ = std::max(std::abs(minCoordinate.z), std::abs(maxCoordinate.z)); xmodel.radius = Eigen::Vector3f(maxX, maxY, maxZ).norm(); +#endif } bool CreateXModelFromJson(const JsonXModel& jXModel, XModel& xmodel) @@ -865,7 +795,7 @@ namespace } auto lodNumber = 0u; - xmodel.numLods = static_cast(jXModel.lods.size()); + xmodel.numLods = static_cast(jXModel.lods.size()); for (const auto& jLod : jXModel.lods) { if (!LoadLod(jLod, xmodel, lodNumber++)) @@ -879,10 +809,14 @@ namespace return false; } - xmodel.numsurfs = static_cast(m_surfaces.size()); + xmodel.numsurfs = static_cast(m_surfaces.size()); + +#if defined(FEATURE_T5) || defined(FEATURE_T6) xmodel.surfs = m_memory.Alloc(xmodel.numsurfs); - xmodel.materialHandles = m_memory.Alloc(xmodel.numsurfs); memcpy(xmodel.surfs, m_surfaces.data(), sizeof(XSurface) * xmodel.numsurfs); +#endif + + xmodel.materialHandles = m_memory.Alloc(xmodel.numsurfs); memcpy(xmodel.materialHandles, m_materials.data(), sizeof(Material*) * xmodel.numsurfs); CalculateModelBounds(xmodel); @@ -894,7 +828,7 @@ namespace PrintError(xmodel, "Collision lod is not a valid lod"); return false; } - xmodel.collLod = static_cast(jXModel.collLod.value()); + xmodel.collLod = static_cast(jXModel.collLod.value()); } else xmodel.collLod = -1; @@ -915,6 +849,25 @@ namespace xmodel.physPreset = nullptr; } +#if defined(FEATURE_IW5) + if (jXModel.physCollmap) + { + auto* physCollmap = m_manager.LoadDependency(jXModel.physCollmap.value()); + if (!physCollmap) + { + PrintError(xmodel, "Could not find phys collmap"); + return false; + } + m_dependencies.emplace(physCollmap); + xmodel.physCollmap = physCollmap->Asset(); + } + else + { + xmodel.physCollmap = nullptr; + } +#endif + +#if defined(FEATURE_T5) || defined(FEATURE_T6) if (jXModel.physConstraints) { auto* physConstraints = m_manager.LoadDependency(jXModel.physConstraints.value()); @@ -930,12 +883,16 @@ namespace { xmodel.physConstraints = nullptr; } +#endif xmodel.flags = jXModel.flags; + +#ifdef FEATURE_T6 xmodel.lightingOriginOffset.x = jXModel.lightingOriginOffset.x; xmodel.lightingOriginOffset.y = jXModel.lightingOriginOffset.y; xmodel.lightingOriginOffset.z = jXModel.lightingOriginOffset.z; xmodel.lightingOriginRange = jXModel.lightingOriginRange; +#endif return true; } @@ -950,18 +907,14 @@ namespace PartClassificationState& m_part_classification_state; std::set& m_dependencies; }; -} // namespace -namespace T6 -{ - bool LoadXModelAsJson( - std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies) + bool LoadXModel(std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies) { std::set dependenciesSet; - JsonLoader loader(stream, *memory, *manager, dependenciesSet); + XModelLoader loader(stream, *memory, *manager, dependenciesSet); dependencies.assign(dependenciesSet.cbegin(), dependenciesSet.cend()); return loader.Load(xmodel); } -} // namespace T6 +} // namespace GAME diff --git a/src/ObjLoading/XModel/XModelLoader.h b/src/ObjLoading/XModel/XModelLoader.h deleted file mode 100644 index 8aeca27a9..000000000 --- a/src/ObjLoading/XModel/XModelLoader.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "XModel/XModelCommon.h" - -#include - -class XModelLoader -{ -public: - XModelLoader() = default; - virtual ~XModelLoader() = default; - XModelLoader(const XModelLoader& other) = default; - XModelLoader(XModelLoader&& other) noexcept = default; - XModelLoader& operator=(const XModelLoader& other) = default; - XModelLoader& operator=(XModelLoader&& other) noexcept = default; - - virtual std::unique_ptr Load() = 0; -}; diff --git a/src/ObjLoading/XModel/XModelLoader.h.template b/src/ObjLoading/XModel/XModelLoader.h.template new file mode 100644 index 000000000..3b9d07290 --- /dev/null +++ b/src/ObjLoading/XModel/XModelLoader.h.template @@ -0,0 +1,19 @@ +#options GAME (IW5, T5, T6) + +#filename "Game/" + GAME + "/XModel/XModelLoader" + GAME + ".h" + +#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\"" + +#pragma once + +#include "AssetLoading/IAssetLoadingManager.h" +#include GAME_HEADER +#include "Utils/MemoryManager.h" + +#include +#include + +namespace GAME +{ + bool LoadXModel(std::istream& stream, XModel& xmodel, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies); +} diff --git a/src/ObjWriting.lua b/src/ObjWriting.lua index 6ca48e197..39f15dca6 100644 --- a/src/ObjWriting.lua +++ b/src/ObjWriting.lua @@ -50,6 +50,8 @@ function ObjWriting:project() path.join(folder, "ObjWriting") } } + + useSourceTemplating("ObjWriting") self:include(includes) Utils:include(includes) diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.cpp new file mode 100644 index 000000000..c4efb8f9d --- /dev/null +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.cpp @@ -0,0 +1,48 @@ +#include "AssetDumperMaterial.h" + +#include "Game/IW5/Material/JsonMaterialWriter.h" +#include "Game/IW5/Material/MaterialConstantZoneState.h" + +#include +#include +#include + +using namespace IW5; + +std::string AssetDumperMaterial::GetFileNameForAsset(const std::string& assetName) +{ + std::string sanitizedFileName(assetName); + if (sanitizedFileName[0] == '*') + { + std::ranges::replace(sanitizedFileName, '*', '_'); + const auto parenthesisPos = sanitizedFileName.find('('); + if (parenthesisPos != std::string::npos) + sanitizedFileName.erase(parenthesisPos); + sanitizedFileName = "generated/" + sanitizedFileName; + } + + return std::format("materials/{}.json", sanitizedFileName); +} + +bool AssetDumperMaterial::ShouldDump(XAssetInfo* asset) +{ + return true; +} + +void AssetDumperMaterial::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto assetFile = context.OpenAssetFile(GetFileNameForAsset(asset->m_name)); + + if (!assetFile) + return; + + DumpMaterialAsJson(*assetFile, asset->Asset(), context); +} + +void AssetDumperMaterial::DumpPool(AssetDumpingContext& context, AssetPool* pool) +{ + auto* materialConstantState = context.GetZoneAssetDumperState(); + materialConstantState->ExtractNamesFromZone(); + + AbstractAssetDumper::DumpPool(context, pool); +} diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.h b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.h new file mode 100644 index 000000000..04c8e914e --- /dev/null +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperMaterial.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW5/IW5.h" + +#include + +namespace IW5 +{ + class AssetDumperMaterial final : public AbstractAssetDumper + { + static std::string GetFileNameForAsset(const std::string& assetName); + + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + + public: + void DumpPool(AssetDumpingContext& context, AssetPool* pool) override; + }; +} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp index dd1bd9d22..15d5fd5b6 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp @@ -545,8 +545,8 @@ namespace IW5 for (auto i = 0u; i < originalKnotCount; i++) { auto& knot = graph.knots[i]; - knot.x = originalKnots[i][0]; - knot.y = originalKnots[i][1]; + knot.x = originalKnots[i].x; + knot.y = originalKnots[i].y; } return graph; diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp index 6a0af2276..bd882e413 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperXModel.cpp @@ -1,506 +1,9 @@ #include "AssetDumperXModel.h" -#include "Game/IW5/CommonIW5.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 "Game/IW5/XModel/XModelDumperIW5.h" using namespace IW5; -namespace -{ - 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 = boneNum - static_cast(model->parentList[boneNum - model->numRootBones]); - else - bone.parentIndex = std::nullopt; - - 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 = { - 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 = {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 = { - 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]; - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - Common::Vec3UnpackUnitVec(v.normal, vertex.normal); - Common::Vec4UnpackGfxColor(v.color, vertex.color); - Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv); - - 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); - } - - float BoneWeight16(const uint16_t value) - { - return static_cast(value) / static_cast(std::numeric_limits::max()); - } - - 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 = weightOffset; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{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 = weightOffset; - const auto boneIndex0 = 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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat); - const auto boneWeight3 = BoneWeight16(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(0, 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] != ','; @@ -508,5 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - DumpXModelSurfs(context, asset); + DumpXModel(context, asset); } diff --git a/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.cpp b/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.cpp new file mode 100644 index 000000000..66ec64b46 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.cpp @@ -0,0 +1,293 @@ +#include "JsonMaterialWriter.h" + +#include "Game/IW5/CommonIW5.h" +#include "Game/IW5/Material/JsonMaterial.h" +#include "Impl/Base64.h" +#include "MaterialConstantZoneState.h" + +#include +#include + +using namespace nlohmann; +using namespace IW5; + +namespace +{ + class JsonDumper + { + public: + JsonDumper(AssetDumpingContext& context, std::ostream& stream) + : m_stream(stream), + m_material_constants(*context.GetZoneAssetDumperState()) + { + } + + void Dump(const Material* material) const + { + JsonMaterial jsonMaterial; + CreateJsonMaterial(jsonMaterial, *material); + json jRoot = jsonMaterial; + + jRoot["_type"] = "material"; + jRoot["_game"] = "iw5"; + 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; + } + + static void CreateJsonGameFlags(JsonMaterial& jMaterial, const unsigned gameFlags) + { + jMaterial.gameFlags.clear(); + for (auto i = 0u; i < sizeof(gameFlags) * 8u; i++) + { + const auto flag = static_cast(1 << i); + + if (gameFlags & flag) + jMaterial.gameFlags.emplace_back(flag); + } + } + + static void CreateJsonSamplerState(JsonSamplerState& jSamplerState, const MaterialTextureDefSamplerState& samplerState) + { + jSamplerState.filter = static_cast(samplerState.filter); + jSamplerState.mipMap = static_cast(samplerState.mipMap); + jSamplerState.clampU = samplerState.clampU; + jSamplerState.clampV = samplerState.clampV; + jSamplerState.clampW = samplerState.clampW; + } + + static void CreateJsonWater(JsonWater& jWater, const water_t& water) + { + jWater.floatTime = water.writable.floatTime; + jWater.m = water.M; + jWater.n = water.N; + jWater.lx = water.Lx; + jWater.lz = water.Lz; + jWater.gravity = water.gravity; + jWater.windvel = water.windvel; + jWater.winddir[0] = water.winddir[0]; + jWater.winddir[1] = water.winddir[1]; + jWater.amplitude = water.amplitude; + jWater.codeConstant[0] = water.codeConstant[0]; + jWater.codeConstant[1] = water.codeConstant[1]; + jWater.codeConstant[2] = water.codeConstant[2]; + jWater.codeConstant[3] = water.codeConstant[3]; + + if (water.H0) + { + const auto count = water.M * water.N; + jWater.h0 = base64::EncodeBase64(water.H0, sizeof(complex_s) * count); + } + + if (water.wTerm) + { + const auto count = water.M * water.N; + jWater.wTerm = base64::EncodeBase64(water.wTerm, sizeof(float) * count); + } + } + + void CreateJsonTexture(JsonTexture& jTextureDef, const MaterialTextureDef& textureDef) const + { + std::string textureDefName; + if (m_material_constants.GetTextureDefName(textureDef.nameHash, textureDefName)) + { + jTextureDef.name = textureDefName; + } + else + { + jTextureDef.nameHash = textureDef.nameHash; + jTextureDef.nameStart = std::string(1u, textureDef.nameStart); + jTextureDef.nameEnd = std::string(1u, textureDef.nameEnd); + } + + jTextureDef.semantic = static_cast(textureDef.semantic); + + CreateJsonSamplerState(jTextureDef.samplerState, textureDef.samplerState); + + if (textureDef.semantic == TS_WATER_MAP) + { + if (textureDef.u.water) + { + const auto& water = *textureDef.u.water; + if (water.image && water.image->name) + jTextureDef.image = AssetName(water.image->name); + + JsonWater jWater; + CreateJsonWater(jWater, water); + + jTextureDef.water = std::move(jWater); + } + } + else + { + if (textureDef.u.image && textureDef.u.image->name) + jTextureDef.image = AssetName(textureDef.u.image->name); + } + } + + void CreateJsonConstant(JsonConstant& jConstantDef, const MaterialConstantDef& constantDef) const + { + const auto fragmentLength = strnlen(constantDef.name, std::extent_v); + const std::string nameFragment(constantDef.name, fragmentLength); + std::string knownConstantName; + + if (fragmentLength < std::extent_v || Common::R_HashString(nameFragment.c_str(), 0) == constantDef.nameHash) + { + jConstantDef.name = nameFragment; + } + else if (m_material_constants.GetConstantName(constantDef.nameHash, knownConstantName)) + { + jConstantDef.name = knownConstantName; + } + else + { + jConstantDef.nameHash = constantDef.nameHash; + jConstantDef.nameFragment = nameFragment; + } + + jConstantDef.literal = std::vector({ + constantDef.literal.x, + constantDef.literal.y, + constantDef.literal.z, + constantDef.literal.w, + }); + } + + static void CreateJsonStencil(JsonStencil& jStencil, const unsigned pass, const unsigned fail, const unsigned zFail, const unsigned func) + { + jStencil.pass = static_cast(pass); + jStencil.fail = static_cast(fail); + jStencil.zfail = static_cast(zFail); + jStencil.func = static_cast(func); + } + + static void CreateJsonStateBitsTableEntry(JsonStateBitsTableEntry& jStateBitsTableEntry, const GfxStateBits& stateBitsTableEntry) + { + const auto& structured = stateBitsTableEntry.loadBits.structured; + + jStateBitsTableEntry.srcBlendRgb = static_cast(structured.srcBlendRgb); + jStateBitsTableEntry.dstBlendRgb = static_cast(structured.dstBlendRgb); + jStateBitsTableEntry.blendOpRgb = static_cast(structured.blendOpRgb); + + assert(structured.alphaTestDisabled || structured.alphaTest == GFXS_ALPHA_TEST_GT_0 || structured.alphaTest == GFXS_ALPHA_TEST_LT_128 + || structured.alphaTest == GFXS_ALPHA_TEST_GE_128); + if (structured.alphaTestDisabled) + jStateBitsTableEntry.alphaTest = JsonAlphaTest::DISABLED; + else if (structured.alphaTest == GFXS_ALPHA_TEST_GT_0) + jStateBitsTableEntry.alphaTest = JsonAlphaTest::GT0; + else if (structured.alphaTest == GFXS_ALPHA_TEST_LT_128) + jStateBitsTableEntry.alphaTest = JsonAlphaTest::LT128; + else if (structured.alphaTest == GFXS_ALPHA_TEST_GE_128) + jStateBitsTableEntry.alphaTest = JsonAlphaTest::GE128; + else + jStateBitsTableEntry.alphaTest = JsonAlphaTest::INVALID; + + assert(structured.cullFace == GFXS_CULL_NONE || structured.cullFace == GFXS_CULL_BACK || structured.cullFace == GFXS_CULL_FRONT); + if (structured.cullFace == GFXS_CULL_NONE) + jStateBitsTableEntry.cullFace = JsonCullFace::NONE; + else if (structured.cullFace == GFXS_CULL_BACK) + jStateBitsTableEntry.cullFace = JsonCullFace::BACK; + else if (structured.cullFace == GFXS_CULL_FRONT) + jStateBitsTableEntry.cullFace = JsonCullFace::FRONT; + else + jStateBitsTableEntry.cullFace = JsonCullFace::INVALID; + + jStateBitsTableEntry.srcBlendAlpha = static_cast(structured.srcBlendAlpha); + jStateBitsTableEntry.dstBlendAlpha = static_cast(structured.dstBlendAlpha); + jStateBitsTableEntry.blendOpAlpha = static_cast(structured.blendOpAlpha); + jStateBitsTableEntry.colorWriteRgb = structured.colorWriteRgb; + jStateBitsTableEntry.colorWriteAlpha = structured.colorWriteAlpha; + jStateBitsTableEntry.gammaWrite = structured.gammaWrite; + jStateBitsTableEntry.polymodeLine = structured.polymodeLine; + jStateBitsTableEntry.depthWrite = structured.depthWrite; + + assert(structured.depthTestDisabled || structured.depthTest == GFXS_DEPTHTEST_ALWAYS || structured.depthTest == GFXS_DEPTHTEST_LESS + || structured.depthTest == GFXS_DEPTHTEST_EQUAL || structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + if (structured.depthTestDisabled) + jStateBitsTableEntry.depthTest = JsonDepthTest::DISABLED; + else if (structured.depthTest == GFXS_DEPTHTEST_ALWAYS) + jStateBitsTableEntry.depthTest = JsonDepthTest::ALWAYS; + else if (structured.depthTest == GFXS_DEPTHTEST_LESS) + jStateBitsTableEntry.depthTest = JsonDepthTest::LESS; + else if (structured.depthTest == GFXS_DEPTHTEST_EQUAL) + jStateBitsTableEntry.depthTest = JsonDepthTest::EQUAL; + else if (structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL) + jStateBitsTableEntry.depthTest = JsonDepthTest::LESS_EQUAL; + else + jStateBitsTableEntry.depthTest = JsonDepthTest::INVALID; + + jStateBitsTableEntry.polygonOffset = static_cast(structured.polygonOffset); + + if (structured.stencilFrontEnabled) + { + JsonStencil jStencilFront; + CreateJsonStencil( + jStencilFront, structured.stencilFrontPass, structured.stencilFrontFail, structured.stencilFrontZFail, structured.stencilFrontFunc); + jStateBitsTableEntry.stencilFront = jStencilFront; + } + + if (structured.stencilBackEnabled) + { + JsonStencil jStencilBack; + CreateJsonStencil( + jStencilBack, structured.stencilBackPass, structured.stencilBackFail, structured.stencilBackZFail, structured.stencilBackFunc); + jStateBitsTableEntry.stencilBack = jStencilBack; + } + } + + void CreateJsonMaterial(JsonMaterial& jMaterial, const Material& material) const + { + CreateJsonGameFlags(jMaterial, material.info.gameFlags); + jMaterial.sortKey = material.info.sortKey; + + jMaterial.textureAtlas = JsonTextureAtlas(); + jMaterial.textureAtlas->rows = material.info.textureAtlasRowCount; + jMaterial.textureAtlas->columns = material.info.textureAtlasColumnCount; + + jMaterial.surfaceTypeBits = material.info.surfaceTypeBits; + + jMaterial.stateBitsEntry.resize(std::extent_v); + for (auto i = 0u; i < std::extent_v; i++) + jMaterial.stateBitsEntry[i] = material.stateBitsEntry[i]; + + jMaterial.stateFlags = material.stateFlags; + jMaterial.cameraRegion = static_cast(material.cameraRegion); + + if (material.techniqueSet && material.techniqueSet->name) + jMaterial.techniqueSet = AssetName(material.techniqueSet->name); + + jMaterial.textures.resize(material.textureCount); + for (auto i = 0u; i < material.textureCount; i++) + CreateJsonTexture(jMaterial.textures[i], material.textureTable[i]); + + jMaterial.constants.resize(material.constantCount); + for (auto i = 0u; i < material.constantCount; i++) + CreateJsonConstant(jMaterial.constants[i], material.constantTable[i]); + + jMaterial.stateBits.resize(material.stateBitsCount); + for (auto i = 0u; i < material.stateBitsCount; i++) + CreateJsonStateBitsTableEntry(jMaterial.stateBits[i], material.stateBitsTable[i]); + } + + std::ostream& m_stream; + const MaterialConstantZoneState& m_material_constants; + }; +} // namespace + +namespace IW5 +{ + void DumpMaterialAsJson(std::ostream& stream, const Material* material, AssetDumpingContext& context) + { + const JsonDumper dumper(context, stream); + dumper.Dump(material); + } +} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.h b/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.h new file mode 100644 index 000000000..c78692865 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Material/JsonMaterialWriter.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Dumping/AssetDumpingContext.h" +#include "Game/IW5/IW5.h" + +#include + +namespace IW5 +{ + void DumpMaterialAsJson(std::ostream& stream, const Material* material, AssetDumpingContext& context); +} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.cpp b/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.cpp new file mode 100644 index 000000000..033421f8e --- /dev/null +++ b/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.cpp @@ -0,0 +1,236 @@ +#include "MaterialConstantZoneState.h" + +#include "Game/IW5/CommonIW5.h" +#include "Game/IW5/GameAssetPoolIW5.h" +#include "Game/IW5/GameIW5.h" +#include "ObjWriting.h" + +namespace IW5 +{ + const char* KNOWN_CONSTANT_NAMES[]{ + "worldViewProjectionMatrix", + "worldViewMatrix2", + "worldViewMatrix1", + "worldViewMatrix", + "worldOutdoorLookupMatrix", + "worldMatrix", + "waterColor", + "viewportDimensions", + "viewProjectionMatrix", + "uvScale", + "uvAnimParms", + "thermalColorOffset", + "sunShadowmapPixelAdjust", + "ssaoParms", + "spotShadowmapPixelAdjust", + "shadowmapSwitchPartition", + "shadowmapScale", + "shadowmapPolygonOffset", + "shadowLookupMatrix", + "renderTargetSize", + "renderSourceSize", + "projectionMatrix", + "playlistPopulationParams", + "pixelCostFracs", + "pixelCostDecode", + "particleCloudSparkColor2", + "particleCloudSparkColor1", + "particleCloudSparkColor0", + "particleCloudMatrix2", + "particleCloudMatrix1", + "particleCloudMatrix", + "particleCloudColor", + "outdoorFeatherParms", + "oceanUVAnimParmPaintedFoam", + "oceanUVAnimParmOctave2", + "oceanUVAnimParmOctave1", + "oceanUVAnimParmOctave0", + "oceanUVAnimParmFoam", + "oceanUVAnimParmDetail1", + "oceanUVAnimParmDetail0", + "oceanScrollParms", + "oceanMiscParms", + "oceanFoamParms", + "oceanAmplitude", + "materialColor", + "lightprobeAmbient", + "lightingLookupScale", + "lightSpotFactors", + "lightSpotDir", + "lightSpecular", + "lightPosition", + "lightFalloffPlacement", + "lightDiffuse", + "inverseWorldViewMatrix", + "inverseViewProjectionMatrix", + "inverseTransposeWorldViewMatrix", + "heatMapDetail", + "glowSetup", + "glowApply", + "gameTime", + "fullscreenDistortion", + "fogSunDir", + "fogSunConsts", + "fogSunColorLinear", + "fogSunColorGamma", + "fogConsts", + "fogColorLinear", + "fogColorGamma", + "flagParms", + "filterTap", + "featherParms", + "falloffParms", + "falloffEndColor", + "falloffBeginColor", + "fadeEffect", + "eyeOffsetParms", + "eyeOffset", + "envMapParms", + "dustTint", + "dustParms", + "dustEyeParms", + "dofRowDelta", + "dofLerpScale", + "dofLerpBias", + "dofEquationViewModelAndFarBlur", + "dofEquationScene", + "distortionScale", + "detailScale", + "depthFromClip", + "debugBumpmap", + "colorTintQuadraticDelta", + "colorTintDelta", + "colorTintBase", + "colorSaturationR", + "colorSaturationG", + "colorSaturationB", + "colorObjMin", + "colorObjMax", + "colorMatrixR", + "colorMatrixG", + "colorMatrixB", + "colorBias", + "codeMeshArg", + "clipSpaceLookupScale", + "clipSpaceLookupOffset", + "baseLightingCoords", + }; + + const char* KNOWN_TEXTURE_DEF_NAMES[]{ + "attenuation", + "attenuationSampler", + "cinematicA", + "cinematicASampler", + "cinematicCb", + "cinematicCbSampler", + "cinematicCr", + "cinematicCrSampler", + "cinematicY", + "cinematicYSampler", + "colorMap", + "colorMap1", + "colorMap2", + "colorMapPostSun", + "colorMapPostSunSampler", + "colorMapSampler", + "colorMapSampler1", + "colorMapSampler2", + "cucoloris", + "cucolorisSampler", + "detailMap", + "detailMapSampler", + "dust", + "dustSampler", + "fadeMap", + "fadeMapSampler", + "floatZ", + "floatZSampler", + "grainMap", + "grainMapSampler", + "halfParticleColor", + "halfParticleColorSampler", + "halfParticleDepth", + "halfParticleDepthSampler", + "heatmap", + "heatmapSampler", + "lightmapPrimary", + "lightmapSamplerPrimary", + "lightmapSamplerSecondary", + "lightmapSecondary", + "lookupMap", + "lookupMapSampler", + "modelLighting", + "modelLightingSampler", + "normalMap", + "normalMapSampler", + "oceanColorRamp", + "oceanColorRampSampler", + "oceanDetailNormal", + "oceanDetailNormalSampler", + "oceanDisplacement", + "oceanDisplacementSampler", + "oceanEnv", + "oceanEnvSampler", + "oceanFoam", + "oceanFoamSampler", + "oceanHeightNormal", + "oceanHeightNormalSampler", + "oceanPaintedFoam", + "oceanPaintedFoamSampler", + "outdoorMap", + "outdoorMapSampler", + "population", + "populationSampler", + "reflectionProbe", + "reflectionProbeSampler", + "shadowmapSamplerSpot", + "shadowmapSamplerSun", + "shadowmapSpot", + "shadowmapSun", + "skyMap", + "skyMapSampler", + "specularMap", + "specularMapSampler", + "ssao", + "ssaoSampler", + "worldMap", + "worldMapSampler", + }; + + void MaterialConstantZoneState::ExtractNamesFromZoneInternal() + { + for (const auto* zone : g_GameIW5.GetZones()) + { + const auto* iw5AssetPools = dynamic_cast(zone->m_pools.get()); + if (!iw5AssetPools) + return; + + for (const auto* vertexShaderAsset : *iw5AssetPools->m_material_vertex_shader) + { + const auto* vertexShader = vertexShaderAsset->Asset(); + if (ShouldDumpFromStruct(vertexShader)) + ExtractNamesFromShader(vertexShader->prog.loadDef.program, static_cast(vertexShader->prog.loadDef.programSize) * sizeof(uint32_t)); + } + + for (const auto* pixelShaderAsset : *iw5AssetPools->m_material_pixel_shader) + { + const auto* pixelShader = pixelShaderAsset->Asset(); + if (ShouldDumpFromStruct(pixelShader)) + ExtractNamesFromShader(pixelShader->prog.loadDef.program, static_cast(pixelShader->prog.loadDef.programSize) * sizeof(uint32_t)); + } + } + } + + void MaterialConstantZoneState::AddStaticKnownNames() + { + for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES) + AddConstantName(knownConstantName); + for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES) + AddTextureDefName(knownTextureDefName); + } + + unsigned MaterialConstantZoneState::HashString(const std::string& str) + { + return Common::R_HashString(str.c_str()); + } +} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.h b/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.h new file mode 100644 index 000000000..d8a333508 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Material/MaterialConstantZoneState.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Material/AbstractMaterialConstantZoneState.h" + +#include + +namespace IW5 +{ + class MaterialConstantZoneState final : public AbstractMaterialConstantZoneStateDx9 + { + protected: + void ExtractNamesFromZoneInternal() override; + void AddStaticKnownNames() override; + unsigned HashString(const std::string& str) override; + }; +} // namespace IW5 diff --git a/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp b/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp index ea18d89cd..f102023cc 100644 --- a/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/ZoneDumperIW5.cpp @@ -5,6 +5,7 @@ #include "AssetDumpers/AssetDumperLeaderboardDef.h" #include "AssetDumpers/AssetDumperLoadedSound.h" #include "AssetDumpers/AssetDumperLocalizeEntry.h" +#include "AssetDumpers/AssetDumperMaterial.h" #include "AssetDumpers/AssetDumperMenuDef.h" #include "AssetDumpers/AssetDumperMenuList.h" #include "AssetDumpers/AssetDumperRawFile.h" @@ -39,7 +40,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts, ASSET_TYPE_XANIMPARTS) // DUMP_ASSET_POOL(AssetDumperXModelSurfs, m_xmodel_surfs, ASSET_TYPE_XMODEL_SURFS) DUMP_ASSET_POOL(AssetDumperXModel, m_xmodel, ASSET_TYPE_XMODEL) - // DUMP_ASSET_POOL(AssetDumperMaterial, m_material, ASSET_TYPE_MATERIAL) + DUMP_ASSET_POOL(AssetDumperMaterial, m_material, ASSET_TYPE_MATERIAL) // DUMP_ASSET_POOL(AssetDumperMaterialPixelShader, m_material_pixel_shader, ASSET_TYPE_PIXELSHADER) // DUMP_ASSET_POOL(AssetDumperMaterialVertexShader, m_material_vertex_shader, ASSET_TYPE_VERTEXSHADER) // DUMP_ASSET_POOL(AssetDumperMaterialVertexDeclaration, m_material_vertex_decl, ASSET_TYPE_VERTEXDECL) diff --git a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp index b7d80214f..8eb958bb9 100644 --- a/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T5/AssetDumpers/AssetDumperXModel.cpp @@ -1,519 +1,9 @@ #include "AssetDumperXModel.h" -#include "Game/T5/CommonT5.h" -#include "ObjWriting.h" -#include "Utils/DistinctMapper.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 "Game/T5/XModel/XModelDumperT5.h" 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 = boneNum - static_cast(model->parentList[boneNum - model->numRootBones]); - else - bone.parentIndex = std::nullopt; - - 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 = { - 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 = {0, 0, 0, 1}; - } - else - { - const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3]; - bone.localOffset[0] = trans[0]; - bone.localOffset[1] = trans[1]; - bone.localOffset[2] = trans[2]; - - const auto& quat = model->quats[boneNum - model->numRootBones]; - bone.localRotation = { - QuatInt16::ToFloat(quat.v[0]), - QuatInt16::ToFloat(quat.v[1]), - QuatInt16::ToFloat(quat.v[2]), - QuatInt16::ToFloat(quat.v[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]; - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz[0]; - vertex.coordinates[1] = v.xyz[1]; - vertex.coordinates[2] = v.xyz[2]; - Common::Vec3UnpackUnitVec(v.normal, vertex.normal); - Common::Vec4UnpackGfxColor(v.color, vertex.color); - Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv); - - 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); - } - - float BoneWeight16(const uint16_t value) - { - return static_cast(value) / static_cast(std::numeric_limits::max()); - } - - 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 = weightOffset; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{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 = weightOffset; - const auto boneIndex0 = 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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat); - const auto boneWeight3 = BoneWeight16(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(0, 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] != ','; @@ -521,5 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - DumpXModelSurfs(context, asset); + DumpXModel(context, asset); } diff --git a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp index d5add6b35..24dc56dcc 100644 --- a/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp +++ b/src/ObjWriting/Game/T6/AssetDumpers/AssetDumperXModel.cpp @@ -1,600 +1,9 @@ #include "AssetDumperXModel.h" -#include "Game/T6/CommonT6.h" -#include "Game/T6/XModel/JsonXModelWriter.h" -#include "ObjWriting.h" -#include "Utils/DistinctMapper.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 "Game/T6/XModel/XModelDumperT6.h" 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; - } - - bool HasDefaultArmature(const XModel* model, const unsigned lod) - { - if (model->numRootBones != 1 || model->numBones != 1) - return false; - - const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; - const auto surfCount = model->lodInfo[lod].numsurfs; - - if (!surfs) - return true; - - for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) - { - const auto& surface = surfs[surfIndex]; - - if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend) - return false; - - const auto& vertList = surface.vertList[0]; - if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount) - return false; - } - - return true; - } - - void OmitDefaultArmature(XModelCommon& common) - { - common.m_bones.clear(); - common.m_bone_weight_data.weights.clear(); - common.m_vertex_bone_weights.resize(common.m_vertices.size()); - for (auto& vertexWeights : common.m_vertex_bone_weights) - { - vertexWeights.weightOffset = 0u; - vertexWeights.weightCount = 0u; - } - } - - 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 = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); - else - bone.parentIndex = std::nullopt; - - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; - - const auto& baseMat = model->baseMat[boneNum]; - bone.globalOffset[0] = baseMat.trans.x; - bone.globalOffset[1] = baseMat.trans.y; - bone.globalOffset[2] = baseMat.trans.z; - bone.globalRotation = { - baseMat.quat.x, - baseMat.quat.y, - baseMat.quat.z, - baseMat.quat.w, - }; - - if (boneNum < model->numRootBones) - { - bone.localOffset[0] = 0; - bone.localOffset[1] = 0; - bone.localOffset[2] = 0; - bone.localRotation = {0, 0, 0, 1}; - } - else - { - const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3]; - bone.localOffset[0] = trans[0]; - bone.localOffset[1] = trans[1]; - bone.localOffset[2] = trans[2]; - - const auto& quat = model->quats[boneNum - model->numRootBones]; - bone.localRotation = { - QuatInt16::ToFloat(quat.v[0]), - QuatInt16::ToFloat(quat.v[1]), - QuatInt16::ToFloat(quat.v[2]), - QuatInt16::ToFloat(quat.v[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]; - - XModelVertex vertex{}; - vertex.coordinates[0] = v.xyz.x; - vertex.coordinates[1] = v.xyz.y; - vertex.coordinates[2] = v.xyz.z; - Common::Vec3UnpackUnitVec(v.normal, vertex.normal); - Common::Vec4UnpackGfxColor(v.color, vertex.color); - Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv); - - 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); - } - - float BoneWeight16(const uint16_t value) - { - return static_cast(value) / static_cast(std::numeric_limits::max()); - } - - 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 = weightOffset; - - weightCollection.weights[weightOffset++] = XModelBoneWeight{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 = weightOffset; - const auto boneIndex0 = 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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(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 = weightOffset; - const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); - const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); - const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); - const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); - const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); - const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat); - const auto boneWeight3 = BoneWeight16(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(0, 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.i[0] + surface.baseVertIndex; - face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex; - face.vertexIndex[2] = tri.i[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); - AddXModelMaterials(out, materialMapper, model); - AddXModelObjects(out, model, lod, materialMapper); - AddXModelVertices(out, model, lod); - AddXModelFaces(out, model, lod); - - if (!HasDefaultArmature(model, lod)) - { - AddXModelBones(out, context, model); - AddXModelVertexBoneWeights(out, model, lod); - } - else - { - OmitDefaultArmature(out); - } - } - - 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; - } - } - } - - void DumpXModel(AssetDumpingContext& context, XAssetInfo* asset) - { - const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name)); - if (!assetFile) - return; - - DumpXModelAsJson(*assetFile, asset->Asset(), context); - } -} // namespace - bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) { return !asset->m_name.empty() && asset->m_name[0] != ','; @@ -602,6 +11,5 @@ bool AssetDumperXModel::ShouldDump(XAssetInfo* asset) void AssetDumperXModel::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { - DumpXModelSurfs(context, asset); DumpXModel(context, asset); } diff --git a/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.cpp b/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.cpp index f92b37833..0219063e6 100644 --- a/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.cpp +++ b/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.cpp @@ -4,16 +4,9 @@ #include "Game/T6/GameAssetPoolT6.h" #include "Game/T6/GameT6.h" #include "ObjWriting.h" -#include "Shader/D3D11ShaderAnalyser.h" - -#include namespace T6 { - static constexpr const char* SAMPLER_STR = "Sampler"; - static constexpr const char* GLOBALS_CBUFFER_NAME = "$Globals"; - static constexpr const char* PER_OBJECT_CONSTS_CBUFFER_NAME = "PerObjectConsts"; - const char* KNOWN_CONSTANT_NAMES[]{ "AngularVelocityScale", "AnimSpeed", @@ -478,15 +471,8 @@ namespace T6 "ui3dSampler", }; - void MaterialConstantZoneState::ExtractNamesFromZone() + void MaterialConstantZoneState::ExtractNamesFromZoneInternal() { - if (ObjWriting::Configuration.Verbose) - std::cout << "Building material constant name lookup...\n"; - - const auto begin = std::chrono::high_resolution_clock::now(); - - AddStaticKnownNames(); - for (const auto* zone : g_GameT6.GetZones()) { const auto* t6AssetPools = dynamic_cast(zone->m_pools.get()); @@ -504,49 +490,18 @@ namespace T6 } } } - - const auto end = std::chrono::high_resolution_clock::now(); - - if (ObjWriting::Configuration.Verbose) - { - const auto durationInMs = std::chrono::duration_cast(end - begin); - std::cout << "Built material constant name lookup in " << durationInMs.count() << "ms: " << m_constant_names_from_shaders.size() - << " constant names; " << m_texture_def_names_from_shaders.size() << " texture def names\n"; - } - } - - bool MaterialConstantZoneState::GetConstantName(const unsigned hash, std::string& constantName) const - { - const auto existingConstantName = m_constant_names_from_shaders.find(hash); - if (existingConstantName != m_constant_names_from_shaders.end()) - { - constantName = existingConstantName->second; - return true; - } - - return false; } - bool MaterialConstantZoneState::GetTextureDefName(const unsigned hash, std::string& textureDefName) const + unsigned MaterialConstantZoneState::HashString(const std::string& str) { - const auto existingTextureDefName = m_texture_def_names_from_shaders.find(hash); - if (existingTextureDefName != m_texture_def_names_from_shaders.end()) - { - textureDefName = existingTextureDefName->second; - return true; - } - - return false; + return Common::R_HashString(str.c_str()); } void MaterialConstantZoneState::ExtractNamesFromTechnique(const MaterialTechnique* technique) { - const auto existingTechnique = m_dumped_techniques.find(technique); - if (existingTechnique != m_dumped_techniques.end()) + if (!ShouldDumpFromStruct(technique)) return; - m_dumped_techniques.emplace(technique); - for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) { const auto& pass = technique->passArray[passIndex]; @@ -559,54 +514,6 @@ namespace T6 } } - void MaterialConstantZoneState::ExtractNamesFromShader(const char* shader, const size_t shaderSize) - { - const auto shaderInfo = d3d11::ShaderAnalyser::GetShaderInfo(reinterpret_cast(shader), shaderSize); - if (!shaderInfo) - return; - - const auto globalsConstantBuffer = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers), - [](const d3d11::ConstantBuffer& constantBuffer) - { - return constantBuffer.m_name == GLOBALS_CBUFFER_NAME; - }); - - const auto perObjectConsts = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers), - [](const d3d11::ConstantBuffer& constantBuffer) - { - return constantBuffer.m_name == PER_OBJECT_CONSTS_CBUFFER_NAME; - }); - - if (globalsConstantBuffer != shaderInfo->m_constant_buffers.end()) - { - for (const auto& variable : globalsConstantBuffer->m_variables) - AddConstantName(variable.m_name); - } - - if (perObjectConsts != shaderInfo->m_constant_buffers.end()) - { - for (const auto& variable : perObjectConsts->m_variables) - AddConstantName(variable.m_name); - } - - for (const auto& boundResource : shaderInfo->m_bound_resources) - { - if (boundResource.m_type == d3d11::BoundResourceType::SAMPLER || boundResource.m_type == d3d11::BoundResourceType::TEXTURE) - { - if (AddTextureDefName(boundResource.m_name)) - { - const auto samplerPos = boundResource.m_name.rfind(SAMPLER_STR); - if (samplerPos != std::string::npos) - { - auto nameWithoutSamplerStr = boundResource.m_name; - nameWithoutSamplerStr.erase(samplerPos, std::char_traits::length(SAMPLER_STR)); - AddTextureDefName(std::move(nameWithoutSamplerStr)); - } - } - } - } - } - void MaterialConstantZoneState::AddStaticKnownNames() { for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES) @@ -614,23 +521,4 @@ namespace T6 for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES) AddTextureDefName(knownTextureDefName); } - - void MaterialConstantZoneState::AddConstantName(std::string constantName) - { - const auto hash = Common::R_HashString(constantName.c_str(), 0); - if (m_constant_names_from_shaders.contains(hash)) - return; - - m_constant_names_from_shaders.emplace(hash, std::move(constantName)); - } - - bool MaterialConstantZoneState::AddTextureDefName(std::string textureDefName) - { - const auto hash = Common::R_HashString(textureDefName.c_str(), 0); - if (m_texture_def_names_from_shaders.contains(hash)) - return false; - - m_texture_def_names_from_shaders.emplace(hash, std::move(textureDefName)); - return true; - } } // namespace T6 diff --git a/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.h b/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.h index 27f3f49e3..148169ebd 100644 --- a/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.h +++ b/src/ObjWriting/Game/T6/Material/MaterialConstantZoneState.h @@ -1,30 +1,18 @@ #pragma once -#include "Dumping/IZoneAssetDumperState.h" #include "Game/T6/T6.h" +#include "Material/AbstractMaterialConstantZoneState.h" #include -#include -#include namespace T6 { - class MaterialConstantZoneState final : public IZoneAssetDumperState + class MaterialConstantZoneState final : public AbstractMaterialConstantZoneStateDx11 { - public: - void ExtractNamesFromZone(); - bool GetConstantName(unsigned hash, std::string& constantName) const; - bool GetTextureDefName(unsigned hash, std::string& textureDefName) const; - - private: + protected: + void ExtractNamesFromZoneInternal() override; void ExtractNamesFromTechnique(const MaterialTechnique* technique); - void ExtractNamesFromShader(const char* shader, size_t shaderSize); - void AddStaticKnownNames(); - void AddConstantName(std::string constantName); - bool AddTextureDefName(std::string textureDefName); - - std::unordered_set m_dumped_techniques; - std::unordered_map m_constant_names_from_shaders; - std::unordered_map m_texture_def_names_from_shaders; + void AddStaticKnownNames() override; + unsigned HashString(const std::string& str) override; }; } // namespace T6 diff --git a/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp b/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp deleted file mode 100644 index 05104da26..000000000 --- a/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "JsonXModelWriter.h" - -#include "Game/T6/CommonT6.h" -#include "Game/T6/Json/JsonXModel.h" -#include "ObjWriting.h" - -#include -#include -#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; - } - - static const char* GetExtensionForModelByConfig() - { - switch (ObjWriting::Configuration.ModelOutputFormat) - { - case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: - return ".XMODEL_EXPORT"; - case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: - return ".OBJ"; - case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: - return ".GLTF"; - case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: - return ".GLB"; - default: - assert(false); - return ""; - } - } - - static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) - { - if (xmodel.collLod >= 0) - jXModel.collLod = xmodel.collLod; - - for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++) - { - JsonXModelLod lod; - lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig()); - lod.distance = xmodel.lodInfo[lodNumber].dist; - - jXModel.lods.emplace_back(std::move(lod)); - } - - 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 deleted file mode 100644 index f40f008d2..000000000 --- a/src/ObjWriting/Game/T6/XModel/JsonXModelWriter.h +++ /dev/null @@ -1,11 +0,0 @@ -#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/Material/AbstractMaterialConstantZoneState.cpp b/src/ObjWriting/Material/AbstractMaterialConstantZoneState.cpp new file mode 100644 index 000000000..bdbee0c2f --- /dev/null +++ b/src/ObjWriting/Material/AbstractMaterialConstantZoneState.cpp @@ -0,0 +1,164 @@ +#include "AbstractMaterialConstantZoneState.h" + +#include "ObjWriting.h" +#include "Shader/D3D11ShaderAnalyser.h" +#include "Shader/D3D9ShaderAnalyser.h" + +#include + +namespace +{ + constexpr const char* SAMPLER_STR = "Sampler"; + constexpr const char* GLOBALS_CBUFFER_NAME = "$Globals"; + constexpr const char* PER_OBJECT_CONSTS_CBUFFER_NAME = "PerObjectConsts"; +} // namespace + +void AbstractMaterialConstantZoneState::ExtractNamesFromZone() +{ + if (ObjWriting::Configuration.Verbose) + std::cout << "Building material constant name lookup...\n"; + + const auto begin = std::chrono::high_resolution_clock::now(); + + AddStaticKnownNames(); + + ExtractNamesFromZoneInternal(); + + const auto end = std::chrono::high_resolution_clock::now(); + + if (ObjWriting::Configuration.Verbose) + { + const auto durationInMs = std::chrono::duration_cast(end - begin); + std::cout << std::format("Built material constant name lookup in {}ms: {} constant names; {} texture def names\n", + durationInMs.count(), + m_constant_names_from_shaders.size(), + m_texture_def_names_from_shaders.size()); + } +} + +bool AbstractMaterialConstantZoneState::GetConstantName(const unsigned hash, std::string& constantName) const +{ + const auto existingConstantName = m_constant_names_from_shaders.find(hash); + if (existingConstantName != m_constant_names_from_shaders.end()) + { + constantName = existingConstantName->second; + return true; + } + + return false; +} + +bool AbstractMaterialConstantZoneState::GetTextureDefName(const unsigned hash, std::string& textureDefName) const +{ + const auto existingTextureDefName = m_texture_def_names_from_shaders.find(hash); + if (existingTextureDefName != m_texture_def_names_from_shaders.end()) + { + textureDefName = existingTextureDefName->second; + return true; + } + + return false; +} + +bool AbstractMaterialConstantZoneState::ShouldDumpFromStruct(const void* pStruct) +{ + const auto existingTextureDefName = m_dumped_structs.find(pStruct); + if (existingTextureDefName != m_dumped_structs.end()) + return false; + + m_dumped_structs.emplace(pStruct); + return true; +} + +void AbstractMaterialConstantZoneState::AddConstantName(const std::string& constantName) +{ + const auto hash = HashString(constantName); + if (m_constant_names_from_shaders.contains(hash)) + return; + + m_constant_names_from_shaders.emplace(hash, constantName); +} + +bool AbstractMaterialConstantZoneState::AddTextureDefName(const std::string& textureDefName) +{ + const auto hash = HashString(textureDefName); + if (m_texture_def_names_from_shaders.contains(hash)) + return false; + + m_texture_def_names_from_shaders.emplace(hash, textureDefName); + return true; +} + +void AbstractMaterialConstantZoneStateDx9::ExtractNamesFromShader(const void* shader, const size_t shaderSize) +{ + const auto shaderInfo = d3d9::ShaderAnalyser::GetShaderInfo(shader, shaderSize); + if (!shaderInfo) + return; + + for (const auto& constant : shaderInfo->m_constants) + { + if (constant.m_register_set == d3d9::RegisterSet::SAMPLER) + { + if (AddTextureDefName(constant.m_name)) + { + const auto samplerPos = constant.m_name.rfind(SAMPLER_STR); + if (samplerPos != std::string::npos) + { + auto nameWithoutSamplerStr = constant.m_name; + nameWithoutSamplerStr.erase(samplerPos, std::char_traits::length(SAMPLER_STR)); + AddTextureDefName(nameWithoutSamplerStr); + } + } + } + else + AddConstantName(constant.m_name); + } +} + +void AbstractMaterialConstantZoneStateDx11::ExtractNamesFromShader(const void* shader, const size_t shaderSize) +{ + const auto shaderInfo = d3d11::ShaderAnalyser::GetShaderInfo(static_cast(shader), shaderSize); + if (!shaderInfo) + return; + + const auto globalsConstantBuffer = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers), + [](const d3d11::ConstantBuffer& constantBuffer) + { + return constantBuffer.m_name == GLOBALS_CBUFFER_NAME; + }); + + const auto perObjectConsts = std::ranges::find_if(std::as_const(shaderInfo->m_constant_buffers), + [](const d3d11::ConstantBuffer& constantBuffer) + { + return constantBuffer.m_name == PER_OBJECT_CONSTS_CBUFFER_NAME; + }); + + if (globalsConstantBuffer != shaderInfo->m_constant_buffers.end()) + { + for (const auto& variable : globalsConstantBuffer->m_variables) + AddConstantName(variable.m_name); + } + + if (perObjectConsts != shaderInfo->m_constant_buffers.end()) + { + for (const auto& variable : perObjectConsts->m_variables) + AddConstantName(variable.m_name); + } + + for (const auto& boundResource : shaderInfo->m_bound_resources) + { + if (boundResource.m_type == d3d11::BoundResourceType::SAMPLER || boundResource.m_type == d3d11::BoundResourceType::TEXTURE) + { + if (AddTextureDefName(boundResource.m_name)) + { + const auto samplerPos = boundResource.m_name.rfind(SAMPLER_STR); + if (samplerPos != std::string::npos) + { + auto nameWithoutSamplerStr = boundResource.m_name; + nameWithoutSamplerStr.erase(samplerPos, std::char_traits::length(SAMPLER_STR)); + AddTextureDefName(nameWithoutSamplerStr); + } + } + } + } +} diff --git a/src/ObjWriting/Material/AbstractMaterialConstantZoneState.h b/src/ObjWriting/Material/AbstractMaterialConstantZoneState.h new file mode 100644 index 000000000..2a7734d84 --- /dev/null +++ b/src/ObjWriting/Material/AbstractMaterialConstantZoneState.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Dumping/IZoneAssetDumperState.h" + +#include +#include +#include + +class AbstractMaterialConstantZoneState : public IZoneAssetDumperState +{ +public: + void ExtractNamesFromZone(); + bool GetConstantName(unsigned hash, std::string& constantName) const; + bool GetTextureDefName(unsigned hash, std::string& textureDefName) const; + +protected: + virtual void ExtractNamesFromShader(const void* shader, size_t shaderSize) = 0; + virtual void ExtractNamesFromZoneInternal() = 0; + virtual void AddStaticKnownNames() = 0; + virtual unsigned HashString(const std::string& str) = 0; + + bool ShouldDumpFromStruct(const void* pStruct); + void AddConstantName(const std::string& constantName); + bool AddTextureDefName(const std::string& textureDefName); + + std::unordered_set m_dumped_structs; + std::unordered_map m_constant_names_from_shaders; + std::unordered_map m_texture_def_names_from_shaders; +}; + +class AbstractMaterialConstantZoneStateDx9 : public AbstractMaterialConstantZoneState +{ +protected: + void ExtractNamesFromShader(const void* shader, size_t shaderSize) override; +}; + +class AbstractMaterialConstantZoneStateDx11 : public AbstractMaterialConstantZoneState +{ +protected: + void ExtractNamesFromShader(const void* shader, size_t shaderSize) override; +}; diff --git a/src/ObjWriting/XModel/GenericXModelDumper.inc.h b/src/ObjWriting/XModel/GenericXModelDumper.inc.h new file mode 100644 index 000000000..9e44946e2 --- /dev/null +++ b/src/ObjWriting/XModel/GenericXModelDumper.inc.h @@ -0,0 +1,687 @@ +#pragma once + +#ifndef GAME_NAMESPACE +#error Must define GAME_NAMESPACE +#endif + +#include "Game/T6/CommonT6.h" +#include "ObjWriting.h" +#include "Utils/DistinctMapper.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 + +namespace GAME_NAMESPACE +{ + inline std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + inline GfxImage* GetImageFromTextureDef(const MaterialTextureDef& textureDef) + { +#ifdef FEATURE_T6 + return textureDef.image; +#else + return textureDef.u.image; +#endif + } + + inline 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 GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') + return GetImageFromTextureDef(*def); + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') + return GetImageFromTextureDef(*def); + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + inline 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 GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + inline 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 GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + inline bool HasDefaultArmature(const XModel* model, const unsigned lod) + { + if (model->numRootBones != 1 || model->numBones != 1) + return false; + + const auto* surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + const auto surfCount = model->lodInfo[lod].numsurfs; + + if (!surfs) + return true; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend) + return false; + + const auto& vertList = surface.vertList[0]; + if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount) + return false; + } + + return true; + } + + inline void OmitDefaultArmature(XModelCommon& common) + { + common.m_bones.clear(); + common.m_bone_weight_data.weights.clear(); + common.m_vertex_bone_weights.resize(common.m_vertices.size()); + for (auto& vertexWeights : common.m_vertex_bone_weights) + { + vertexWeights.weightOffset = 0u; + vertexWeights.weightCount = 0u; + } + } + + inline 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 = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + else + bone.parentIndex = std::nullopt; + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + const auto& baseMat = model->baseMat[boneNum]; + bone.globalOffset[0] = baseMat.trans.x; + bone.globalOffset[1] = baseMat.trans.y; + bone.globalOffset[2] = baseMat.trans.z; + bone.globalRotation = { + baseMat.quat.x, + baseMat.quat.y, + baseMat.quat.z, + baseMat.quat.w, + }; + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = {0, 0, 0, 1}; + } + else + { + const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3]; + bone.localOffset[0] = trans[0]; + bone.localOffset[1] = trans[1]; + bone.localOffset[2] = trans[2]; + + const auto& quat = model->quats[boneNum - model->numRootBones]; + bone.localRotation = { + QuatInt16::ToFloat(quat.v[0]), + QuatInt16::ToFloat(quat.v[1]), + QuatInt16::ToFloat(quat.v[2]), + QuatInt16::ToFloat(quat.v[3]), + }; + } + + out.m_bones.emplace_back(std::move(bone)); + } + } + + inline const char* AssetName(const char* input) + { + if (input && input[0] == ',') + return &input[1]; + + return input; + } + + inline 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)); + } + } + } + + inline 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)); + } + } + + inline 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]; + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz.x; + vertex.coordinates[1] = v.xyz.y; + vertex.coordinates[2] = v.xyz.z; + Common::Vec3UnpackUnitVec(v.normal, vertex.normal); + Common::Vec4UnpackGfxColor(v.color, vertex.color); + Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv); + + out.m_vertices.emplace_back(vertex); + } + } + } + + inline 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); + } + + inline float BoneWeight16(const uint16_t value) + { + return static_cast(value) / static_cast(std::numeric_limits::max()); + } + + inline 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 = weightOffset; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{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 = weightOffset; + const auto boneIndex0 = 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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); + const auto boneWeight2 = BoneWeight16(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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); + const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat); + const auto boneWeight3 = BoneWeight16(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(0, 0); + } + } + } + + inline 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.i[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri.i[2] + surface.baseVertIndex; + object.m_faces.emplace_back(face); + } + } + } + + inline 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); + AddXModelMaterials(out, materialMapper, model); + AddXModelObjects(out, model, lod, materialMapper); + AddXModelVertices(out, model, lod); + AddXModelFaces(out, model, lod); + + if (!HasDefaultArmature(model, lod)) + { + AddXModelBones(out, context, model); + AddXModelVertexBoneWeights(out, model, lod); + } + else + { + OmitDefaultArmature(out); + } + } + + inline 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); + } + + inline 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); + } + + inline 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); + } + + inline 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; + } + } + } + + class JsonDumper + { + public: + JsonDumper(AssetDumpingContext& context, std::ostream& stream) + : m_stream(stream) + { + } + + void Dump(const XModel* xmodel) const + { + JsonXModel jsonXModel; + CreateJsonXModel(jsonXModel, *xmodel); + nlohmann::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; + } + + static const char* GetExtensionForModelByConfig() + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + return ".XMODEL_EXPORT"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + return ".OBJ"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + return ".GLTF"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + return ".GLB"; + default: + assert(false); + return ""; + } + } + + static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) + { + if (xmodel.collLod >= 0) + jXModel.collLod = xmodel.collLod; + + for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++) + { + JsonXModelLod lod; + lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig()); + lod.distance = xmodel.lodInfo[lodNumber].dist; + + jXModel.lods.emplace_back(std::move(lod)); + } + + 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; + +#ifdef FEATURE_T6 + jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x; + jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y; + jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z; + jXModel.lightingOriginRange = xmodel.lightingOriginRange; +#endif + } + + std::ostream& m_stream; + }; + + inline void DumpXModelJson(AssetDumpingContext& context, XAssetInfo* asset) + { + const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name)); + if (!assetFile) + return; + + const JsonDumper dumper(context, *assetFile); + dumper.Dump(asset->Asset()); + } +} // namespace GAME_NAMESPACE diff --git a/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp b/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp index effb524ab..49afebd6b 100644 --- a/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp +++ b/src/ObjWriting/XModel/Gltf/GltfTextOutput.cpp @@ -6,6 +6,8 @@ #include #define LTC_NO_PROTOTYPES +#include "Impl/Base64.h" + #include using namespace gltf; @@ -17,18 +19,15 @@ TextOutput::TextOutput(std::ostream& stream) std::optional TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const { - const auto base64Length = 4u * ((bufferSize + 2u) / 3u); + const auto base64Length = base64::GetBase64EncodeOutputLength(bufferSize); 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); + auto result = base64::EncodeBase64(buffer, bufferSize, &output[URI_PREFIX_LENGTH], base64Length + 1u); + assert(result); return output; } diff --git a/src/ObjWriting/XModel/XModelDumper.cpp.template b/src/ObjWriting/XModel/XModelDumper.cpp.template new file mode 100644 index 000000000..f3644849a --- /dev/null +++ b/src/ObjWriting/XModel/XModelDumper.cpp.template @@ -0,0 +1,733 @@ +#options GAME (IW5, T5, T6) + +#filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".cpp" + +#set DUMPER_HEADER "\"XModelDumper" + GAME + ".h\"" +#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\"" +#set JSON_HEADER "\"Game/" + GAME + "/XModel/JsonXModel" + GAME + ".h\"" + +#if GAME == "IW5" +#define FEATURE_IW5 +#elif GAME == "T5" +#define FEATURE_T5 +#elif GAME == "T6" +#define FEATURE_T6 +#endif + +#include DUMPER_HEADER + +#include COMMON_HEADER +#include JSON_HEADER + +#include "ObjWriting.h" +#include "Utils/DistinctMapper.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 + +namespace GAME +{ + std::string GetFileNameForLod(const std::string& modelName, const unsigned lod, const std::string& extension) + { + return std::format("model_export/{}_lod{}{}", modelName, lod, extension); + } + + GfxImage* GetImageFromTextureDef(const MaterialTextureDef& textureDef) + { +#ifdef FEATURE_T6 + return textureDef.image; +#else + return textureDef.u.image; +#endif + } + + GfxImage* GetMaterialColorMap(const Material* material) + { + std::vector potentialTextureDefs; + + for (auto textureIndex = 0u; textureIndex < material->textureCount; textureIndex++) + { + MaterialTextureDef* def = &material->textureTable[textureIndex]; + +#ifdef FEATURE_IW5 + if (def->semantic == TS_COLOR_MAP) + potentialTextureDefs.push_back(def); +#else + if (def->semantic == TS_COLOR_MAP || def->semantic >= TS_COLOR0_MAP && def->semantic <= TS_COLOR15_MAP) + potentialTextureDefs.push_back(def); +#endif + } + + if (potentialTextureDefs.empty()) + return nullptr; + if (potentialTextureDefs.size() == 1) + return GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'c' && tolower(def->nameEnd) == 'p') + return GetImageFromTextureDef(*def); + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'r' && tolower(def->nameEnd) == 'k') + return GetImageFromTextureDef(*def); + } + + for (const auto* def : potentialTextureDefs) + { + if (tolower(def->nameStart) == 'd' && tolower(def->nameEnd) == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + 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 GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 'n' && def->nameEnd == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + 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 GetImageFromTextureDef(*potentialTextureDefs[0]); + + for (const auto* def : potentialTextureDefs) + { + if (def->nameStart == 's' && def->nameEnd == 'p') + return GetImageFromTextureDef(*def); + } + + return GetImageFromTextureDef(*potentialTextureDefs[0]); + } + + bool GetSurfaces(const XModel* model, const unsigned lod, XSurface*& surfs, unsigned& surfCount) + { +#ifdef FEATURE_IW5 + if (!model->lodInfo[lod].modelSurfs || !model->lodInfo[lod].modelSurfs->surfs) + return false; + + surfs = model->lodInfo[lod].modelSurfs->surfs; + surfCount = model->lodInfo[lod].modelSurfs->numsurfs; +#else + if (!model->surfs) + return false; + + surfs = &model->surfs[model->lodInfo[lod].surfIndex]; + surfCount = model->lodInfo[lod].numsurfs; +#endif + + return true; + } + + bool HasDefaultArmature(const XModel* model, const unsigned lod) + { + if (model->numRootBones != 1 || model->numBones != 1) + return false; + + XSurface* surfs; + unsigned surfCount; + if (!GetSurfaces(model, lod, surfs, surfCount)) + return true; + + for (auto surfIndex = 0u; surfIndex < surfCount; surfIndex++) + { + const auto& surface = surfs[surfIndex]; + + if (surface.vertListCount != 1 || surface.vertInfo.vertsBlend) + return false; + + const auto& vertList = surface.vertList[0]; + if (vertList.boneOffset != 0 || vertList.triOffset != 0 || vertList.triCount != surface.triCount || vertList.vertCount != surface.vertCount) + return false; + } + + return true; + } + + void OmitDefaultArmature(XModelCommon& common) + { + common.m_bones.clear(); + common.m_bone_weight_data.weights.clear(); + common.m_vertex_bone_weights.resize(common.m_vertices.size()); + for (auto& vertexWeights : common.m_vertex_bone_weights) + { + vertexWeights.weightOffset = 0u; + vertexWeights.weightCount = 0u; + } + } + + 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 = static_cast(boneNum - static_cast(model->parentList[boneNum - model->numRootBones])); + else + bone.parentIndex = std::nullopt; + + bone.scale[0] = 1.0f; + bone.scale[1] = 1.0f; + bone.scale[2] = 1.0f; + + const auto& baseMat = model->baseMat[boneNum]; + bone.globalOffset[0] = baseMat.trans.x; + bone.globalOffset[1] = baseMat.trans.y; + bone.globalOffset[2] = baseMat.trans.z; + bone.globalRotation = { + baseMat.quat.x, + baseMat.quat.y, + baseMat.quat.z, + baseMat.quat.w, + }; + + if (boneNum < model->numRootBones) + { + bone.localOffset[0] = 0; + bone.localOffset[1] = 0; + bone.localOffset[2] = 0; + bone.localRotation = {0, 0, 0, 1}; + } + else + { + const auto* trans = &model->trans[(boneNum - model->numRootBones) * 3]; + bone.localOffset[0] = trans[0]; + bone.localOffset[1] = trans[1]; + bone.localOffset[2] = trans[2]; + + const auto& quat = model->quats[boneNum - model->numRootBones]; + bone.localRotation = { + QuatInt16::ToFloat(quat.v[0]), + QuatInt16::ToFloat(quat.v[1]), + QuatInt16::ToFloat(quat.v[2]), + QuatInt16::ToFloat(quat.v[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) + { + XSurface* surfs; + unsigned surfCount; + if (!GetSurfaces(model, lod, surfs, surfCount)) + 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]; + + XModelVertex vertex{}; + vertex.coordinates[0] = v.xyz.x; + vertex.coordinates[1] = v.xyz.y; + vertex.coordinates[2] = v.xyz.z; + Common::Vec3UnpackUnitVec(v.normal, vertex.normal); + Common::Vec4UnpackGfxColor(v.color, vertex.color); + Common::Vec2UnpackTexCoords(v.texCoord, vertex.uv); + + out.m_vertices.emplace_back(vertex); + } + } + } + + void AllocateXModelBoneWeights(const XModel* model, const unsigned lod, XModelVertexBoneWeightCollection& weightCollection) + { + XSurface* surfs; + unsigned surfCount; + if (!GetSurfaces(model, lod, surfs, surfCount)) + 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); + } + + float BoneWeight16(const uint16_t value) + { + return static_cast(value) / static_cast(std::numeric_limits::max()); + } + + void AddXModelVertexBoneWeights(XModelCommon& out, const XModel* model, const unsigned lod) + { + XSurface* surfs; + unsigned surfCount; + if (!GetSurfaces(model, lod, surfs, surfCount)) + return; + + 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 = weightOffset; + + weightCollection.weights[weightOffset++] = XModelBoneWeight{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 = weightOffset; + const auto boneIndex0 = 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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); + const auto boneWeight2 = BoneWeight16(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 = weightOffset; + const auto boneIndex0 = surface.vertInfo.vertsBlend[vertsBlendOffset + 0] / sizeof(DObjSkelMat); + const auto boneIndex1 = surface.vertInfo.vertsBlend[vertsBlendOffset + 1] / sizeof(DObjSkelMat); + const auto boneWeight1 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 2]); + const auto boneIndex2 = surface.vertInfo.vertsBlend[vertsBlendOffset + 3] / sizeof(DObjSkelMat); + const auto boneWeight2 = BoneWeight16(surface.vertInfo.vertsBlend[vertsBlendOffset + 4]); + const auto boneIndex3 = surface.vertInfo.vertsBlend[vertsBlendOffset + 5] / sizeof(DObjSkelMat); + const auto boneWeight3 = BoneWeight16(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(0, 0); + } + } + } + + void AddXModelFaces(XModelCommon& out, const XModel* model, const unsigned lod) + { + XSurface* surfs; + unsigned surfCount; + if (!GetSurfaces(model, lod, surfs, surfCount)) + 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.i[0] + surface.baseVertIndex; + face.vertexIndex[1] = tri.i[1] + surface.baseVertIndex; + face.vertexIndex[2] = tri.i[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); + AddXModelMaterials(out, materialMapper, model); + AddXModelObjects(out, model, lod, materialMapper); + AddXModelVertices(out, model, lod); + AddXModelFaces(out, model, lod); + + if (!HasDefaultArmature(model, lod)) + { + AddXModelBones(out, context, model); + AddXModelVertexBoneWeights(out, model, lod); + } + else + { + OmitDefaultArmature(out); + } + } + + 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; + } + } + } + + class JsonDumper + { + public: + JsonDumper(AssetDumpingContext& context, std::ostream& stream) + : m_stream(stream) + { + } + + void Dump(const XModel* xmodel) const + { + JsonXModel jsonXModel; + CreateJsonXModel(jsonXModel, *xmodel); + nlohmann::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; + } + + static const char* GetExtensionForModelByConfig() + { + switch (ObjWriting::Configuration.ModelOutputFormat) + { + case ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT: + return ".XMODEL_EXPORT"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ: + return ".OBJ"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF: + return ".GLTF"; + case ObjWriting::Configuration_t::ModelOutputFormat_e::GLB: + return ".GLB"; + default: + assert(false); + return ""; + } + } + + static void CreateJsonXModel(JsonXModel& jXModel, const XModel& xmodel) + { + if (xmodel.collLod >= 0) + jXModel.collLod = xmodel.collLod; + + for (auto lodNumber = 0u; lodNumber < xmodel.numLods; lodNumber++) + { + JsonXModelLod lod; + lod.file = std::format("model_export/{}_lod{}{}", xmodel.name, lodNumber, GetExtensionForModelByConfig()); + lod.distance = xmodel.lodInfo[lodNumber].dist; + + jXModel.lods.emplace_back(std::move(lod)); + } + + if (xmodel.physPreset && xmodel.physPreset->name) + jXModel.physPreset = AssetName(xmodel.physPreset->name); + +#ifdef FEATURE_IW5 + if (xmodel.physCollmap && xmodel.physCollmap->name) + jXModel.physCollmap = AssetName(xmodel.physCollmap->name); +#endif + +#if defined(FEATURE_T5) || defined(FEATURE_T6) + if (xmodel.physConstraints && xmodel.physConstraints->name) + jXModel.physConstraints = AssetName(xmodel.physConstraints->name); +#endif + + jXModel.flags = xmodel.flags; + +#ifdef FEATURE_T6 + jXModel.lightingOriginOffset.x = xmodel.lightingOriginOffset.x; + jXModel.lightingOriginOffset.y = xmodel.lightingOriginOffset.y; + jXModel.lightingOriginOffset.z = xmodel.lightingOriginOffset.z; + jXModel.lightingOriginRange = xmodel.lightingOriginRange; +#endif + } + + std::ostream& m_stream; + }; + + void DumpXModelJson(AssetDumpingContext& context, XAssetInfo* asset) + { + const auto assetFile = context.OpenAssetFile(std::format("xmodel/{}.json", asset->m_name)); + if (!assetFile) + return; + + const JsonDumper dumper(context, *assetFile); + dumper.Dump(asset->Asset()); + } + + void DumpXModel(AssetDumpingContext& context, XAssetInfo* asset) + { + DumpXModelJson(context, asset); + DumpXModelSurfs(context, asset); + } +} // namespace GAME diff --git a/src/ObjWriting/XModel/XModelDumper.h.template b/src/ObjWriting/XModel/XModelDumper.h.template new file mode 100644 index 000000000..c981ef6bb --- /dev/null +++ b/src/ObjWriting/XModel/XModelDumper.h.template @@ -0,0 +1,15 @@ +#options GAME (IW5, T5, T6) + +#filename "Game/" + GAME + "/XModel/XModelDumper" + GAME + ".h" + +#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\"" + +#pragma once + +#include "Dumping/AssetDumpingContext.h" +#include GAME_HEADER + +namespace GAME +{ + void DumpXModel(AssetDumpingContext& context, XAssetInfo* asset); +} diff --git a/src/ZoneCode/Game/IW5/XAssets/XModel.txt b/src/ZoneCode/Game/IW5/XAssets/XModel.txt index 436705729..72a489472 100644 --- a/src/ZoneCode/Game/IW5/XAssets/XModel.txt +++ b/src/ZoneCode/Game/IW5/XAssets/XModel.txt @@ -13,7 +13,7 @@ set count parentList numBones - numRootBones; set reusable quats; set count quats numBones - numRootBones; set reusable trans; -set count trans numBones - numRootBones; +set count trans (numBones - numRootBones) * 3; set reusable partClassification; set count partClassification numBones; set reusable baseMat; diff --git a/src/ZoneCode/Game/IW5/XAssets/XModelSurfs.txt b/src/ZoneCode/Game/IW5/XAssets/XModelSurfs.txt index dd41e7c31..1e939bbf7 100644 --- a/src/ZoneCode/Game/IW5/XAssets/XModelSurfs.txt +++ b/src/ZoneCode/Game/IW5/XAssets/XModelSurfs.txt @@ -14,6 +14,9 @@ set count vertList vertListCount; set reusable triIndices; set block triIndices XFILE_BLOCK_INDEX; set count triIndices triCount; +set reusable verts0; +set block verts0 XFILE_BLOCK_VERTEX; +set count verts0 XSurface::vertCount; reorder: zoneHandle vertInfo @@ -29,15 +32,6 @@ set count vertsBlend vertCount[0] + 5 * vertCount[2] + 7 * vertCount[3]; -// GfxVertexUnion0 -use GfxVertexUnion0; -set condition quantizedNoColorVerts0 never; -set condition quantizedVerts0 never; -set condition verts0 never; -set reusable packedVerts0; -set block packedVerts0 XFILE_BLOCK_VERTEX; -set count packedVerts0 XSurface::vertCount; - // XRigidVertList set reusable XRigidVertList::collisionTree; diff --git a/tools/scripts/source_templating.lua b/tools/scripts/source_templating.lua new file mode 100644 index 000000000..a87d4d533 --- /dev/null +++ b/tools/scripts/source_templating.lua @@ -0,0 +1,66 @@ +function useSourceTemplating(projectName) + local projectFolder = path.join(ProjectFolder(), projectName) + local templateFiles = os.matchfiles(path.join(projectFolder, "**.template")) + + local createdFiles = {} + + for i = 1, #templateFiles do + local templateFile = templateFiles[i] + local relativeTemplatePath = path.getrelative(projectFolder, templateFile) + local relativeResultPath = path.replaceextension(relativeTemplatePath, "") + local resultExtension = path.getextension(relativeResultPath) + + local data = io.readfile(templateFile) + local gameOptionsStart, gameOptionsCount = string.find(data, "#options%s+GAME%s*%(") + + if gameOptionsStart == nil then + error("Source template " .. relativeTemplatePath .. " must define an option called GAME") + end + + local gameOptionsPos, gameOptionsLenPlusOne = string.find(data, "[%a%d%s,]+%)", gameOptionsStart + gameOptionsCount) + + if gameOptionsPos ~= gameOptionsStart + gameOptionsCount then + error("Source template " .. relativeTemplatePath .. " must define an option called GAME") + end + + local gameOptions = string.sub(data, gameOptionsPos, gameOptionsLenPlusOne - 1) + local games = string.explode(gameOptions, ",%s*") + + files { + templateFile + } + + filter("files:" .. templateFile) + buildmessage("Templating source file " .. relativeTemplatePath) + buildinputs { + TargetDirectoryBuildTools .. "/" .. ExecutableByOs('RawTemplater') + } + buildcommands { + '"' .. TargetDirectoryBuildTools .. '/' .. ExecutableByOs('RawTemplater') .. '"' + .. ' -o "%{prj.location}/"' + .. " %{file.relpath}" + } + for i = 1, #games do + local gameName = games[i] + local outputFileName = path.replaceextension(path.replaceextension(relativeResultPath, "") .. gameName, resultExtension) + local outputFile = "%{prj.location}/Game/" .. gameName .. "/" .. outputFileName + + table.insert(createdFiles, outputFile) + + buildoutputs { + outputFile + } + end + filter {} + + includedirs { + "%{prj.location}" + } + + files { + createdFiles + } + + RawTemplater:use() + end +end