#include #include #include #include #include #include #include namespace XCEngine { namespace Resources { namespace { std::string ToStdString(const Containers::Array& data) { return std::string(reinterpret_cast(data.Data()), data.Size()); } size_t SkipWhitespace(const std::string& text, size_t pos) { while (pos < text.size() && std::isspace(static_cast(text[pos])) != 0) { ++pos; } return pos; } bool HasKey(const std::string& json, const char* key) { const std::string token = std::string("\"") + key + "\""; return json.find(token) != std::string::npos; } bool FindValueStart(const std::string& json, const char* key, size_t& valuePos) { const std::string token = std::string("\"") + key + "\""; const size_t keyPos = json.find(token); if (keyPos == std::string::npos) { return false; } const size_t colonPos = json.find(':', keyPos + token.length()); if (colonPos == std::string::npos) { return false; } valuePos = SkipWhitespace(json, colonPos + 1); return valuePos < json.size(); } bool ParseQuotedString(const std::string& text, size_t quotePos, Containers::String& outValue, size_t* nextPos = nullptr) { if (quotePos >= text.size() || text[quotePos] != '"') { return false; } std::string parsed; ++quotePos; while (quotePos < text.size()) { const char ch = text[quotePos]; if (ch == '\\') { if (quotePos + 1 >= text.size()) { return false; } parsed.push_back(text[quotePos + 1]); quotePos += 2; continue; } if (ch == '"') { outValue = parsed.c_str(); if (nextPos != nullptr) { *nextPos = quotePos + 1; } return true; } parsed.push_back(ch); ++quotePos; } return false; } bool TryParseStringValue(const std::string& json, const char* key, Containers::String& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos)) { return false; } return ParseQuotedString(json, valuePos, outValue); } bool TryParseIntValue(const std::string& json, const char* key, Core::int32& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos)) { return false; } if (valuePos >= json.size() || (json[valuePos] != '-' && std::isdigit(static_cast(json[valuePos])) == 0)) { return false; } char* endPtr = nullptr; const long parsed = std::strtol(json.c_str() + valuePos, &endPtr, 10); if (endPtr == json.c_str() + valuePos) { return false; } outValue = static_cast(parsed); return true; } bool TryParseBoolValue(const std::string& json, const char* key, bool& outValue) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos)) { return false; } if (json.compare(valuePos, 4, "true") == 0) { outValue = true; return true; } if (json.compare(valuePos, 5, "false") == 0) { outValue = false; return true; } return false; } bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) { size_t valuePos = 0; if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '{') { return false; } bool inString = false; bool escaped = false; int depth = 0; for (size_t pos = valuePos; pos < json.size(); ++pos) { const char ch = json[pos]; if (escaped) { escaped = false; continue; } if (ch == '\\') { escaped = true; continue; } if (ch == '"') { inString = !inString; continue; } if (inString) { continue; } if (ch == '{') { ++depth; } else if (ch == '}') { --depth; if (depth == 0) { outObject = json.substr(valuePos, pos - valuePos + 1); return true; } } } return false; } bool TryParseRenderQueueName(const Containers::String& queueName, Core::int32& outQueue) { const Containers::String normalized = queueName.ToLower(); if (normalized == "background") { outQueue = static_cast(MaterialRenderQueue::Background); return true; } if (normalized == "geometry" || normalized == "opaque") { outQueue = static_cast(MaterialRenderQueue::Geometry); return true; } if (normalized == "alphatest" || normalized == "alpha_test") { outQueue = static_cast(MaterialRenderQueue::AlphaTest); return true; } if (normalized == "transparent") { outQueue = static_cast(MaterialRenderQueue::Transparent); return true; } if (normalized == "overlay") { outQueue = static_cast(MaterialRenderQueue::Overlay); return true; } return false; } bool TryParseTagMap(const std::string& objectText, Material* material) { if (material == nullptr || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') { return false; } size_t pos = 1; while (pos < objectText.size()) { pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size()) { return false; } if (objectText[pos] == '}') { return true; } Containers::String key; if (!ParseQuotedString(objectText, pos, key, &pos)) { return false; } pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size() || objectText[pos] != ':') { return false; } pos = SkipWhitespace(objectText, pos + 1); Containers::String value; if (!ParseQuotedString(objectText, pos, value, &pos)) { return false; } material->SetTag(key, value); pos = SkipWhitespace(objectText, pos); if (pos >= objectText.size()) { return false; } if (objectText[pos] == ',') { ++pos; continue; } if (objectText[pos] == '}') { return true; } return false; } return false; } bool TryParseCullMode(const Containers::String& value, MaterialCullMode& outMode) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "none" || normalized == "off") { outMode = MaterialCullMode::None; return true; } if (normalized == "front") { outMode = MaterialCullMode::Front; return true; } if (normalized == "back") { outMode = MaterialCullMode::Back; return true; } return false; } bool TryParseComparisonFunc(const Containers::String& value, MaterialComparisonFunc& outFunc) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "never") { outFunc = MaterialComparisonFunc::Never; return true; } if (normalized == "less") { outFunc = MaterialComparisonFunc::Less; return true; } if (normalized == "equal") { outFunc = MaterialComparisonFunc::Equal; return true; } if (normalized == "lessequal" || normalized == "less_equal" || normalized == "lequal") { outFunc = MaterialComparisonFunc::LessEqual; return true; } if (normalized == "greater") { outFunc = MaterialComparisonFunc::Greater; return true; } if (normalized == "notequal" || normalized == "not_equal") { outFunc = MaterialComparisonFunc::NotEqual; return true; } if (normalized == "greaterequal" || normalized == "greater_equal" || normalized == "gequal") { outFunc = MaterialComparisonFunc::GreaterEqual; return true; } if (normalized == "always") { outFunc = MaterialComparisonFunc::Always; return true; } return false; } bool TryParseBlendFactor(const Containers::String& value, MaterialBlendFactor& outFactor) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "zero") { outFactor = MaterialBlendFactor::Zero; return true; } if (normalized == "one") { outFactor = MaterialBlendFactor::One; return true; } if (normalized == "srccolor" || normalized == "src_color") { outFactor = MaterialBlendFactor::SrcColor; return true; } if (normalized == "invsrccolor" || normalized == "inv_src_color" || normalized == "one_minus_src_color") { outFactor = MaterialBlendFactor::InvSrcColor; return true; } if (normalized == "srcalpha" || normalized == "src_alpha") { outFactor = MaterialBlendFactor::SrcAlpha; return true; } if (normalized == "invsrcalpha" || normalized == "inv_src_alpha" || normalized == "one_minus_src_alpha") { outFactor = MaterialBlendFactor::InvSrcAlpha; return true; } if (normalized == "dstalpha" || normalized == "dst_alpha") { outFactor = MaterialBlendFactor::DstAlpha; return true; } if (normalized == "invdstalpha" || normalized == "inv_dst_alpha" || normalized == "one_minus_dst_alpha") { outFactor = MaterialBlendFactor::InvDstAlpha; return true; } if (normalized == "dstcolor" || normalized == "dst_color") { outFactor = MaterialBlendFactor::DstColor; return true; } if (normalized == "invdstcolor" || normalized == "inv_dst_color" || normalized == "one_minus_dst_color") { outFactor = MaterialBlendFactor::InvDstColor; return true; } if (normalized == "srcalphasat" || normalized == "src_alpha_sat") { outFactor = MaterialBlendFactor::SrcAlphaSat; return true; } if (normalized == "blendfactor" || normalized == "blend_factor") { outFactor = MaterialBlendFactor::BlendFactor; return true; } if (normalized == "invblendfactor" || normalized == "inv_blend_factor" || normalized == "one_minus_blend_factor") { outFactor = MaterialBlendFactor::InvBlendFactor; return true; } if (normalized == "src1color" || normalized == "src1_color") { outFactor = MaterialBlendFactor::Src1Color; return true; } if (normalized == "invsrc1color" || normalized == "inv_src1_color" || normalized == "one_minus_src1_color") { outFactor = MaterialBlendFactor::InvSrc1Color; return true; } if (normalized == "src1alpha" || normalized == "src1_alpha") { outFactor = MaterialBlendFactor::Src1Alpha; return true; } if (normalized == "invsrc1alpha" || normalized == "inv_src1_alpha" || normalized == "one_minus_src1_alpha") { outFactor = MaterialBlendFactor::InvSrc1Alpha; return true; } return false; } bool TryParseBlendOp(const Containers::String& value, MaterialBlendOp& outOp) { const Containers::String normalized = value.Trim().ToLower(); if (normalized == "add") { outOp = MaterialBlendOp::Add; return true; } if (normalized == "subtract") { outOp = MaterialBlendOp::Subtract; return true; } if (normalized == "reversesubtract" || normalized == "reverse_subtract") { outOp = MaterialBlendOp::ReverseSubtract; return true; } if (normalized == "min") { outOp = MaterialBlendOp::Min; return true; } if (normalized == "max") { outOp = MaterialBlendOp::Max; return true; } return false; } bool TryParseRenderStateObject(const std::string& objectText, Material* material) { if (material == nullptr || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') { return false; } MaterialRenderState renderState = material->GetRenderState(); Containers::String enumValue; if (HasKey(objectText, "cull")) { if (!TryParseStringValue(objectText, "cull", enumValue) || !TryParseCullMode(enumValue, renderState.cullMode)) { return false; } } bool boolValue = false; if (HasKey(objectText, "depthTest")) { if (!TryParseBoolValue(objectText, "depthTest", boolValue)) { return false; } renderState.depthTestEnable = boolValue; } if (HasKey(objectText, "depthWrite")) { if (!TryParseBoolValue(objectText, "depthWrite", boolValue)) { return false; } renderState.depthWriteEnable = boolValue; } if (HasKey(objectText, "zWrite")) { if (!TryParseBoolValue(objectText, "zWrite", boolValue)) { return false; } renderState.depthWriteEnable = boolValue; } if (HasKey(objectText, "blend")) { if (!TryParseBoolValue(objectText, "blend", boolValue)) { return false; } renderState.blendEnable = boolValue; } if (HasKey(objectText, "blendEnable")) { if (!TryParseBoolValue(objectText, "blendEnable", boolValue)) { return false; } renderState.blendEnable = boolValue; } if (HasKey(objectText, "depthFunc")) { if (!TryParseStringValue(objectText, "depthFunc", enumValue) || !TryParseComparisonFunc(enumValue, renderState.depthFunc)) { return false; } } if (HasKey(objectText, "zTest")) { if (!TryParseStringValue(objectText, "zTest", enumValue) || !TryParseComparisonFunc(enumValue, renderState.depthFunc)) { return false; } } if (HasKey(objectText, "colorWriteMask")) { Core::int32 colorWriteMask = 0; if (!TryParseIntValue(objectText, "colorWriteMask", colorWriteMask) || colorWriteMask < 0 || colorWriteMask > 0xF) { return false; } renderState.colorWriteMask = static_cast(colorWriteMask); } if (HasKey(objectText, "srcBlend")) { if (!TryParseStringValue(objectText, "srcBlend", enumValue) || !TryParseBlendFactor(enumValue, renderState.srcBlend)) { return false; } } if (HasKey(objectText, "dstBlend")) { if (!TryParseStringValue(objectText, "dstBlend", enumValue) || !TryParseBlendFactor(enumValue, renderState.dstBlend)) { return false; } } if (HasKey(objectText, "srcBlendAlpha")) { if (!TryParseStringValue(objectText, "srcBlendAlpha", enumValue) || !TryParseBlendFactor(enumValue, renderState.srcBlendAlpha)) { return false; } } if (HasKey(objectText, "dstBlendAlpha")) { if (!TryParseStringValue(objectText, "dstBlendAlpha", enumValue) || !TryParseBlendFactor(enumValue, renderState.dstBlendAlpha)) { return false; } } if (HasKey(objectText, "blendOp")) { if (!TryParseStringValue(objectText, "blendOp", enumValue) || !TryParseBlendOp(enumValue, renderState.blendOp)) { return false; } } if (HasKey(objectText, "blendOpAlpha")) { if (!TryParseStringValue(objectText, "blendOpAlpha", enumValue) || !TryParseBlendOp(enumValue, renderState.blendOpAlpha)) { return false; } } material->SetRenderState(renderState); return true; } ResourceHandle LoadShaderHandle(const Containers::String& shaderPath) { ResourceHandle shader = ResourceManager::Get().Load(shaderPath); if (shader.IsValid()) { return shader; } ShaderLoader shaderLoader; LoadResult shaderResult = shaderLoader.Load(shaderPath); if (!shaderResult || shaderResult.resource == nullptr) { return ResourceHandle(); } return ResourceHandle(static_cast(shaderResult.resource)); } } // namespace MaterialLoader::MaterialLoader() = default; MaterialLoader::~MaterialLoader() = default; Containers::Array MaterialLoader::GetSupportedExtensions() const { Containers::Array extensions; extensions.PushBack("mat"); extensions.PushBack("material"); extensions.PushBack("json"); return extensions; } bool MaterialLoader::CanLoad(const Containers::String& path) const { Containers::String ext = GetExtension(path); return ext == "mat" || ext == "material" || ext == "json"; } LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) { (void)settings; Containers::Array data = ReadFileData(path); if (data.Empty()) { return LoadResult("Failed to read material file: " + path); } Material* material = new Material(); material->m_path = path; material->m_name = path; material->m_guid = ResourceGUID::Generate(path); if (!ParseMaterialData(data, material)) { delete material; return LoadResult("Failed to parse material file: " + path); } material->m_isValid = true; material->RecalculateMemorySize(); return LoadResult(material); } ImportSettings* MaterialLoader::GetDefaultSettings() const { return nullptr; } bool MaterialLoader::ParseMaterialData(const Containers::Array& data, Material* material) { if (material == nullptr) { return false; } const std::string jsonText = ToStdString(data); Containers::String shaderPath; if (HasKey(jsonText, "shader")) { if (!TryParseStringValue(jsonText, "shader", shaderPath)) { return false; } const ResourceHandle shaderHandle = LoadShaderHandle(shaderPath); if (shaderHandle.IsValid()) { material->SetShader(shaderHandle); } } Containers::String shaderPass; if (HasKey(jsonText, "shaderPass")) { if (!TryParseStringValue(jsonText, "shaderPass", shaderPass)) { return false; } material->SetShaderPass(shaderPass); } else if (HasKey(jsonText, "pass")) { if (!TryParseStringValue(jsonText, "pass", shaderPass)) { return false; } material->SetShaderPass(shaderPass); } if (HasKey(jsonText, "renderQueue")) { Core::int32 renderQueue = 0; if (TryParseIntValue(jsonText, "renderQueue", renderQueue)) { material->SetRenderQueue(renderQueue); } else { Containers::String renderQueueName; if (!TryParseStringValue(jsonText, "renderQueue", renderQueueName) || !TryParseRenderQueueName(renderQueueName, renderQueue)) { return false; } material->SetRenderQueue(renderQueue); } } if (HasKey(jsonText, "tags")) { std::string tagObject; if (!TryExtractObject(jsonText, "tags", tagObject) || !TryParseTagMap(tagObject, material)) { return false; } } if (HasKey(jsonText, "renderState")) { std::string renderStateObject; if (!TryExtractObject(jsonText, "renderState", renderStateObject) || !TryParseRenderStateObject(renderStateObject, material)) { return false; } } return true; } } // namespace Resources } // namespace XCEngine