diff --git a/src/Common/Game/IW3/CommonIW3.h b/src/Common/Game/IW3/CommonIW3.h index 7bfef8c6..26e019f5 100644 --- a/src/Common/Game/IW3/CommonIW3.h +++ b/src/Common/Game/IW3/CommonIW3.h @@ -7,6 +7,20 @@ namespace IW3 class Common { public: + static constexpr uint32_t R_HashString(const char* string, const uint32_t hash) + { + const char* v2 = string; // edx@1 + char v3 = *string; // cl@1 + uint32_t result = hash; + + for (; *v2; v3 = *v2) + { + ++v2; + result = 33 * result ^ (v3 | 0x20); + } + return result; + } + static PackedTexCoords Vec2PackTexCoords(const vec2_t* in); static PackedUnitVec Vec3PackUnitVec(const vec3_t* in); static GfxColor Vec4PackGfxColor(const vec4_t* in); diff --git a/src/Common/Game/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index 1d79c0ba..145e03c7 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -509,6 +509,109 @@ namespace IW3 PhysGeomList* physGeoms; }; + 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_MASK = 0xF, + }; + + 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_MASK = 0x7, + }; + + 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, + + GFXS_STENCILOP_COUNT, + GFXS_STENCILOP_MASK = 0x7 + }; + + enum GfxStateBitsEnum : unsigned int + { + GFXS0_SRCBLEND_RGB_SHIFT = 0x0, + GFXS0_SRCBLEND_RGB_MASK = 0xF, + GFXS0_DSTBLEND_RGB_SHIFT = 0x4, + GFXS0_DSTBLEND_RGB_MASK = 0xF0, + GFXS0_BLENDOP_RGB_SHIFT = 0x8, + GFXS0_BLENDOP_RGB_MASK = 0x700, + GFXS0_BLEND_RGB_MASK = 0x7FF, + GFXS0_ATEST_DISABLE = 0x800, + GFXS0_ATEST_GT_0 = 0x1000, + GFXS0_ATEST_LT_128 = 0x2000, + 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_MASK = 0xC000, + GFXS0_SRCBLEND_ALPHA_SHIFT = 0x10, + GFXS0_SRCBLEND_ALPHA_MASK = 0xF0000, + GFXS0_DSTBLEND_ALPHA_SHIFT = 0x14, + GFXS0_DSTBLEND_ALPHA_MASK = 0xF00000, + GFXS0_BLENDOP_ALPHA_SHIFT = 0x18, + GFXS0_BLENDOP_ALPHA_MASK = 0x7000000, + GFXS0_BLEND_ALPHA_MASK = 0x7FF0000, + GFXS0_COLORWRITE_RGB = 0x8000000, + GFXS0_COLORWRITE_ALPHA = 0x10000000, + GFXS0_COLORWRITE_MASK = 0x18000000, + GFXS0_POLYMODE_LINE = 0x80000000, + 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_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_MASK = 0x30, + GFXS1_STENCIL_FRONT_ENABLE = 0x40, + GFXS1_STENCIL_BACK_ENABLE = 0x80, + GFXS1_STENCIL_MASK = 0xC0, + GFXS1_STENCIL_FRONT_PASS_SHIFT = 0x8, + GFXS1_STENCIL_FRONT_FAIL_SHIFT = 0xB, + GFXS1_STENCIL_FRONT_ZFAIL_SHIFT = 0xE, + GFXS1_STENCIL_FRONT_FUNC_SHIFT = 0x11, + GFXS1_STENCIL_FRONT_MASK = 0xFFF00, + GFXS1_STENCIL_BACK_PASS_SHIFT = 0x14, + GFXS1_STENCIL_BACK_FAIL_SHIFT = 0x17, + GFXS1_STENCIL_BACK_ZFAIL_SHIFT = 0x1A, + GFXS1_STENCIL_BACK_FUNC_SHIFT = 0x1D, + GFXS1_STENCIL_BACK_MASK = 0xFFF00000, + GFXS1_STENCILFUNC_FRONTBACK_MASK = 0xE00E0000, + GFXS1_STENCILOP_FRONTBACK_MASK = 0x1FF1FF00, + }; + struct GfxStateBits { unsigned int loadBits[2]; @@ -555,13 +658,38 @@ namespace IW3 water_t* water; }; + 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 MaterialTextureDef { unsigned int nameHash; char nameStart; char nameEnd; - char samplerState; - char semantic; + unsigned char samplerState; // SamplerStateBits_e + unsigned char semantic; // TextureSemantic MaterialTextureDefInfo u; }; @@ -584,13 +712,48 @@ namespace IW3 gcc_align(8) uint64_t packed; }; + enum materialSurfType_t + { + SURF_TYPE_DEFAULT, + SURF_TYPE_BARK, + SURF_TYPE_BRICK, + SURF_TYPE_CARPET, + SURF_TYPE_CLOTH, + SURF_TYPE_CONCRETE, + SURF_TYPE_DIRT, + SURF_TYPE_FLESH, + SURF_TYPE_FOLIAGE, + SURF_TYPE_GLASS, + SURF_TYPE_GRASS, + SURF_TYPE_GRAVEL, + SURF_TYPE_ICE, + SURF_TYPE_METAL, + SURF_TYPE_MUD, + SURF_TYPE_PAPER, + SURF_TYPE_PLASTER, + SURF_TYPE_ROCK, + SURF_TYPE_SAND, + SURF_TYPE_SNOW, + SURF_TYPE_WATER, + SURF_TYPE_WOOD, + SURF_TYPE_ASPHALT, + SURF_TYPE_CERAMIC, + SURF_TYPE_PLASTIC, + SURF_TYPE_RUBBER, + SURF_TYPE_CUSHION, + SURF_TYPE_FRUIT, + SURF_TYPE_PAINTED_METAL, + + SURF_TYPE_NUM + }; + struct MaterialInfo { const char* name; - char gameFlags; - char sortKey; - char textureAtlasRowCount; - char textureAtlasColumnCount; + unsigned char gameFlags; + unsigned char sortKey; + unsigned char textureAtlasRowCount; + unsigned char textureAtlasColumnCount; GfxDrawSurf drawSurf; unsigned int surfaceTypeBits; uint16_t hashIndex; @@ -604,7 +767,7 @@ namespace IW3 unsigned char constantCount; unsigned char stateBitsCount; unsigned char stateFlags; - char cameraRegion; + unsigned char cameraRegion; MaterialTechniqueSet* techniqueSet; MaterialTextureDef* textureTable; MaterialConstantDef* constantTable; @@ -649,10 +812,46 @@ namespace IW3 MaterialArgumentDef u; }; + enum MaterialStreamStreamSource_e + { + STREAM_SRC_POSITION = 0x0, + STREAM_SRC_COLOR = 0x1, + STREAM_SRC_TEXCOORD_0 = 0x2, + STREAM_SRC_NORMAL = 0x3, + STREAM_SRC_TANGENT = 0x4, + + STREAM_SRC_OPTIONAL_BEGIN = 0x5, + STREAM_SRC_PRE_OPTIONAL_BEGIN = 0x4, + + STREAM_SRC_TEXCOORD_1 = 0x5, + STREAM_SRC_TEXCOORD_2 = 0x6, + STREAM_SRC_NORMAL_TRANSFORM_0 = 0x7, + STREAM_SRC_NORMAL_TRANSFORM_1 = 0x8, + + STREAM_SRC_COUNT + }; + + enum MaterialStreamDestination_e + { + STREAM_DST_POSITION = 0x0, + STREAM_DST_NORMAL = 0x1, + STREAM_DST_COLOR_0 = 0x2, + STREAM_DST_COLOR_1 = 0x3, + STREAM_DST_TEXCOORD_0 = 0x4, + STREAM_DST_TEXCOORD_1 = 0x5, + STREAM_DST_TEXCOORD_2 = 0x6, + STREAM_DST_TEXCOORD_3 = 0x7, + STREAM_DST_TEXCOORD_4 = 0x8, + STREAM_DST_TEXCOORD_5 = 0x9, + STREAM_DST_TEXCOORD_6 = 0xA, + STREAM_DST_TEXCOORD_7 = 0xB, + STREAM_DST_COUNT + }; + struct MaterialStreamRouting { - char source; - char dest; + unsigned char source; + unsigned char dest; }; struct MaterialVertexStreamRouting @@ -727,6 +926,53 @@ namespace IW3 MaterialPass passArray[1]; }; + enum MaterialTechniqueType + { + TECHNIQUE_DEPTH_PREPASS = 0x0, + TECHNIQUE_BUILD_FLOAT_Z = 0x1, + TECHNIQUE_BUILD_SHADOWMAP_DEPTH = 0x2, + TECHNIQUE_BUILD_SHADOWMAP_COLOR = 0x3, + TECHNIQUE_UNLIT = 0x4, + TECHNIQUE_EMISSIVE = 0x5, + TECHNIQUE_EMISSIVE_SHADOW = 0x6, + + TECHNIQUE_LIT_BEGIN = 0x7, + + TECHNIQUE_LIT = 0x7, + TECHNIQUE_LIT_SUN = 0x8, + TECHNIQUE_LIT_SUN_SHADOW = 0x9, + TECHNIQUE_LIT_SPOT = 0xA, + TECHNIQUE_LIT_SPOT_SHADOW = 0xB, + TECHNIQUE_LIT_OMNI = 0xC, + TECHNIQUE_LIT_OMNI_SHADOW = 0xD, + TECHNIQUE_LIT_INSTANCED = 0xE, + TECHNIQUE_LIT_INSTANCED_SUN = 0xF, + TECHNIQUE_LIT_INSTANCED_SUN_SHADOW = 0x10, + TECHNIQUE_LIT_INSTANCED_SPOT = 0x11, + TECHNIQUE_LIT_INSTANCED_SPOT_SHADOW = 0x12, + TECHNIQUE_LIT_INSTANCED_OMNI = 0x13, + TECHNIQUE_LIT_INSTANCED_OMNI_SHADOW = 0x14, + + TECHNIQUE_LIT_END = 0x15, + + TECHNIQUE_LIGHT_SPOT = 0x15, + TECHNIQUE_LIGHT_OMNI = 0x16, + TECHNIQUE_LIGHT_SPOT_SHADOW = 0x17, + TECHNIQUE_FAKELIGHT_NORMAL = 0x18, + TECHNIQUE_FAKELIGHT_VIEW = 0x19, + TECHNIQUE_SUNLIGHT_PREVIEW = 0x1A, + TECHNIQUE_CASE_TEXTURE = 0x1B, + TECHNIQUE_WIREFRAME_SOLID = 0x1C, + TECHNIQUE_WIREFRAME_SHADED = 0x1D, + TECHNIQUE_SHADOWCOOKIE_CASTER = 0x1E, + TECHNIQUE_SHADOWCOOKIE_RECEIVER = 0x1F, + TECHNIQUE_DEBUG_BUMPMAP = 0x20, + TECHNIQUE_DEBUG_BUMPMAP_INSTANCED = 0x21, + TECHNIQUE_COUNT = 0x22, + TECHNIQUE_TOTAL_COUNT = 0x23, + TECHNIQUE_NONE = 0x24, + }; + struct MaterialTechniqueSet { const char* name; diff --git a/src/ObjCommon/Game/IW3/MaterialConstantsIW3.h b/src/ObjCommon/Game/IW3/MaterialConstantsIW3.h new file mode 100644 index 00000000..0c22c5c4 --- /dev/null +++ b/src/ObjCommon/Game/IW3/MaterialConstantsIW3.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "Game/IW3/IW3.h" + +namespace IW3 +{ + inline const char* surfaceTypeNames[] + { + "default", + "bark", + "brick", + "carpet", + "cloth", + "concrete", + "dirt", + "flesh", + "foliage", + "glass", + "grass", + "gravel", + "ice", + "metal", + "mud", + "paper", + "plaster", + "rock", + "sand", + "snow", + "water", + "wood", + "asphalt", + "ceramic", + "plastic", + "rubber", + "cushion", + "fruit", + "paintedmetal" + }; + static_assert(std::extent_v == SURF_TYPE_NUM); +} diff --git a/src/ObjCommon/Game/IW3/TechsetConstantsIW3.h b/src/ObjCommon/Game/IW3/TechsetConstantsIW3.h new file mode 100644 index 00000000..44a7878a --- /dev/null +++ b/src/ObjCommon/Game/IW3/TechsetConstantsIW3.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include + +#include "Game/IW3/CommonIW3.h" +#include "Game/IW3/IW3.h" + +namespace IW3 +{ + inline const char* techniqueTypeNames[] + { + "depth prepass", + "build floatz", + "build shadowmap depth", + "build shadowmap color", + "unlit", + "emissive", + "emissive shadow", + "lit", + "lit sun", + "lit sun shadow", + "lit spot", + "lit spot shadow", + "lit omni", + "lit omni shadow", + "lit instanced", + "lit instanced sun", + "lit instanced sun shadow", + "lit instanced spot", + "lit instanced spot shadow", + "lit instanced omni", + "lit instanced omni shadow", + "light spot", + "light omni", + "light spot shadow", + "fakelight normal", + "fakelight view", + "sunlight preview", + "case texture", + "solid wireframe", + "shaded wireframe", + "shadowcookie caster", + "shadowcookie receiver", + "debug bumpmap", + "debug bumpmap instanced", + }; + static_assert(std::extent_v == TECHNIQUE_COUNT); + + static const char* materialStreamDestinationNames[] + { + "position", + "normal", + "color[0]", + "color[1]", + "texcoord[0]", + "texcoord[1]", + "texcoord[2]", + "texcoord[3]", + "texcoord[4]", + "texcoord[5]", + "texcoord[6]", + "texcoord[7]", + }; + static_assert(std::extent_v == STREAM_DST_COUNT); + + static const char* materialStreamSourceNames[] + { + "position", + "color", + "texcoord[0]", + "normal", + "tangent", + "texcoord[1]", + "texcoord[2]", + "normalTransform[0]", + "normalTransform[1]" + }; + static_assert(std::extent_v == STREAM_SRC_COUNT); + + static constexpr std::pair KnownMaterialSource(const char* name) + { + return std::make_pair(Common::R_HashString(name, 0u), name); + } + + inline std::unordered_map knownMaterialSourceNames + { + KnownMaterialSource("colorMap"), + KnownMaterialSource("colorMap0"), + KnownMaterialSource("colorMap1"), + KnownMaterialSource("colorMap2"), + KnownMaterialSource("colorMap3"), + KnownMaterialSource("colorMap4"), + KnownMaterialSource("colorMap5"), + KnownMaterialSource("colorMap6"), + KnownMaterialSource("colorMap7"), + KnownMaterialSource("normalMap"), + KnownMaterialSource("normalMap0"), + KnownMaterialSource("normalMap1"), + KnownMaterialSource("normalMap2"), + KnownMaterialSource("normalMap3"), + KnownMaterialSource("normalMap4"), + KnownMaterialSource("normalMap5"), + KnownMaterialSource("normalMap6"), + KnownMaterialSource("normalMap7"), + KnownMaterialSource("specularMap"), + KnownMaterialSource("specularMap0"), + KnownMaterialSource("specularMap1"), + KnownMaterialSource("specularMap2"), + KnownMaterialSource("specularMap3"), + KnownMaterialSource("specularMap4"), + KnownMaterialSource("specularMap5"), + KnownMaterialSource("specularMap6"), + KnownMaterialSource("specularMap7"), + KnownMaterialSource("detailMap"), + KnownMaterialSource("detailMap0"), + KnownMaterialSource("detailMap1"), + KnownMaterialSource("detailMap2"), + KnownMaterialSource("detailMap3"), + KnownMaterialSource("detailMap4"), + KnownMaterialSource("detailMap5"), + KnownMaterialSource("detailMap6"), + KnownMaterialSource("detailMap7"), + KnownMaterialSource("attenuationMap"), + KnownMaterialSource("attenuationMap0"), + KnownMaterialSource("attenuationMap1"), + KnownMaterialSource("attenuationMap2"), + KnownMaterialSource("attenuationMap3"), + KnownMaterialSource("attenuationMap4"), + KnownMaterialSource("attenuationMap5"), + KnownMaterialSource("attenuationMap6"), + KnownMaterialSource("attenuationMap7"), + KnownMaterialSource("distortionScale"), + KnownMaterialSource("eyeOffsetParms"), + KnownMaterialSource("falloffBeginColor"), + KnownMaterialSource("falloffEndColor"), + }; +} diff --git a/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp new file mode 100644 index 00000000..801f7c3f --- /dev/null +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.cpp @@ -0,0 +1,460 @@ +#include "AssetDumperMaterial.h" + +#include +#include +#include + +#include "Game/IW3/MaterialConstantsIW3.h" +#include "Game/IW3/TechsetConstantsIW3.h" + +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()); + } +} + +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", { + {"gameFlags", material->info.gameFlags}, // TODO: Find out what gameflags mean + {"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))}, + {"stateFlags", material->stateFlags}, + {"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/AssetDumpers/AssetDumperMaterial.h b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.h new file mode 100644 index 00000000..94f9e54d --- /dev/null +++ b/src/ObjWriting/Game/IW3/AssetDumpers/AssetDumperMaterial.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW3/IW3.h" + +namespace IW3 +{ + class AssetDumperMaterial final : public AbstractAssetDumper + { + protected: + bool ShouldDump(XAssetInfo* asset) override; + void DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) override; + }; +} diff --git a/src/ObjWriting/Game/IW3/ZoneDumperIW3.cpp b/src/ObjWriting/Game/IW3/ZoneDumperIW3.cpp index 86ce234b..a01e839e 100644 --- a/src/ObjWriting/Game/IW3/ZoneDumperIW3.cpp +++ b/src/ObjWriting/Game/IW3/ZoneDumperIW3.cpp @@ -8,6 +8,7 @@ #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" @@ -34,7 +35,7 @@ bool ZoneDumper::DumpZone(AssetDumpingContext& context) const // DUMP_ASSET_POOL(AssetDumperPhysPreset, m_phys_preset, ASSET_TYPE_PHYSPRESET) // DUMP_ASSET_POOL(AssetDumperXAnimParts, m_xanim_parts, ASSET_TYPE_XANIMPARTS) 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(AssetDumperMaterialTechniqueSet, m_technique_set, ASSET_TYPE_TECHNIQUE_SET) DUMP_ASSET_POOL(AssetDumperGfxImage, m_image, ASSET_TYPE_IMAGE) // DUMP_ASSET_POOL(AssetDumpersnd_alias_list_t, m_sound, ASSET_TYPE_SOUND)