From fda91b84bd7f611e0bed2dacbda05fe13f932f08 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 29 Jun 2025 23:05:36 +0100 Subject: [PATCH] feat: add json material loading/writing for iw3 --- src/Common/Game/IW3/CommonIW3.h | 5 + src/Common/Game/IW3/IW3_Assets.h | 150 ++++- .../Material/JsonMaterial.h.template | 99 ++- .../Game/IW3/Material/LoaderMaterialIW3.cpp | 54 ++ .../Game/IW3/Material/LoaderMaterialIW3.h | 12 + src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 3 +- .../Material/JsonMaterialLoader.cpp.template | 12 +- .../Material/JsonMaterialLoader.h.template | 2 +- .../IW3/AssetDumpers/AssetDumperMaterial.cpp | 467 -------------- .../Game/IW3/Material/DumperMaterialIW3.cpp | 30 + .../DumperMaterialIW3.h} | 3 + .../Material/MaterialConstantZoneStateIW3.cpp | 250 ++++++++ .../Material/MaterialConstantZoneStateIW3.h | 18 + src/ObjWriting/Game/IW3/ObjWriterIW3.cpp | 2 +- src/ObjWriting/Game/IW3/ObjWriterIW3.h | 1 + .../Material/JsonMaterialWriter.cpp.template | 12 +- .../Material/JsonMaterialWriter.h.template | 2 +- src/ZoneCommon/Game/IW3/GameAssetPoolIW3.h | 3 - .../IW3/Material/LoaderMaterialIW3Test.cpp | 577 ++++++++++++++++++ .../IW3/Material/DumperMaterialIW3Test.cpp | 571 +++++++++++++++++ 20 files changed, 1727 insertions(+), 546 deletions(-) create mode 100644 src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.cpp create mode 100644 src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.h delete mode 100644 src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp create mode 100644 src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.cpp rename src/ObjWriting/Game/IW3/{AssetDumpers/AssetDumperMaterial.h => Material/DumperMaterialIW3.h} (78%) create mode 100644 src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.cpp create mode 100644 src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.h create mode 100644 test/ObjLoadingTests/Game/IW3/Material/LoaderMaterialIW3Test.cpp create mode 100644 test/ObjWritingTests/Game/IW3/Material/DumperMaterialIW3Test.cpp diff --git a/src/Common/Game/IW3/CommonIW3.h b/src/Common/Game/IW3/CommonIW3.h index a01a6feb..43664b62 100644 --- a/src/Common/Game/IW3/CommonIW3.h +++ b/src/Common/Game/IW3/CommonIW3.h @@ -21,6 +21,11 @@ namespace IW3 return result; } + 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/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index 4e27aa9a..4a645889 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -566,7 +566,8 @@ namespace IW3 GFXS_BLEND_INVDESTALPHA = 0x8, GFXS_BLEND_DESTCOLOR = 0x9, GFXS_BLEND_INVDESTCOLOR = 0xA, - GFXS_BLEND_MASK = 0xF, + + GFXS_BLEND_COUNT }; enum GfxBlendOp @@ -577,7 +578,40 @@ namespace IW3 GFXS_BLENDOP_REVSUBTRACT = 0x3, GFXS_BLENDOP_MIN = 0x4, GFXS_BLENDOP_MAX = 0x5, - GFXS_BLENDOP_MASK = 0x7, + + 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 @@ -589,10 +623,19 @@ namespace IW3 GFXS_STENCILOP_DECRSAT = 0x4, GFXS_STENCILOP_INVERT = 0x5, GFXS_STENCILOP_INCR = 0x6, - GFXS_STENCILOP_DECR = 0x7, + GFXS_STENCILOP_DECR = 0x7 + }; - GFXS_STENCILOP_COUNT, - GFXS_STENCILOP_MASK = 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 }; enum GfxStateBitsEnum : unsigned int @@ -613,10 +656,10 @@ namespace IW3 GFXS0_ATEST_GE_128 = 0x3000, GFXS0_ATEST_MASK = 0x3000, - GFXS0_CULL_SHIFT = 0xE, GFXS0_CULL_NONE = 0x4000, GFXS0_CULL_BACK = 0x8000, GFXS0_CULL_FRONT = 0xC000, + GFXS0_CULL_SHIFT = 0xE, GFXS0_CULL_MASK = 0xC000, GFXS0_SRCBLEND_ALPHA_SHIFT = 0x10, @@ -638,18 +681,18 @@ namespace IW3 GFXS1_DEPTHWRITE = 0x1, GFXS1_DEPTHTEST_DISABLE = 0x2, - GFXS1_DEPTHTEST_SHIFT = 0x2, GFXS1_DEPTHTEST_ALWAYS = 0x0, GFXS1_DEPTHTEST_LESS = 0x4, GFXS1_DEPTHTEST_EQUAL = 0x8, GFXS1_DEPTHTEST_LESSEQUAL = 0xC, + GFXS1_DEPTHTEST_SHIFT = 0x2, GFXS1_DEPTHTEST_MASK = 0xC, - GFXS1_POLYGON_OFFSET_SHIFT = 0x4, GFXS1_POLYGON_OFFSET_0 = 0x0, GFXS1_POLYGON_OFFSET_1 = 0x10, GFXS1_POLYGON_OFFSET_2 = 0x20, GFXS1_POLYGON_OFFSET_SHADOWMAP = 0x30, + GFXS1_POLYGON_OFFSET_SHIFT = 0x4, GFXS1_POLYGON_OFFSET_MASK = 0x30, GFXS1_STENCIL_FRONT_ENABLE = 0x40, @@ -672,16 +715,61 @@ namespace IW3 GFXS1_STENCILOP_FRONTBACK_MASK = 0x1FF1FF00, }; + 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 unused1 : 2; // 29-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; }; struct type_align(16) MaterialConstantDef { unsigned int nameHash; char name[12]; - float literal[4]; + vec4_t literal; }; struct complex_s @@ -718,6 +806,26 @@ namespace IW3 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, @@ -743,13 +851,26 @@ namespace IW3 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; // SamplerStateBits_e - unsigned char semantic; // TextureSemantic + MaterialTextureDefSamplerState samplerState; // SamplerStateBits_e + unsigned char semantic; // TextureSemantic MaterialTextureDefInfo u; }; @@ -842,8 +963,9 @@ namespace IW3 CAMERA_REGION_LIT = 0x0, CAMERA_REGION_DECAL = 0x1, CAMERA_REGION_EMISSIVE = 0x2, - CAMERA_REGION_COUNT = 0x3, - CAMERA_REGION_NONE = 0x3, + + CAMERA_REGION_COUNT, + CAMERA_REGION_NONE = CAMERA_REGION_COUNT, }; enum MaterialStateFlags diff --git a/src/ObjCommon/Material/JsonMaterial.h.template b/src/ObjCommon/Material/JsonMaterial.h.template index 7c7f0663..426bf449 100644 --- a/src/ObjCommon/Material/JsonMaterial.h.template +++ b/src/ObjCommon/Material/JsonMaterial.h.template @@ -1,8 +1,11 @@ -#options GAME (IW4, IW5, T6) +#options GAME (IW3, IW4, IW5, T6) #filename "Game/" + GAME + "/Material/JsonMaterial" + GAME + ".h" -#if GAME == "IW4" +#if GAME == "IW3" +#define FEATURE_IW3 +#define HAS_WATER +#elif GAME == "IW4" #define FEATURE_IW4 #define HAS_WATER #elif GAME == "IW5" @@ -75,7 +78,7 @@ namespace GAME INVALID, DISABLED, GT0, -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) LT128, #endif GE128 @@ -85,7 +88,7 @@ namespace GAME {JsonAlphaTest::INVALID, nullptr }, {JsonAlphaTest::DISABLED, "disabled"}, {JsonAlphaTest::GT0, "gt0" }, -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) {JsonAlphaTest::LT128, "lt128" }, #endif {JsonAlphaTest::GE128, "ge128" } @@ -315,52 +318,46 @@ namespace GAME #endif NLOHMANN_JSON_SERIALIZE_ENUM(TextureSemantic, { -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) - {TS_2D, "2D" }, - {TS_FUNCTION, "function" }, - {TS_COLOR_MAP, "colorMap" }, + {TS_2D, "2D" }, + {TS_FUNCTION, "function" }, + {TS_COLOR_MAP, "colorMap" }, +#if defined(FEATURE_IW3) || defined(FEATURE_T6) + {TS_UNUSED_1, "unused1" }, +#else {TS_DETAIL_MAP, "detailMap" }, +#endif {TS_UNUSED_2, "unused2" }, {TS_NORMAL_MAP, "normalMap" }, {TS_UNUSED_3, "unused3" }, {TS_UNUSED_4, "unused4" }, {TS_SPECULAR_MAP, "specularMap" }, {TS_UNUSED_5, "unused5" }, +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) {TS_UNUSED_6, "unused6" }, {TS_WATER_MAP, "waterMap" }, #ifdef FEATURE_IW5 {TS_DISPLACEMENT_MAP, "displacementMap"}, #endif #elif defined(FEATURE_T6) - {TS_2D, "2D" }, - {TS_FUNCTION, "function" }, - {TS_COLOR_MAP, "colorMap" }, - {TS_UNUSED_1, "unused1" }, - {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_OCCLUSION_MAP, "occlusionMap"}, - {TS_UNUSED_6, "unused6" }, - {TS_COLOR0_MAP, "color0Map" }, - {TS_COLOR1_MAP, "color1Map" }, - {TS_COLOR2_MAP, "color2Map" }, - {TS_COLOR3_MAP, "color3Map" }, - {TS_COLOR4_MAP, "color4Map" }, - {TS_COLOR5_MAP, "color5Map" }, - {TS_COLOR6_MAP, "color6Map" }, - {TS_COLOR7_MAP, "color7Map" }, - {TS_COLOR8_MAP, "color8Map" }, - {TS_COLOR9_MAP, "color9Map" }, - {TS_COLOR10_MAP, "color10Map" }, - {TS_COLOR11_MAP, "color11Map" }, - {TS_COLOR12_MAP, "color12Map" }, - {TS_COLOR13_MAP, "color13Map" }, - {TS_COLOR14_MAP, "color14Map" }, - {TS_COLOR15_MAP, "color15Map" }, - {TS_THROW_MAP, "throwMap" }, + {TS_OCCLUSION_MAP, "occlusionMap" }, + {TS_UNUSED_6, "unused6" }, + {TS_COLOR0_MAP, "color0Map" }, + {TS_COLOR1_MAP, "color1Map" }, + {TS_COLOR2_MAP, "color2Map" }, + {TS_COLOR3_MAP, "color3Map" }, + {TS_COLOR4_MAP, "color4Map" }, + {TS_COLOR5_MAP, "color5Map" }, + {TS_COLOR6_MAP, "color6Map" }, + {TS_COLOR7_MAP, "color7Map" }, + {TS_COLOR8_MAP, "color8Map" }, + {TS_COLOR9_MAP, "color9Map" }, + {TS_COLOR10_MAP, "color10Map" }, + {TS_COLOR11_MAP, "color11Map" }, + {TS_COLOR12_MAP, "color12Map" }, + {TS_COLOR13_MAP, "color13Map" }, + {TS_COLOR14_MAP, "color14Map" }, + {TS_COLOR15_MAP, "color15Map" }, + {TS_THROW_MAP, "throwMap" }, #endif }); @@ -437,26 +434,25 @@ namespace GAME ); NLOHMANN_JSON_SERIALIZE_ENUM(MaterialGameFlags, { -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) - {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" }, -#elif defined(FEATURE_T6) {MTL_GAMEFLAG_1, "1" }, {MTL_GAMEFLAG_2, "2" }, +#ifdef FEATURE_T6 {MTL_GAMEFLAG_NO_MARKS, "NO_MARKS" }, {MTL_GAMEFLAG_NO_MARKS, "4" }, +#else + {MTL_GAMEFLAG_4, "4" }, +#endif {MTL_GAMEFLAG_8, "8" }, {MTL_GAMEFLAG_10, "10" }, {MTL_GAMEFLAG_20, "20" }, +#if defined(FEATURE_IW3) || defined(FEATURE_T6) {MTL_GAMEFLAG_CASTS_SHADOW, "CASTS_SHADOW"}, {MTL_GAMEFLAG_CASTS_SHADOW, "40" }, +#else + {MTL_GAMEFLAG_40, "40" }, +#endif {MTL_GAMEFLAG_80, "80" }, +#ifdef FEATURE_T6 {MTL_GAMEFLAG_100, "100" }, {MTL_GAMEFLAG_200, "200" }, {MTL_GAMEFLAG_400, "400" }, @@ -466,7 +462,11 @@ namespace GAME }); NLOHMANN_JSON_SERIALIZE_ENUM(GfxCameraRegionType, { -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) + {CAMERA_REGION_LIT, "lit" }, + {CAMERA_REGION_DECAL, "decal" }, + {CAMERA_REGION_EMISSIVE, "emissive" }, +#elif defined(FEATURE_IW4) || defined(FEATURE_IW5) {CAMERA_REGION_LIT_OPAQUE, "litOpaque" }, {CAMERA_REGION_LIT_TRANS, "litTrans" }, {CAMERA_REGION_EMISSIVE, "emissive" }, @@ -474,7 +474,6 @@ namespace GAME #ifdef FEATURE_IW5 {CAMERA_REGION_LIGHT_MAP_OPAQUE, "lightMapOpaque"}, #endif - {CAMERA_REGION_NONE, "none" }, #elif defined(FEATURE_T6) {CAMERA_REGION_LIT_OPAQUE, "litOpaque" }, {CAMERA_REGION_LIT_TRANS, "litTrans" }, @@ -486,8 +485,8 @@ namespace GAME {CAMERA_REGION_DEPTH_HACK, "depthHack" }, {CAMERA_REGION_UNUSED, "unused" }, {CAMERA_REGION_SONAR, "sonar" }, - {CAMERA_REGION_NONE, "none" }, #endif + {CAMERA_REGION_NONE, "none" }, }); class JsonMaterial diff --git a/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.cpp b/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.cpp new file mode 100644 index 00000000..aec04c81 --- /dev/null +++ b/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.cpp @@ -0,0 +1,54 @@ +#include "LoaderMaterialIW3.h" + +#include "Game/IW3/IW3.h" +#include "Game/IW3/Material/JsonMaterialLoaderIW3.h" +#include "Material/MaterialCommon.h" + +#include +#include + +using namespace IW3; + +namespace +{ + class MaterialLoader final : public AssetCreator + { + public: + MaterialLoader(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto file = m_search_path.Open(material::GetFileNameForAssetName(assetName)); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + auto* material = m_memory.Alloc(); + material->info.name = m_memory.Dup(assetName.c_str()); + + AssetRegistration registration(assetName, material); + if (!LoadMaterialAsJson(*file.m_stream, *material, m_memory, context, registration)) + { + std::cerr << std::format("Failed to load material \"{}\"\n", assetName); + return AssetCreationResult::Failure(); + } + + return AssetCreationResult::Success(context.AddAsset(std::move(registration))); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace IW3 +{ + std::unique_ptr> CreateMaterialLoader(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace IW3 diff --git a/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.h b/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.h new file mode 100644 index 00000000..8d2dd024 --- /dev/null +++ b/src/ObjLoading/Game/IW3/Material/LoaderMaterialIW3.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/IW3/IW3.h" +#include "Gdt/IGdtQueryable.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace IW3 +{ + std::unique_ptr> CreateMaterialLoader(MemoryManager& memory, ISearchPath& searchPath); +} // namespace IW3 diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index eeb2d3a8..9c2ef22f 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -5,6 +5,7 @@ #include "Game/IW3/IW3.h" #include "Image/AssetLoaderImageIW3.h" #include "Localize/AssetLoaderLocalizeIW3.h" +#include "Material/LoaderMaterialIW3.h" #include "ObjLoading.h" #include "RawFile/AssetLoaderRawFileIW3.h" #include "StringTable/AssetLoaderStringTableIW3.h" @@ -90,7 +91,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(CreateMaterialLoader(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(CreateImageLoader(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template index 65687955..8db6cd37 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.cpp.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.cpp.template @@ -1,8 +1,12 @@ -#options GAME (IW4, IW5, T6) +#options GAME (IW3, IW4, IW5, T6) #filename "Game/" + GAME + "/Material/JsonMaterialLoader" + GAME + ".cpp" -#if GAME == "IW4" +#if GAME == "IW3" +#define FEATURE_IW3 +#define HAS_WATER +#define GAME_LOWER "iw3" +#elif GAME == "IW4" #define FEATURE_IW4 #define HAS_WATER #define GAME_LOWER "iw4" @@ -94,7 +98,7 @@ namespace std::cerr << std::format("Cannot load material \"{}\": {}\n", material.info.name, message); } -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) static bool CreateGameFlagsFromJson(const JsonMaterial& jMaterial, unsigned char& gameFlags) #elif defined(FEATURE_T6) static bool CreateGameFlagsFromJson(const JsonMaterial& jMaterial, unsigned& gameFlags) @@ -304,7 +308,7 @@ namespace structured.alphaTestDisabled = 0; structured.alphaTest = GFXS_ALPHA_TEST_GT_0; } -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::LT128) { structured.alphaTestDisabled = 0; diff --git a/src/ObjLoading/Material/JsonMaterialLoader.h.template b/src/ObjLoading/Material/JsonMaterialLoader.h.template index ae57b3b6..49b8057d 100644 --- a/src/ObjLoading/Material/JsonMaterialLoader.h.template +++ b/src/ObjLoading/Material/JsonMaterialLoader.h.template @@ -1,4 +1,4 @@ -#options GAME (IW4, IW5, T6) +#options GAME (IW3, IW4, IW5, T6) #filename "Game/" + GAME + "/Material/JsonMaterialLoader" + GAME + ".h" diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp deleted file mode 100644 index 0f90a158..00000000 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp +++ /dev/null @@ -1,467 +0,0 @@ -#include "AssetDumperMaterial.h" - -#include "Game/IW3/MaterialConstantsIW3.h" -#include "Game/IW3/TechsetConstantsIW3.h" - -#include -#include -#include - -// #define FLAGS_DEBUG 1 - -using namespace IW3; -using json = nlohmann::json; - -namespace IW3 -{ - const char* AssetName(const char* name) - { - if (name && name[0] == ',') - return &name[1]; - return name; - } - - template json ArrayEntry(const char* (&a)[S], const size_t index) - { - assert(index < S); - if (index < S) - return a[index]; - - return json{}; - } - - json BuildComplexTableJson(const complex_s* complexTable, const size_t count) - { - auto jArray = json::array(); - - if (complexTable) - { - for (auto index = 0u; index < count; index++) - { - const auto& entry = complexTable[index]; - jArray.emplace_back(json{ - {"real", entry.real}, - {"imag", entry.imag}, - }); - } - } - - return jArray; - } - - json BuildWaterJson(water_t* water) - { - if (!water) - return json{}; - - return json{ - {"floatTime", water->writable.floatTime}, - {"H0", BuildComplexTableJson(water->H0, water->M * water->N)}, - {"wTerm", water->wTerm ? json{std::vector(water->wTerm, water->wTerm + (water->M * water->N))} : json::array()}, - {"M", water->M}, - {"N", water->N}, - {"Lx", water->Lx}, - {"Lz", water->Lz}, - {"windvel", water->windvel}, - {"winddir", std::vector(std::begin(water->winddir), std::end(water->winddir))}, - {"amplitude", water->amplitude}, - {"codeConstant", std::vector(std::begin(water->codeConstant), std::end(water->codeConstant))}, - {"image", water->image && water->image->name ? AssetName(water->image->name) : nullptr}, - }; - } - - json BuildSamplerStateJson(unsigned char samplerState) - { - static const char* samplerFilterNames[]{ - "none", - "nearest", - "linear", - "aniso2x", - "aniso4x", - }; - static const char* samplerMipmapNames[]{ - "disabled", - "nearest", - "linear", - }; - - return json{ - {"filter", ArrayEntry(samplerFilterNames, (samplerState & SAMPLER_FILTER_MASK) >> SAMPLER_FILTER_SHIFT)}, - {"mipmap", ArrayEntry(samplerMipmapNames, (samplerState & SAMPLER_MIPMAP_MASK) >> SAMPLER_MIPMAP_SHIFT)}, - {"clampU", (samplerState & SAMPLER_CLAMP_U) ? true : false}, - {"clampV", (samplerState & SAMPLER_CLAMP_V) ? true : false}, - {"clampW", (samplerState & SAMPLER_CLAMP_W) ? true : false}, - }; - } - - json BuildTextureTableJson(const MaterialTextureDef* textureTable, const size_t count) - { - static const char* semanticNames[]{ - "2d", - "function", - "colorMap", - "unused1", - "unused2", - "normalMap", - "unused3", - "unused4", - "specularMap", - "unused5", - "unused6", - "waterMap", - }; - - auto jArray = json::array(); - - if (textureTable) - { - for (auto index = 0u; index < count; index++) - { - const auto& entry = textureTable[index]; - - json jEntry = { - {"samplerState", BuildSamplerStateJson(entry.samplerState)}, - {"semantic", ArrayEntry(semanticNames, entry.semantic)}, - }; - - const auto knownMaterialSourceName = knownMaterialSourceNames.find(entry.nameHash); - if (knownMaterialSourceName != knownMaterialSourceNames.end()) - { - jEntry["name"] = knownMaterialSourceName->second; - } - else - { - jEntry.merge_patch({ - {"nameHash", entry.nameHash }, - {"nameStart", entry.nameStart}, - {"nameEnd", entry.nameEnd }, - }); - } - - if (entry.semantic == TS_WATER_MAP) - { - jEntry["water"] = BuildWaterJson(entry.u.water); - } - else - { - jEntry["image"] = entry.u.image && entry.u.image->name ? AssetName(entry.u.image->name) : nullptr; - } - - jArray.emplace_back(std::move(jEntry)); - } - } - - return jArray; - } - - json BuildConstantTableJson(const MaterialConstantDef* constantTable, const size_t count) - { - auto jArray = json::array(); - - if (constantTable) - { - for (auto index = 0u; index < count; index++) - { - const auto& entry = constantTable[index]; - json jEntry = { - {"literal", std::vector(std::begin(entry.literal), std::end(entry.literal))}, - }; - - const auto nameLen = strnlen(entry.name, std::extent_v); - if (nameLen == std::extent_v) - { - std::string fullLengthName(entry.name, std::extent_v); - const auto fullLengthHash = Common::R_HashString(fullLengthName.c_str(), 0); - - if (fullLengthHash == entry.nameHash) - { - jEntry["name"] = fullLengthName; - } - else - { - const auto knownMaterialSourceName = knownMaterialSourceNames.find(entry.nameHash); - if (knownMaterialSourceName != knownMaterialSourceNames.end()) - { - jEntry["name"] = knownMaterialSourceName->second; - } - else - { - jEntry.merge_patch({ - {"nameHash", entry.nameHash}, - {"namePart", fullLengthName}, - }); - } - } - } - else - { - jEntry["name"] = std::string(entry.name, nameLen); - } - - jArray.emplace_back(std::move(jEntry)); - } - } - - return jArray; - } - - json BuildStateBitsTableJson(const GfxStateBits* stateBitsTable, const size_t count) - { - static const char* blendNames[]{ - "disabled", - "zero", - "one", - "srcColor", - "invSrcColor", - "srcAlpha", - "invSrcAlpha", - "destAlpha", - "invDestAlpha", - "destColor", - "invDestColor", - }; - static const char* blendOpNames[]{ - "disabled", - "add", - "subtract", - "revSubtract", - "min", - "max", - }; - static const char* depthTestNames[]{ - "always", - "less", - "equal", - "lessEqual", - }; - static const char* polygonOffsetNames[]{ - "0", - "1", - "2", - "shadowMap", - }; - static const char* stencilOpNames[]{ - "keep", - "zero", - "replace", - "incrSat", - "decrSat", - "invert", - "incr", - "decr", - }; - - auto jArray = json::array(); - - if (stateBitsTable) - { - for (auto index = 0u; index < count; index++) - { - const auto& entry = stateBitsTable[index]; - - const auto srcBlendRgb = (entry.loadBits[0] & GFXS0_SRCBLEND_RGB_MASK) >> GFXS0_SRCBLEND_RGB_SHIFT; - const auto dstBlendRgb = (entry.loadBits[0] & GFXS0_DSTBLEND_RGB_MASK) >> GFXS0_DSTBLEND_RGB_SHIFT; - const auto blendOpRgb = (entry.loadBits[0] & GFXS0_BLENDOP_RGB_MASK) >> GFXS0_BLENDOP_RGB_SHIFT; - const auto srcBlendAlpha = (entry.loadBits[0] & GFXS0_SRCBLEND_ALPHA_MASK) >> GFXS0_SRCBLEND_ALPHA_SHIFT; - const auto dstBlendAlpha = (entry.loadBits[0] & GFXS0_DSTBLEND_ALPHA_MASK) >> GFXS0_DSTBLEND_ALPHA_SHIFT; - const auto blendOpAlpha = (entry.loadBits[0] & GFXS0_BLENDOP_ALPHA_MASK) >> GFXS0_BLENDOP_ALPHA_SHIFT; - const auto depthTest = (entry.loadBits[1] & GFXS1_DEPTHTEST_MASK) >> GFXS1_DEPTHTEST_SHIFT; - const auto polygonOffset = (entry.loadBits[1] & GFXS1_POLYGON_OFFSET_MASK) >> GFXS1_POLYGON_OFFSET_SHIFT; - - const auto* alphaTest = "disable"; - if (entry.loadBits[0] & GFXS0_ATEST_GT_0) - alphaTest = "gt0"; - else if (entry.loadBits[0] & GFXS0_ATEST_LT_128) - alphaTest = "lt128"; - else if (entry.loadBits[0] & GFXS0_ATEST_GE_128) - alphaTest = "ge128"; - else - assert(entry.loadBits[0] & GFXS0_ATEST_DISABLE); - - const auto* cullFace = "none"; - if ((entry.loadBits[0] & GFXS0_CULL_MASK) == GFXS0_CULL_BACK) - cullFace = "back"; - else if ((entry.loadBits[0] & GFXS0_CULL_MASK) == GFXS0_CULL_FRONT) - cullFace = "front"; - else - assert((entry.loadBits[0] & GFXS0_CULL_MASK) == GFXS0_CULL_NONE); - - jArray.emplace_back(json{ - {"srcBlendRgb", ArrayEntry(blendNames, srcBlendRgb)}, - {"dstBlendRgb", ArrayEntry(blendNames, dstBlendRgb)}, - {"blendOpRgb", ArrayEntry(blendOpNames, blendOpRgb)}, - {"alphaTest", alphaTest}, - {"cullFace", cullFace}, - {"srcBlendAlpha", ArrayEntry(blendNames, srcBlendAlpha)}, - {"dstBlendAlpha", ArrayEntry(blendNames, dstBlendAlpha)}, - {"blendOpAlpha", ArrayEntry(blendOpNames, blendOpAlpha)}, - {"colorWriteRgb", (entry.loadBits[0] & GFXS0_COLORWRITE_RGB) ? true : false}, - {"colorWriteAlpha", (entry.loadBits[0] & GFXS0_COLORWRITE_ALPHA) ? true : false}, - {"polymodeLine", (entry.loadBits[0] & GFXS0_POLYMODE_LINE) ? true : false}, - - {"depthWrite", (entry.loadBits[1] & GFXS1_DEPTHWRITE) ? true : false}, - {"depthTest", (entry.loadBits[1] & GFXS1_DEPTHTEST_DISABLE) ? json("disable") : ArrayEntry(depthTestNames, depthTest)}, - {"polygonOffset", ArrayEntry(polygonOffsetNames, polygonOffset)}, - {"stencilFrontEnabled", (entry.loadBits[1] & GFXS1_STENCIL_FRONT_ENABLE) ? true : false}, - {"stencilBackEnabled", (entry.loadBits[1] & GFXS1_STENCIL_BACK_ENABLE) ? true : false}, - {"stencilFrontPass", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_FRONT_PASS_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilFrontFail", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_FRONT_FAIL_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilFrontZFail", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_FRONT_ZFAIL_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilFrontFunc", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_FRONT_FUNC_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilBackPass", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_BACK_PASS_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilBackFail", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_BACK_FAIL_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilBackZFail", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_BACK_ZFAIL_SHIFT) & GFXS_STENCILOP_MASK)}, - {"stencilBackFunc", ArrayEntry(stencilOpNames, (entry.loadBits[1] >> GFXS1_STENCIL_BACK_FUNC_SHIFT) & GFXS_STENCILOP_MASK)}, - }); - } - } - - return jArray; - } - - json BuildSurfaceTypeBitsJson(const unsigned surfaceTypeBits) - { - if (!surfaceTypeBits) - return json(surfaceTypeNames[SURF_TYPE_DEFAULT]); - - static constexpr auto NON_SURFACE_TYPE_BITS = - ~(std::numeric_limits::max() >> ((sizeof(unsigned) * 8) - (static_cast(SURF_TYPE_NUM) - 1))); - assert((surfaceTypeBits & NON_SURFACE_TYPE_BITS) == 0); - - std::ostringstream ss; - auto firstSurfaceType = true; - for (auto surfaceTypeIndex = static_cast(SURF_TYPE_BARK); surfaceTypeIndex < SURF_TYPE_NUM; surfaceTypeIndex++) - { - if ((surfaceTypeBits & (1 << (surfaceTypeIndex - 1))) == 0) - continue; - - if (firstSurfaceType) - firstSurfaceType = false; - else - ss << ","; - ss << surfaceTypeNames[surfaceTypeIndex]; - } - - if (firstSurfaceType) - return json(surfaceTypeNames[SURF_TYPE_DEFAULT]); - - return json(ss.str()); - } - - json BuildCharFlagsJson(const std::string& prefix, const unsigned char gameFlags) - { - std::vector values; - - for (auto i = 0u; i < (sizeof(gameFlags) * 8u); i++) - { - if (gameFlags & (1 << i)) - { - std::ostringstream ss; - ss << prefix << " 0x" << std::hex << (1 << i); - values.emplace_back(ss.str()); - } - } - - return json(values); - } -} // namespace IW3 - -bool AssetDumperMaterial::ShouldDump(XAssetInfo* asset) -{ - return true; -} - -void AssetDumperMaterial::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) -{ - auto* material = asset->Asset(); - - std::ostringstream ss; - ss << "materials/" << asset->m_name << ".json"; - const auto assetFile = context.OpenAssetFile(ss.str()); - - if (!assetFile) - return; - - auto& stream = *assetFile; - - static const char* cameraRegionNames[]{"lit", "decal", "emissive", "none"}; - static std::unordered_map sortKeyNames{ - {0, "distortion" }, - {1, "opaque water" }, - {2, "boat hull" }, - {3, "opaque ambient" }, - {4, "opaque" }, - {5, "sky" }, - {6, "skybox - sun / moon" }, - {7, "skybox - clouds" }, - {8, "skybox - horizon" }, - {9, "decal - bottom 1" }, - {10, "decal - bottom 2" }, - {11, "decal - bottom 3" }, - {12, "decal - static decal" }, - {13, "decal - middle 1" }, - {14, "decal - middle 2" }, - {15, "decal - middle 3" }, - {24, "decal - weapon impact" }, - {29, "decal - top 1" }, - {30, "decal - top 2" }, - {31, "decal - top 3" }, - {32, "multiplicative" }, - {33, "banner / curtain" }, - {34, "hair" }, - {35, "underwater" }, - {36, "transparent water" }, - {37, "corona" }, - {38, "window inside" }, - {39, "window outside" }, - {40, "before effects - bottom"}, - {41, "before effects - middle"}, - {42, "before effects - top" }, - {43, "blend / additive" }, - {48, "effect - auto sort" }, - {56, "after effects - bottom" }, - {57, "after effects - middle" }, - {58, "after effects - top" }, - {59, "viewmodel effect" }, - }; - - const auto foundSortKeyName = sortKeyNames.find(material->info.sortKey); - assert(foundSortKeyName != sortKeyNames.end()); - - const json j = { - {"info", - { -#if defined(FLAGS_DEBUG) && FLAGS_DEBUG == 1 - {"gameFlags", BuildCharFlagsJson("gameFlag", material->info.gameFlags)}, // TODO: Find out what gameflags mean -#else - {"gameFlags", material->info.gameFlags}, // TODO: Find out what gameflags mean -#endif - {"sortKey", foundSortKeyName != sortKeyNames.end() ? foundSortKeyName->second : std::to_string(material->info.sortKey)}, - {"textureAtlasRowCount", material->info.textureAtlasRowCount}, - {"textureAtlasColumnCount", material->info.textureAtlasColumnCount}, - {"drawSurf", - {{"objectId", static_cast(material->info.drawSurf.fields.objectId)}, - {"reflectionProbeIndex", static_cast(material->info.drawSurf.fields.reflectionProbeIndex)}, - {"customIndex", static_cast(material->info.drawSurf.fields.customIndex)}, - {"materialSortedIndex", static_cast(material->info.drawSurf.fields.materialSortedIndex)}, - {"prepass", static_cast(material->info.drawSurf.fields.prepass)}, - {"useHeroLighting", static_cast(material->info.drawSurf.fields.primaryLightIndex)}, - {"surfType", static_cast(material->info.drawSurf.fields.surfType)}, - {"primarySortKey", static_cast(material->info.drawSurf.fields.primarySortKey)}}}, - {"surfaceTypeBits", BuildSurfaceTypeBitsJson(material->info.surfaceTypeBits)}, - {"hashIndex", material->info.hashIndex}}}, - {"stateBitsEntry", std::vector(std::begin(material->stateBitsEntry), std::end(material->stateBitsEntry))}, -#if defined(FLAGS_DEBUG) && FLAGS_DEBUG == 1 - {"stateFlags", BuildCharFlagsJson("stateFlag", material->stateFlags)}, -#else - {"stateFlags", material->stateFlags}, -#endif - {"cameraRegion", ArrayEntry(cameraRegionNames, material->cameraRegion)}, - {"techniqueSet", material->techniqueSet && material->techniqueSet->name ? AssetName(material->techniqueSet->name) : nullptr}, - {"textureTable", BuildTextureTableJson(material->textureTable, material->textureCount)}, - {"constantTable", BuildConstantTableJson(material->constantTable, material->constantCount)}, - {"stateBitsTable", BuildStateBitsTableJson(material->stateBitsTable, material->stateBitsCount)}, - }; - - stream << std::setw(4) << j; -} diff --git a/src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.cpp b/src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.cpp new file mode 100644 index 00000000..bb3a87a8 --- /dev/null +++ b/src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.cpp @@ -0,0 +1,30 @@ +#include "DumperMaterialIW3.h" + +#include "Game/IW3/Material/JsonMaterialWriterIW3.h" +#include "Game/IW3/Material/MaterialConstantZoneStateIW3.h" +#include "Material/MaterialCommon.h" + +using namespace IW3; + +void AssetDumperMaterial::DumpPool(AssetDumpingContext& context, AssetPool* pool) +{ + auto* materialConstantState = context.GetZoneAssetDumperState(); + materialConstantState->ExtractNamesFromZone(); + + AbstractAssetDumper::DumpPool(context, pool); +} + +bool AssetDumperMaterial::ShouldDump(XAssetInfo* asset) +{ + return true; +} + +void AssetDumperMaterial::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) +{ + const auto assetFile = context.OpenAssetFile(material::GetFileNameForAssetName(asset->m_name)); + + if (!assetFile) + return; + + DumpMaterialAsJson(*assetFile, *asset->Asset(), context); +} diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.h b/src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.h similarity index 78% rename from src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.h rename to src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.h index 09933cf4..8438f6af 100644 --- a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.h +++ b/src/ObjWriting/Game/IW3/Material/DumperMaterialIW3.h @@ -7,6 +7,9 @@ namespace IW3 { class AssetDumperMaterial final : public AbstractAssetDumper { + public: + void DumpPool(AssetDumpingContext& context, AssetPool* pool) override; + protected: bool ShouldDump(XAssetInfo* asset) override; void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; diff --git a/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.cpp b/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.cpp new file mode 100644 index 00000000..9cd1099c --- /dev/null +++ b/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.cpp @@ -0,0 +1,250 @@ +#include "MaterialConstantZoneStateIW3.h" + +#include "Game/IW3/CommonIW3.h" +#include "Game/IW3/GameAssetPoolIW3.h" +#include "Game/IW3/GameIW3.h" +#include "ObjWriting.h" + +namespace IW3 +{ + 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 : IGame::GetGameById(GameId::IW3)->GetZones()) + { + const auto* assetPools = dynamic_cast(zone->m_pools.get()); + if (!assetPools) + return; + + for (const auto* techniqueSetInfo : *assetPools->m_technique_set) + { + const auto* techniqueSet = techniqueSetInfo->Asset(); + + for (const auto* technique : techniqueSet->techniques) + { + if (technique) + ExtractNamesFromTechnique(technique); + } + } + } + } + + unsigned MaterialConstantZoneState::HashString(const std::string& str) + { + return Common::R_HashString(str.c_str()); + } + + void MaterialConstantZoneState::ExtractNamesFromTechnique(const MaterialTechnique* technique) + { + if (!ShouldDumpFromStruct(technique)) + return; + + for (auto passIndex = 0u; passIndex < technique->passCount; passIndex++) + { + const auto& pass = technique->passArray[passIndex]; + + if (pass.vertexShader && pass.vertexShader->prog.loadDef.program) + ExtractNamesFromShader(pass.vertexShader->prog.loadDef.program, pass.vertexShader->prog.loadDef.programSize); + + if (pass.pixelShader && pass.pixelShader->prog.loadDef.program) + ExtractNamesFromShader(pass.pixelShader->prog.loadDef.program, pass.pixelShader->prog.loadDef.programSize); + } + } + + void MaterialConstantZoneState::AddStaticKnownNames() + { + for (const auto* knownConstantName : KNOWN_CONSTANT_NAMES) + AddConstantName(knownConstantName); + for (const auto* knownTextureDefName : KNOWN_TEXTURE_DEF_NAMES) + AddTextureDefName(knownTextureDefName); + } +} // namespace IW3 diff --git a/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.h b/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.h new file mode 100644 index 00000000..f46d514b --- /dev/null +++ b/src/ObjWriting/Game/IW3/Material/MaterialConstantZoneStateIW3.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Game/IW3/IW3.h" +#include "Material/AbstractMaterialConstantZoneState.h" + +#include + +namespace IW3 +{ + class MaterialConstantZoneState final : public AbstractMaterialConstantZoneStateDx9 + { + protected: + void ExtractNamesFromZoneInternal() override; + void ExtractNamesFromTechnique(const MaterialTechnique* technique); + void AddStaticKnownNames() override; + unsigned HashString(const std::string& str) override; + }; +} // namespace IW3 diff --git a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp index 2dc30adf..55da3a17 100644 --- a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp +++ b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp @@ -4,12 +4,12 @@ #include "AssetDumpers/AssetDumperLoadedSound.h" #include "AssetDumpers/AssetDumperLocalizeEntry.h" #include "AssetDumpers/AssetDumperMapEnts.h" -#include "AssetDumpers/AssetDumperMaterial.h" #include "AssetDumpers/AssetDumperRawFile.h" #include "AssetDumpers/AssetDumperStringTable.h" #include "AssetDumpers/AssetDumperWeapon.h" #include "AssetDumpers/AssetDumperXModel.h" #include "Game/IW3/GameAssetPoolIW3.h" +#include "Material/DumperMaterialIW3.h" #include "ObjWriting.h" using namespace IW3; diff --git a/src/ObjWriting/Game/IW3/ObjWriterIW3.h b/src/ObjWriting/Game/IW3/ObjWriterIW3.h index 6e6bdbe4..e28527d4 100644 --- a/src/ObjWriting/Game/IW3/ObjWriterIW3.h +++ b/src/ObjWriting/Game/IW3/ObjWriterIW3.h @@ -1,4 +1,5 @@ #pragma once + #include "IObjWriter.h" namespace IW3 diff --git a/src/ObjWriting/Material/JsonMaterialWriter.cpp.template b/src/ObjWriting/Material/JsonMaterialWriter.cpp.template index 2c0ef0f0..f87ca242 100644 --- a/src/ObjWriting/Material/JsonMaterialWriter.cpp.template +++ b/src/ObjWriting/Material/JsonMaterialWriter.cpp.template @@ -1,8 +1,12 @@ -#options GAME (IW4, IW5, T6) +#options GAME (IW3, IW4, IW5, T6) #filename "Game/" + GAME + "/Material/JsonMaterialWriter" + GAME + ".cpp" -#if GAME == "IW4" +#if GAME == "IW3" +#define FEATURE_IW3 +#define HAS_WATER +#define GAME_LOWER "iw3" +#elif GAME == "IW4" #define FEATURE_IW4 #define HAS_WATER #define GAME_LOWER "iw4" @@ -218,7 +222,7 @@ namespace assert(structured.alphaTestDisabled || structured.alphaTest == GFXS_ALPHA_TEST_GT_0 -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) || structured.alphaTest == GFXS_ALPHA_TEST_LT_128 #endif || structured.alphaTest == GFXS_ALPHA_TEST_GE_128); @@ -226,7 +230,7 @@ namespace jStateBitsTableEntry.alphaTest = JsonAlphaTest::DISABLED; else if (structured.alphaTest == GFXS_ALPHA_TEST_GT_0) jStateBitsTableEntry.alphaTest = JsonAlphaTest::GT0; -#if defined(FEATURE_IW4) || defined(FEATURE_IW5) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5) else if (structured.alphaTest == GFXS_ALPHA_TEST_LT_128) jStateBitsTableEntry.alphaTest = JsonAlphaTest::LT128; #endif diff --git a/src/ObjWriting/Material/JsonMaterialWriter.h.template b/src/ObjWriting/Material/JsonMaterialWriter.h.template index d44748db..82362d35 100644 --- a/src/ObjWriting/Material/JsonMaterialWriter.h.template +++ b/src/ObjWriting/Material/JsonMaterialWriter.h.template @@ -1,4 +1,4 @@ -#options GAME (IW4, IW5, T6) +#options GAME (IW3, IW4, IW5, T6) #filename "Game/" + GAME + "/Material/JsonMaterialWriter" + GAME + ".h" diff --git a/src/ZoneCommon/Game/IW3/GameAssetPoolIW3.h b/src/ZoneCommon/Game/IW3/GameAssetPoolIW3.h index 8f6b3bf9..9d675501 100644 --- a/src/ZoneCommon/Game/IW3/GameAssetPoolIW3.h +++ b/src/ZoneCommon/Game/IW3/GameAssetPoolIW3.h @@ -14,9 +14,6 @@ public: std::unique_ptr> m_xanim_parts; std::unique_ptr> m_xmodel; std::unique_ptr> m_material; - std::unique_ptr> m_material_pixel_shader; - std::unique_ptr> m_material_vertex_shader; - std::unique_ptr> m_material_vertex_decl; std::unique_ptr> m_technique_set; std::unique_ptr> m_image; std::unique_ptr> m_sound; diff --git a/test/ObjLoadingTests/Game/IW3/Material/LoaderMaterialIW3Test.cpp b/test/ObjLoadingTests/Game/IW3/Material/LoaderMaterialIW3Test.cpp new file mode 100644 index 00000000..1a469c62 --- /dev/null +++ b/test/ObjLoadingTests/Game/IW3/Material/LoaderMaterialIW3Test.cpp @@ -0,0 +1,577 @@ +#include "Game/IW3/Material/LoaderMaterialIW3.h" + +#include "Game/IW3/CommonIW3.h" +#include "Game/IW3/GameIW3.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include + +using namespace IW3; +using namespace Catch; +using namespace std::literals; + +namespace +{ + void GivenImage(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* image = memory.Alloc(); + image->name = memory.Dup(name.c_str()); + + AssetRegistration registration(name); + registration.SetAsset(image); + context.AddAsset(std::move(registration)); + } + + void GivenTechset(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = memory.Dup(name.c_str()); + + AssetRegistration registration(name); + registration.SetAsset(techset); + context.AddAsset(std::move(registration)); + } + + TEST_CASE("LoaderMaterial(IW3): Can parse material", "[iw3][material][assetloader]") + { + MockSearchPath searchPath; + searchPath.AddFileData("materials/wc/ch_plasterwall_long.json", + R"MATERIAL( +{ + "_game": "iw3", + "_type": "material", + "_version": 1, + "cameraRegion": "lit", + "constants": [ + { + "literal": [ + 0.800000011920929, + 2.0, + 1.0, + 0.625 + ], + "name": "envMapParms" + }, + { + "literal": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "name": "colorTint" + } + ], + "gameFlags": [ + "2", + "10", + "CASTS_SHADOW" + ], + "sortKey": 4, + "stateBits": [ + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": false, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": false, + "cullFace": "none", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offsetShadowmap", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "none", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "add", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "one", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "invdestalpha", + "stencilFront": { + "fail": "keep", + "func": "equal", + "pass": "keep", + "zfail": "keep" + } + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset2", + "polymodeLine": true, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "add", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "invsrccolor", + "polygonOffset": "offset2", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "zero" + } + ], + "stateBitsEntry": [ + 0, + 1, + 2, + 3, + 1, + -1, + -1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 4, + 4, + 4, + -1, + -1, + -1, + -1, + 5, + -1, + 1, + 6, + 1, + -1 + ], + "stateFlags": 57, + "surfaceTypeBits": 32768, + "techniqueSet": "wc_l_sm_r0c0n0s0", + "textureAtlas": { + "columns": 1, + "rows": 1 + }, + "textures": [ + { + "image": "~ch_plasterwall_long_spc-r-49~3c124bfe", + "name": "specularMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "nearest" + }, + "semantic": "specularMap" + }, + { + "image": "ch_plasterwall_long_nml", + "name": "normalMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "linear" + }, + "semantic": "normalMap" + }, + { + "image": "ch_plasterwall_long_col", + "name": "colorMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "linear" + }, + "semantic": "colorMap" + } + ] +})MATERIAL"); + + Zone zone("MockZone", 0, IGame::GetGameById(GameId::IW3)); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + GivenImage("~ch_plasterwall_long_spc-r-49~3c124bfe", context, memory); + GivenImage("ch_plasterwall_long_nml", context, memory); + GivenImage("ch_plasterwall_long_col", context, memory); + GivenTechset("wc_l_sm_r0c0n0s0", context, memory); + + auto loader = CreateMaterialLoader(memory, searchPath); + auto result = loader->CreateAsset("wc/ch_plasterwall_long", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* material = assetInfo->Asset(); + + REQUIRE(material->info.name == "wc/ch_plasterwall_long"s); + REQUIRE(material->info.gameFlags == 0x52); + REQUIRE(material->info.sortKey == 4); + REQUIRE(material->info.textureAtlasRowCount == 1); + REQUIRE(material->info.textureAtlasColumnCount == 1); + REQUIRE(material->info.surfaceTypeBits == 0x8000); + + constexpr int8_t expectedStateBitsEntry[]{0, 1, 2, 3, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, + -1, -1, -1, -1, 4, 4, 4, -1, -1, -1, -1, 5, -1, 1, 6, 1, -1}; + REQUIRE(std::memcmp(material->stateBitsEntry, expectedStateBitsEntry, sizeof(expectedStateBitsEntry)) == 0); + + REQUIRE(material->stateFlags == 57); + REQUIRE(material->cameraRegion == CAMERA_REGION_LIT); + + REQUIRE(material->techniqueSet != nullptr); + REQUIRE(material->techniqueSet->name != nullptr); + REQUIRE(material->techniqueSet->name == "wc_l_sm_r0c0n0s0"s); + + REQUIRE(material->textureCount == 3); + REQUIRE(material->textureTable); + + const auto& textureDef0 = material->textureTable[0]; + REQUIRE(textureDef0.nameHash == 0x34ecccb3); + REQUIRE(textureDef0.nameStart == 's'); + REQUIRE(textureDef0.nameEnd == 'p'); + REQUIRE(textureDef0.samplerState.filter == TEXTURE_FILTER_ANISO2X); + REQUIRE(textureDef0.samplerState.mipMap == SAMPLER_MIPMAP_ENUM_NEAREST); + REQUIRE(textureDef0.samplerState.clampU == 0); + REQUIRE(textureDef0.samplerState.clampV == 0); + REQUIRE(textureDef0.samplerState.clampW == 0); + REQUIRE(textureDef0.semantic == TS_SPECULAR_MAP); + REQUIRE(textureDef0.u.image); + REQUIRE(textureDef0.u.image->name); + REQUIRE(textureDef0.u.image->name == "~ch_plasterwall_long_spc-r-49~3c124bfe"s); + + const auto& textureDef1 = material->textureTable[1]; + REQUIRE(textureDef1.nameHash == 0x59d30d0f); + REQUIRE(textureDef1.nameStart == 'n'); + REQUIRE(textureDef1.nameEnd == 'p'); + REQUIRE(textureDef1.samplerState.filter == TEXTURE_FILTER_ANISO2X); + REQUIRE(textureDef1.samplerState.mipMap == SAMPLER_MIPMAP_ENUM_LINEAR); + REQUIRE(textureDef1.samplerState.clampU == 0); + REQUIRE(textureDef1.samplerState.clampV == 0); + REQUIRE(textureDef1.samplerState.clampW == 0); + REQUIRE(textureDef1.semantic == TS_NORMAL_MAP); + REQUIRE(textureDef1.u.image); + REQUIRE(textureDef1.u.image->name); + REQUIRE(textureDef1.u.image->name == "ch_plasterwall_long_nml"s); + + const auto& textureDef2 = material->textureTable[2]; + REQUIRE(textureDef2.nameHash == 0xa0ab1041); + REQUIRE(textureDef2.nameStart == 'c'); + REQUIRE(textureDef2.nameEnd == 'p'); + REQUIRE(textureDef2.samplerState.filter == TEXTURE_FILTER_ANISO2X); + REQUIRE(textureDef2.samplerState.mipMap == SAMPLER_MIPMAP_ENUM_LINEAR); + REQUIRE(textureDef2.samplerState.clampU == 0); + REQUIRE(textureDef2.samplerState.clampV == 0); + REQUIRE(textureDef2.samplerState.clampW == 0); + REQUIRE(textureDef2.semantic == TS_COLOR_MAP); + REQUIRE(textureDef2.u.image); + REQUIRE(textureDef2.u.image->name); + REQUIRE(textureDef2.u.image->name == "ch_plasterwall_long_col"s); + + REQUIRE(material->constantCount == 2); + REQUIRE(material->constantTable); + + const auto& constantDef0 = material->constantTable[0]; + REQUIRE(constantDef0.nameHash == 0x3d9994dc); + REQUIRE(strncmp(constantDef0.name, "envMapParms", std::extent_v) == 0); + REQUIRE(constantDef0.literal.x == Approx(0.8f)); + REQUIRE(constantDef0.literal.y == Approx(2.0f)); + REQUIRE(constantDef0.literal.z == Approx(1.0f)); + REQUIRE(constantDef0.literal.w == Approx(0.625f)); + + const auto& constantDef1 = material->constantTable[1]; + REQUIRE(constantDef1.nameHash == 0xb60c3b3a); + REQUIRE(strncmp(constantDef1.name, "colorTint", std::extent_v) == 0); + REQUIRE(constantDef1.literal.x == Approx(1.0f)); + REQUIRE(constantDef1.literal.y == Approx(1.0f)); + REQUIRE(constantDef1.literal.z == Approx(1.0f)); + REQUIRE(constantDef1.literal.w == Approx(1.0f)); + + REQUIRE(material->stateBitsCount == 7); + REQUIRE(material->stateBitsTable); + + const auto& stateBits0 = material->stateBitsTable[0]; + REQUIRE(stateBits0.loadBits.structured.srcBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits0.loadBits.structured.dstBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits0.loadBits.structured.blendOpRgb == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits0.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits0.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits0.loadBits.structured.cullFace == GFXS_CULL_BACK); + REQUIRE(stateBits0.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits0.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits0.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits0.loadBits.structured.colorWriteRgb == 0); + REQUIRE(stateBits0.loadBits.structured.colorWriteAlpha == 0); + REQUIRE(stateBits0.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits0.loadBits.structured.depthWrite == 1); + REQUIRE(stateBits0.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits0.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits0.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_0); + REQUIRE(stateBits0.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits0.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits0.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits0.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits0.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits0.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits0.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits0.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits0.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits0.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits1 = material->stateBitsTable[1]; + REQUIRE(stateBits1.loadBits.structured.srcBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits1.loadBits.structured.dstBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits1.loadBits.structured.blendOpRgb == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits1.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits1.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits1.loadBits.structured.cullFace == GFXS_CULL_BACK); + REQUIRE(stateBits1.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits1.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits1.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits1.loadBits.structured.colorWriteRgb == 1); + REQUIRE(stateBits1.loadBits.structured.colorWriteAlpha == 1); + REQUIRE(stateBits1.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits1.loadBits.structured.depthWrite == 1); + REQUIRE(stateBits1.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits1.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits1.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_0); + REQUIRE(stateBits1.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits1.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits1.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits1.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits1.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits1.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits1.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits1.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits1.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits1.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits2 = material->stateBitsTable[2]; + REQUIRE(stateBits2.loadBits.structured.srcBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits2.loadBits.structured.dstBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits2.loadBits.structured.blendOpRgb == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits2.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits2.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits2.loadBits.structured.cullFace == GFXS_CULL_NONE); + REQUIRE(stateBits2.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits2.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits2.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits2.loadBits.structured.colorWriteRgb == 0); + REQUIRE(stateBits2.loadBits.structured.colorWriteAlpha == 0); + REQUIRE(stateBits2.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits2.loadBits.structured.depthWrite == 1); + REQUIRE(stateBits2.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits2.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits2.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_SHADOWMAP); + REQUIRE(stateBits2.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits2.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits2.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits2.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits2.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits2.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits2.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits2.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits2.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits2.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits3 = material->stateBitsTable[3]; + REQUIRE(stateBits3.loadBits.structured.srcBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits3.loadBits.structured.dstBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits3.loadBits.structured.blendOpRgb == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits3.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits3.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits3.loadBits.structured.cullFace == GFXS_CULL_NONE); + REQUIRE(stateBits3.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits3.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits3.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits3.loadBits.structured.colorWriteRgb == 1); + REQUIRE(stateBits3.loadBits.structured.colorWriteAlpha == 1); + REQUIRE(stateBits3.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits3.loadBits.structured.depthWrite == 1); + REQUIRE(stateBits3.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits3.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits3.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_0); + REQUIRE(stateBits3.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits3.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits3.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits3.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits3.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits3.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits3.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits3.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits3.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits3.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits4 = material->stateBitsTable[4]; + REQUIRE(stateBits4.loadBits.structured.srcBlendRgb == GFXS_BLEND_INVDESTALPHA); + REQUIRE(stateBits4.loadBits.structured.dstBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits4.loadBits.structured.blendOpRgb == GFXS_BLENDOP_ADD); + REQUIRE(stateBits4.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits4.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits4.loadBits.structured.cullFace == GFXS_CULL_BACK); + REQUIRE(stateBits4.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits4.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits4.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits4.loadBits.structured.colorWriteRgb == 1); + REQUIRE(stateBits4.loadBits.structured.colorWriteAlpha == 1); + REQUIRE(stateBits4.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits4.loadBits.structured.depthWrite == 0); + REQUIRE(stateBits4.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits4.loadBits.structured.depthTest == GFXS_DEPTHTEST_EQUAL); + REQUIRE(stateBits4.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_0); + REQUIRE(stateBits4.loadBits.structured.stencilFrontEnabled == 1); + REQUIRE(stateBits4.loadBits.structured.stencilFrontPass == GFXS_STENCILOP_KEEP); + REQUIRE(stateBits4.loadBits.structured.stencilFrontFail == GFXS_STENCILOP_KEEP); + REQUIRE(stateBits4.loadBits.structured.stencilFrontZFail == GFXS_STENCILOP_KEEP); + REQUIRE(stateBits4.loadBits.structured.stencilFrontFunc == GFXS_STENCILFUNC_EQUAL); + REQUIRE(stateBits4.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits4.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits4.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits4.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits4.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits5 = material->stateBitsTable[5]; + REQUIRE(stateBits5.loadBits.structured.srcBlendRgb == GFXS_BLEND_ONE); + REQUIRE(stateBits5.loadBits.structured.dstBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits5.loadBits.structured.blendOpRgb == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits5.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits5.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits5.loadBits.structured.cullFace == GFXS_CULL_BACK); + REQUIRE(stateBits5.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits5.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits5.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits5.loadBits.structured.colorWriteRgb == 1); + REQUIRE(stateBits5.loadBits.structured.colorWriteAlpha == 0); + REQUIRE(stateBits5.loadBits.structured.polymodeLine == 1); + REQUIRE(stateBits5.loadBits.structured.depthWrite == 0); + REQUIRE(stateBits5.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits5.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits5.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_2); + REQUIRE(stateBits5.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits5.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits5.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits5.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits5.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits5.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits5.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits5.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits5.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits5.loadBits.structured.stencilBackFunc == 0); + + const auto& stateBits6 = material->stateBitsTable[6]; + REQUIRE(stateBits6.loadBits.structured.srcBlendRgb == GFXS_BLEND_ZERO); + REQUIRE(stateBits6.loadBits.structured.dstBlendRgb == GFXS_BLEND_INVSRCCOLOR); + REQUIRE(stateBits6.loadBits.structured.blendOpRgb == GFXS_BLENDOP_ADD); + REQUIRE(stateBits6.loadBits.structured.alphaTestDisabled == 1); + REQUIRE(stateBits6.loadBits.structured.alphaTest == 0); + REQUIRE(stateBits6.loadBits.structured.cullFace == GFXS_CULL_BACK); + REQUIRE(stateBits6.loadBits.structured.srcBlendAlpha == GFXS_BLEND_ONE); + REQUIRE(stateBits6.loadBits.structured.dstBlendAlpha == GFXS_BLEND_ZERO); + REQUIRE(stateBits6.loadBits.structured.blendOpAlpha == GFXS_BLENDOP_DISABLED); + REQUIRE(stateBits6.loadBits.structured.colorWriteRgb == 1); + REQUIRE(stateBits6.loadBits.structured.colorWriteAlpha == 1); + REQUIRE(stateBits6.loadBits.structured.polymodeLine == 0); + REQUIRE(stateBits6.loadBits.structured.depthWrite == 0); + REQUIRE(stateBits6.loadBits.structured.depthTestDisabled == 0); + REQUIRE(stateBits6.loadBits.structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL); + REQUIRE(stateBits6.loadBits.structured.polygonOffset == GFXS_POLYGON_OFFSET_2); + REQUIRE(stateBits6.loadBits.structured.stencilFrontEnabled == 0); + REQUIRE(stateBits6.loadBits.structured.stencilFrontPass == 0); + REQUIRE(stateBits6.loadBits.structured.stencilFrontFail == 0); + REQUIRE(stateBits6.loadBits.structured.stencilFrontZFail == 0); + REQUIRE(stateBits6.loadBits.structured.stencilFrontFunc == 0); + REQUIRE(stateBits6.loadBits.structured.stencilBackEnabled == 0); + REQUIRE(stateBits6.loadBits.structured.stencilBackPass == 0); + REQUIRE(stateBits6.loadBits.structured.stencilBackFail == 0); + REQUIRE(stateBits6.loadBits.structured.stencilBackZFail == 0); + REQUIRE(stateBits6.loadBits.structured.stencilBackFunc == 0); + } +} // namespace diff --git a/test/ObjWritingTests/Game/IW3/Material/DumperMaterialIW3Test.cpp b/test/ObjWritingTests/Game/IW3/Material/DumperMaterialIW3Test.cpp new file mode 100644 index 00000000..42871ad2 --- /dev/null +++ b/test/ObjWritingTests/Game/IW3/Material/DumperMaterialIW3Test.cpp @@ -0,0 +1,571 @@ +#include "Game/IW3/Material/DumperMaterialIW3.h" + +#include "Asset/AssetRegistration.h" +#include "Game/IW3/CommonIW3.h" +#include "Game/IW3/GameIW3.h" +#include "NormalizedJson.h" +#include "Pool/AssetPoolDynamic.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include + +using namespace IW3; +using namespace Catch; +using namespace std::literals; + +namespace +{ + GfxImage* GivenImage(const std::string& name, MemoryManager& memory) + { + auto* image = memory.Alloc(); + image->name = memory.Dup(name.c_str()); + + return image; + } + + MaterialTechniqueSet* GivenTechset(const std::string& name, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = memory.Dup(name.c_str()); + + return techset; + } + + void GivenMaterial(const std::string& name, AssetPool& pool, MemoryManager& memory) + { + auto* material = memory.Alloc(); + material->info.name = memory.Dup(name.c_str()); + material->info.gameFlags = 0x52; + material->info.sortKey = 4; + material->info.textureAtlasRowCount = 1; + material->info.textureAtlasColumnCount = 1; + material->info.surfaceTypeBits = 0x8000; + + constexpr int8_t stateBitsEntry[] = {0, 1, 2, 3, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, + -1, -1, -1, -1, 4, 4, 4, -1, -1, -1, -1, 5, -1, 1, 6, 1, -1}; + std::memcpy(material->stateBitsEntry, stateBitsEntry, sizeof(material->stateBitsEntry)); + + material->cameraRegion = CAMERA_REGION_LIT; + material->stateFlags = 57; + material->techniqueSet = GivenTechset("wc_l_sm_r0c0n0s0", memory); + + material->textureCount = 3; + material->textureTable = memory.Alloc(3); + + auto& textureDef0 = material->textureTable[0]; + textureDef0.u.image = GivenImage("~ch_plasterwall_long_spc-r-49~3c124bfe", memory); + textureDef0.nameHash = 0x34ecccb3; + textureDef0.nameStart = 's'; + textureDef0.nameEnd = 'p'; + textureDef0.samplerState.filter = TEXTURE_FILTER_ANISO2X; + textureDef0.samplerState.mipMap = SAMPLER_MIPMAP_ENUM_NEAREST; + textureDef0.samplerState.clampU = 0; + textureDef0.samplerState.clampV = 0; + textureDef0.samplerState.clampW = 0; + textureDef0.semantic = TS_SPECULAR_MAP; + + auto& textureDef1 = material->textureTable[1]; + textureDef1.u.image = GivenImage("ch_plasterwall_long_nml", memory); + textureDef1.nameHash = 0x59d30d0f; + textureDef1.nameStart = 'n'; + textureDef1.nameEnd = 'p'; + textureDef1.samplerState.filter = TEXTURE_FILTER_ANISO2X; + textureDef1.samplerState.mipMap = SAMPLER_MIPMAP_ENUM_LINEAR; + textureDef1.samplerState.clampU = 0; + textureDef1.samplerState.clampV = 0; + textureDef1.samplerState.clampW = 0; + textureDef1.semantic = TS_NORMAL_MAP; + + auto& textureDef2 = material->textureTable[2]; + textureDef2.u.image = GivenImage("ch_plasterwall_long_col", memory); + textureDef2.nameHash = 0xa0ab1041; + textureDef2.nameStart = 'c'; + textureDef2.nameEnd = 'p'; + textureDef2.samplerState.filter = TEXTURE_FILTER_ANISO2X; + textureDef2.samplerState.mipMap = SAMPLER_MIPMAP_ENUM_LINEAR; + textureDef2.samplerState.clampU = 0; + textureDef2.samplerState.clampV = 0; + textureDef2.samplerState.clampW = 0; + textureDef2.semantic = TS_COLOR_MAP; + + material->constantCount = 2; + material->constantTable = memory.Alloc(2); + + auto& constantDef0 = material->constantTable[0]; + constantDef0.nameHash = 0x3d9994dc; + strncpy(constantDef0.name, "envMapParms", std::extent_v); + constantDef0.literal.x = 0.8f; + constantDef0.literal.y = 2.0f; + constantDef0.literal.z = 1.0f; + constantDef0.literal.w = 0.625f; + + auto& constantDef1 = material->constantTable[1]; + constantDef1.nameHash = 0xb60c3b3a; + strncpy(constantDef1.name, "colorTint", std::extent_v); + constantDef1.literal.x = 1.0f; + constantDef1.literal.y = 1.0f; + constantDef1.literal.z = 1.0f; + constantDef1.literal.w = 1.0f; + + material->stateBitsCount = 7; + material->stateBitsTable = memory.Alloc(7); + + auto& stateBits0 = material->stateBitsTable[0]; + stateBits0.loadBits.structured.srcBlendRgb = GFXS_BLEND_ONE; + stateBits0.loadBits.structured.dstBlendRgb = GFXS_BLEND_ZERO; + stateBits0.loadBits.structured.blendOpRgb = GFXS_BLENDOP_DISABLED; + stateBits0.loadBits.structured.alphaTestDisabled = 1; + stateBits0.loadBits.structured.alphaTest = 0; + stateBits0.loadBits.structured.cullFace = GFXS_CULL_BACK; + stateBits0.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits0.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits0.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits0.loadBits.structured.colorWriteRgb = 0; + stateBits0.loadBits.structured.colorWriteAlpha = 0; + stateBits0.loadBits.structured.polymodeLine = 0; + stateBits0.loadBits.structured.depthWrite = 1; + stateBits0.loadBits.structured.depthTestDisabled = 0; + stateBits0.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits0.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_0; + stateBits0.loadBits.structured.stencilFrontEnabled = 0; + stateBits0.loadBits.structured.stencilFrontPass = 0; + stateBits0.loadBits.structured.stencilFrontFail = 0; + stateBits0.loadBits.structured.stencilFrontZFail = 0; + stateBits0.loadBits.structured.stencilFrontFunc = 0; + stateBits0.loadBits.structured.stencilBackEnabled = 0; + stateBits0.loadBits.structured.stencilBackPass = 0; + stateBits0.loadBits.structured.stencilBackFail = 0; + stateBits0.loadBits.structured.stencilBackZFail = 0; + stateBits0.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits1 = material->stateBitsTable[1]; + stateBits1.loadBits.structured.srcBlendRgb = GFXS_BLEND_ONE; + stateBits1.loadBits.structured.dstBlendRgb = GFXS_BLEND_ZERO; + stateBits1.loadBits.structured.blendOpRgb = GFXS_BLENDOP_DISABLED; + stateBits1.loadBits.structured.alphaTestDisabled = 1; + stateBits1.loadBits.structured.alphaTest = 0; + stateBits1.loadBits.structured.cullFace = GFXS_CULL_BACK; + stateBits1.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits1.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits1.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits1.loadBits.structured.colorWriteRgb = 1; + stateBits1.loadBits.structured.colorWriteAlpha = 1; + stateBits1.loadBits.structured.polymodeLine = 0; + stateBits1.loadBits.structured.depthWrite = 1; + stateBits1.loadBits.structured.depthTestDisabled = 0; + stateBits1.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits1.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_0; + stateBits1.loadBits.structured.stencilFrontEnabled = 0; + stateBits1.loadBits.structured.stencilFrontPass = 0; + stateBits1.loadBits.structured.stencilFrontFail = 0; + stateBits1.loadBits.structured.stencilFrontZFail = 0; + stateBits1.loadBits.structured.stencilFrontFunc = 0; + stateBits1.loadBits.structured.stencilBackEnabled = 0; + stateBits1.loadBits.structured.stencilBackPass = 0; + stateBits1.loadBits.structured.stencilBackFail = 0; + stateBits1.loadBits.structured.stencilBackZFail = 0; + stateBits1.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits2 = material->stateBitsTable[2]; + stateBits2.loadBits.structured.srcBlendRgb = GFXS_BLEND_ONE; + stateBits2.loadBits.structured.dstBlendRgb = GFXS_BLEND_ZERO; + stateBits2.loadBits.structured.blendOpRgb = GFXS_BLENDOP_DISABLED; + stateBits2.loadBits.structured.alphaTestDisabled = 1; + stateBits2.loadBits.structured.alphaTest = 0; + stateBits2.loadBits.structured.cullFace = GFXS_CULL_NONE; + stateBits2.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits2.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits2.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits2.loadBits.structured.colorWriteRgb = 0; + stateBits2.loadBits.structured.colorWriteAlpha = 0; + stateBits2.loadBits.structured.polymodeLine = 0; + stateBits2.loadBits.structured.depthWrite = 1; + stateBits2.loadBits.structured.depthTestDisabled = 0; + stateBits2.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits2.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_SHADOWMAP; + stateBits2.loadBits.structured.stencilFrontEnabled = 0; + stateBits2.loadBits.structured.stencilFrontPass = 0; + stateBits2.loadBits.structured.stencilFrontFail = 0; + stateBits2.loadBits.structured.stencilFrontZFail = 0; + stateBits2.loadBits.structured.stencilFrontFunc = 0; + stateBits2.loadBits.structured.stencilBackEnabled = 0; + stateBits2.loadBits.structured.stencilBackPass = 0; + stateBits2.loadBits.structured.stencilBackFail = 0; + stateBits2.loadBits.structured.stencilBackZFail = 0; + stateBits2.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits3 = material->stateBitsTable[3]; + stateBits3.loadBits.structured.srcBlendRgb = GFXS_BLEND_ONE; + stateBits3.loadBits.structured.dstBlendRgb = GFXS_BLEND_ZERO; + stateBits3.loadBits.structured.blendOpRgb = GFXS_BLENDOP_DISABLED; + stateBits3.loadBits.structured.alphaTestDisabled = 1; + stateBits3.loadBits.structured.alphaTest = 0; + stateBits3.loadBits.structured.cullFace = GFXS_CULL_NONE; + stateBits3.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits3.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits3.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits3.loadBits.structured.colorWriteRgb = 1; + stateBits3.loadBits.structured.colorWriteAlpha = 1; + stateBits3.loadBits.structured.polymodeLine = 0; + stateBits3.loadBits.structured.depthWrite = 1; + stateBits3.loadBits.structured.depthTestDisabled = 0; + stateBits3.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits3.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_0; + stateBits3.loadBits.structured.stencilFrontEnabled = 0; + stateBits3.loadBits.structured.stencilFrontPass = 0; + stateBits3.loadBits.structured.stencilFrontFail = 0; + stateBits3.loadBits.structured.stencilFrontZFail = 0; + stateBits3.loadBits.structured.stencilFrontFunc = 0; + stateBits3.loadBits.structured.stencilBackEnabled = 0; + stateBits3.loadBits.structured.stencilBackPass = 0; + stateBits3.loadBits.structured.stencilBackFail = 0; + stateBits3.loadBits.structured.stencilBackZFail = 0; + stateBits3.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits4 = material->stateBitsTable[4]; + stateBits4.loadBits.structured.srcBlendRgb = GFXS_BLEND_INVDESTALPHA; + stateBits4.loadBits.structured.dstBlendRgb = GFXS_BLEND_ONE; + stateBits4.loadBits.structured.blendOpRgb = GFXS_BLENDOP_ADD; + stateBits4.loadBits.structured.alphaTestDisabled = 1; + stateBits4.loadBits.structured.alphaTest = 0; + stateBits4.loadBits.structured.cullFace = GFXS_CULL_BACK; + stateBits4.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits4.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits4.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits4.loadBits.structured.colorWriteRgb = 1; + stateBits4.loadBits.structured.colorWriteAlpha = 1; + stateBits4.loadBits.structured.polymodeLine = 0; + stateBits4.loadBits.structured.depthWrite = 0; + stateBits4.loadBits.structured.depthTestDisabled = 0; + stateBits4.loadBits.structured.depthTest = GFXS_DEPTHTEST_EQUAL; + stateBits4.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_0; + stateBits4.loadBits.structured.stencilFrontEnabled = 1; + stateBits4.loadBits.structured.stencilFrontPass = GFXS_STENCILOP_KEEP; + stateBits4.loadBits.structured.stencilFrontFail = GFXS_STENCILOP_KEEP; + stateBits4.loadBits.structured.stencilFrontZFail = GFXS_STENCILOP_KEEP; + stateBits4.loadBits.structured.stencilFrontFunc = GFXS_STENCILFUNC_EQUAL; + stateBits4.loadBits.structured.stencilBackEnabled = 0; + stateBits4.loadBits.structured.stencilBackPass = 0; + stateBits4.loadBits.structured.stencilBackFail = 0; + stateBits4.loadBits.structured.stencilBackZFail = 0; + stateBits4.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits5 = material->stateBitsTable[5]; + stateBits5.loadBits.structured.srcBlendRgb = GFXS_BLEND_ONE; + stateBits5.loadBits.structured.dstBlendRgb = GFXS_BLEND_ZERO; + stateBits5.loadBits.structured.blendOpRgb = GFXS_BLENDOP_DISABLED; + stateBits5.loadBits.structured.alphaTestDisabled = 1; + stateBits5.loadBits.structured.alphaTest = 0; + stateBits5.loadBits.structured.cullFace = GFXS_CULL_BACK; + stateBits5.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits5.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits5.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits5.loadBits.structured.colorWriteRgb = 1; + stateBits5.loadBits.structured.colorWriteAlpha = 0; + stateBits5.loadBits.structured.polymodeLine = 1; + stateBits5.loadBits.structured.depthWrite = 0; + stateBits5.loadBits.structured.depthTestDisabled = 0; + stateBits5.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits5.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_2; + stateBits5.loadBits.structured.stencilFrontEnabled = 0; + stateBits5.loadBits.structured.stencilFrontPass = 0; + stateBits5.loadBits.structured.stencilFrontFail = 0; + stateBits5.loadBits.structured.stencilFrontZFail = 0; + stateBits5.loadBits.structured.stencilFrontFunc = 0; + stateBits5.loadBits.structured.stencilBackEnabled = 0; + stateBits5.loadBits.structured.stencilBackPass = 0; + stateBits5.loadBits.structured.stencilBackFail = 0; + stateBits5.loadBits.structured.stencilBackZFail = 0; + stateBits5.loadBits.structured.stencilBackFunc = 0; + + auto& stateBits6 = material->stateBitsTable[6]; + stateBits6.loadBits.structured.srcBlendRgb = GFXS_BLEND_ZERO; + stateBits6.loadBits.structured.dstBlendRgb = GFXS_BLEND_INVSRCCOLOR; + stateBits6.loadBits.structured.blendOpRgb = GFXS_BLENDOP_ADD; + stateBits6.loadBits.structured.alphaTestDisabled = 1; + stateBits6.loadBits.structured.alphaTest = 0; + stateBits6.loadBits.structured.cullFace = GFXS_CULL_BACK; + stateBits6.loadBits.structured.srcBlendAlpha = GFXS_BLEND_ONE; + stateBits6.loadBits.structured.dstBlendAlpha = GFXS_BLEND_ZERO; + stateBits6.loadBits.structured.blendOpAlpha = GFXS_BLENDOP_DISABLED; + stateBits6.loadBits.structured.colorWriteRgb = 1; + stateBits6.loadBits.structured.colorWriteAlpha = 1; + stateBits6.loadBits.structured.polymodeLine = 0; + stateBits6.loadBits.structured.depthWrite = 0; + stateBits6.loadBits.structured.depthTestDisabled = 0; + stateBits6.loadBits.structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + stateBits6.loadBits.structured.polygonOffset = GFXS_POLYGON_OFFSET_2; + stateBits6.loadBits.structured.stencilFrontEnabled = 0; + stateBits6.loadBits.structured.stencilFrontPass = 0; + stateBits6.loadBits.structured.stencilFrontFail = 0; + stateBits6.loadBits.structured.stencilFrontZFail = 0; + stateBits6.loadBits.structured.stencilFrontFunc = 0; + stateBits6.loadBits.structured.stencilBackEnabled = 0; + stateBits6.loadBits.structured.stencilBackPass = 0; + stateBits6.loadBits.structured.stencilBackFail = 0; + stateBits6.loadBits.structured.stencilBackZFail = 0; + stateBits6.loadBits.structured.stencilBackFunc = 0; + + pool.AddAsset(std::make_unique>(ASSET_TYPE_MATERIAL, name, material)); + } + + TEST_CASE("DumperMaterial(IW3): Can dump material", "[iw3][material][assetdumper]") + { + std::string expected(R"MATERIAL( +{ + "_game": "iw3", + "_type": "material", + "_version": 1, + "cameraRegion": "lit", + "constants": [ + { + "literal": [ + 0.800000011920929, + 2.0, + 1.0, + 0.625 + ], + "name": "envMapParms" + }, + { + "literal": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "name": "colorTint" + } + ], + "gameFlags": [ + "2", + "10", + "CASTS_SHADOW" + ], + "sortKey": 4, + "stateBits": [ + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": false, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": false, + "cullFace": "none", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offsetShadowmap", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "none", + "depthTest": "less_equal", + "depthWrite": true, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "add", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "one", + "polygonOffset": "offset0", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "invdestalpha", + "stencilFront": { + "fail": "keep", + "func": "equal", + "pass": "keep", + "zfail": "keep" + } + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "disabled", + "colorWriteAlpha": false, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "zero", + "polygonOffset": "offset2", + "polymodeLine": true, + "srcBlendAlpha": "one", + "srcBlendRgb": "one" + }, + { + "alphaTest": "disabled", + "blendOpAlpha": "disabled", + "blendOpRgb": "add", + "colorWriteAlpha": true, + "colorWriteRgb": true, + "cullFace": "back", + "depthTest": "less_equal", + "depthWrite": false, + "dstBlendAlpha": "zero", + "dstBlendRgb": "invsrccolor", + "polygonOffset": "offset2", + "polymodeLine": false, + "srcBlendAlpha": "one", + "srcBlendRgb": "zero" + } + ], + "stateBitsEntry": [ + 0, + 1, + 2, + 3, + 1, + -1, + -1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 4, + 4, + 4, + -1, + -1, + -1, + -1, + 5, + -1, + 1, + 6, + 1, + -1 + ], + "stateFlags": 57, + "surfaceTypeBits": 32768, + "techniqueSet": "wc_l_sm_r0c0n0s0", + "textureAtlas": { + "columns": 1, + "rows": 1 + }, + "textures": [ + { + "image": "~ch_plasterwall_long_spc-r-49~3c124bfe", + "name": "specularMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "nearest" + }, + "semantic": "specularMap" + }, + { + "image": "ch_plasterwall_long_nml", + "name": "normalMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "linear" + }, + "semantic": "normalMap" + }, + { + "image": "ch_plasterwall_long_col", + "name": "colorMap", + "samplerState": { + "clampU": false, + "clampV": false, + "clampW": false, + "filter": "aniso2x", + "mipMap": "linear" + }, + "semantic": "colorMap" + } + ] +})MATERIAL"); + + Zone zone("MockZone", 0, IGame::GetGameById(GameId::IW3)); + + MemoryManager memory; + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath); + + AssetPoolDynamic materialPool(0); + + GivenMaterial("wc/ch_plasterwall_long", materialPool, memory); + + AssetDumperMaterial dumper; + dumper.DumpPool(context, &materialPool); + + const auto* file = mockOutput.GetMockedFile("materials/wc/ch_plasterwall_long.json"); + REQUIRE(file); + REQUIRE(JsonNormalized(file->AsString()) == JsonNormalized(expected)); + } +} // namespace