#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 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; } 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; } } return true; } } // namespace Resources } // namespace XCEngine