#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()); } json BuildGameFlagsJson(const unsigned char gameFlags) { std::vector values; for (auto i = 0u; i < (sizeof(gameFlags) * 8u); i++) { if (gameFlags & (1 << i)) { std::ostringstream ss; ss << "0x" << std::hex << (1 << i); values.emplace_back(ss.str()); } } return json(values); } } 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", BuildGameFlagsJson(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; }