#include #include #include #include #include #include #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()); } Containers::Array ReadMaterialArtifactFileData(const Containers::String& path) { Containers::Array data; auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) { std::ifstream file(filePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { opened = false; return; } opened = true; const std::streamsize size = file.tellg(); if (size <= 0) { return; } file.seekg(0, std::ios::beg); data.Resize(static_cast(size)); if (!file.read(reinterpret_cast(data.Data()), size)) { data.Clear(); } }; bool opened = false; const std::filesystem::path inputPath(path.CStr()); tryRead(inputPath, opened); if (opened || path.Empty() || inputPath.is_absolute()) { return data; } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (resourceRoot.Empty()) { return data; } tryRead(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened); return data; } Containers::String NormalizePathString(const std::filesystem::path& path) { return Containers::String(path.lexically_normal().generic_string().c_str()); } bool IsProjectRelativePath(const std::filesystem::path& path) { const std::string generic = path.generic_string(); return !generic.empty() && generic != "." && generic != ".." && generic.rfind("../", 0) != 0; } Containers::String ToProjectRelativeIfPossible(const std::filesystem::path& path) { const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); const std::filesystem::path normalizedPath = path.lexically_normal(); if (!resourceRoot.Empty() && normalizedPath.is_absolute()) { std::error_code ec; const std::filesystem::path relativePath = std::filesystem::relative(normalizedPath, std::filesystem::path(resourceRoot.CStr()), ec); if (!ec && IsProjectRelativePath(relativePath)) { return NormalizePathString(relativePath); } } return NormalizePathString(normalizedPath); } Containers::String ResolveSourceDependencyPath(const Containers::String& dependencyPath, const Containers::String& sourcePath) { if (dependencyPath.Empty()) { return dependencyPath; } std::filesystem::path dependencyFsPath(dependencyPath.CStr()); if (dependencyFsPath.is_absolute()) { return NormalizePathString(dependencyFsPath); } const std::filesystem::path sourceFsPath(sourcePath.CStr()); if (sourceFsPath.is_absolute()) { return ToProjectRelativeIfPossible(sourceFsPath.parent_path() / dependencyFsPath); } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (!resourceRoot.Empty()) { return ToProjectRelativeIfPossible( std::filesystem::path(resourceRoot.CStr()) / sourceFsPath.parent_path() / dependencyFsPath); } return NormalizePathString(sourceFsPath.parent_path() / dependencyFsPath); } Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath, const Containers::String& ownerArtifactPath) { if (dependencyPath.Empty()) { return dependencyPath; } std::filesystem::path dependencyFsPath(dependencyPath.CStr()); if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) { return NormalizePathString(dependencyFsPath); } if (std::filesystem::exists(dependencyFsPath)) { return NormalizePathString(dependencyFsPath); } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (!resourceRoot.Empty()) { const std::filesystem::path projectRelativeCandidate = std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath; if (std::filesystem::exists(projectRelativeCandidate)) { return NormalizePathString(projectRelativeCandidate); } } const std::filesystem::path ownerArtifactFsPath(ownerArtifactPath.CStr()); if (!ownerArtifactFsPath.is_absolute()) { return dependencyPath; } const std::filesystem::path ownerRelativeCandidate = ownerArtifactFsPath.parent_path() / dependencyFsPath; if (std::filesystem::exists(ownerRelativeCandidate)) { return NormalizePathString(ownerRelativeCandidate); } std::filesystem::path current = ownerArtifactFsPath.parent_path(); while (!current.empty()) { if (current.filename() == "Library") { const std::filesystem::path projectRoot = current.parent_path(); if (!projectRoot.empty()) { const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath; if (std::filesystem::exists(projectRelativeCandidate)) { return NormalizePathString(projectRelativeCandidate); } } break; } const std::filesystem::path parent = current.parent_path(); if (parent == current) { break; } current = parent; } return dependencyPath; } 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 TryParseStringMapObject( const std::string& objectText, const std::function& onEntry) { if (!onEntry || 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; } onEntry(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 TryApplyTexturePath(Material* material, const Containers::String& textureName, const Containers::String& texturePath) { if (material == nullptr || textureName.Empty() || texturePath.Empty()) { return false; } material->SetTexturePath( textureName, ResolveSourceDependencyPath(texturePath, material->GetPath())); return true; } bool TryParseMaterialTextureBindings(const std::string& jsonText, Material* material) { if (material == nullptr) { return false; } static const char* const kKnownTextureKeys[] = { "baseColorTexture", "_BaseColorTexture", "_MainTex", "normalTexture", "_BumpMap", "specularTexture", "emissiveTexture", "metallicTexture", "roughnessTexture", "occlusionTexture", "opacityTexture" }; for (const char* key : kKnownTextureKeys) { if (!HasKey(jsonText, key)) { continue; } Containers::String texturePath; if (!TryParseStringValue(jsonText, key, texturePath)) { return false; } TryApplyTexturePath(material, Containers::String(key), texturePath); } if (HasKey(jsonText, "textures")) { std::string texturesObject; if (!TryExtractObject(jsonText, "textures", texturesObject)) { return false; } if (!TryParseStringMapObject( texturesObject, [material](const Containers::String& name, const Containers::String& value) { TryApplyTexturePath(material, name, value); })) { return false; } } return true; } 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)); } bool MaterialFileExists(const Containers::String& path) { const std::filesystem::path inputPath(path.CStr()); if (std::filesystem::exists(inputPath)) { return true; } if (inputPath.is_absolute()) { return false; } const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot(); if (resourceRoot.Empty()) { return false; } return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath); } ResourceHandle LoadShaderHandle(const Containers::String& shaderPath); template bool ReadMaterialArtifactValue(const Containers::Array& data, size_t& offset, T& outValue) { if (offset + sizeof(T) > data.Size()) { return false; } std::memcpy(&outValue, data.Data() + offset, sizeof(T)); offset += sizeof(T); return true; } bool ReadMaterialArtifactString(const Containers::Array& data, size_t& offset, Containers::String& outValue) { Core::uint32 length = 0; if (!ReadMaterialArtifactValue(data, offset, length)) { return false; } if (length == 0) { outValue.Clear(); return true; } if (offset + length > data.Size()) { return false; } outValue = Containers::String( std::string(reinterpret_cast(data.Data() + offset), length).c_str()); offset += length; return true; } void ApplyMaterialProperty(Material& material, const MaterialProperty& property) { switch (property.type) { case MaterialPropertyType::Float: material.SetFloat(property.name, property.value.floatValue[0]); break; case MaterialPropertyType::Float2: material.SetFloat2( property.name, Math::Vector2(property.value.floatValue[0], property.value.floatValue[1])); break; case MaterialPropertyType::Float3: material.SetFloat3( property.name, Math::Vector3( property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2])); break; case MaterialPropertyType::Float4: material.SetFloat4( property.name, Math::Vector4( property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2], property.value.floatValue[3])); break; case MaterialPropertyType::Int: material.SetInt(property.name, property.value.intValue[0]); break; case MaterialPropertyType::Bool: material.SetBool(property.name, property.value.boolValue); break; default: break; } } LoadResult LoadMaterialArtifact(const Containers::String& path) { const Containers::Array data = ReadMaterialArtifactFileData(path); if (data.Empty()) { return LoadResult("Failed to read material artifact: " + path); } size_t offset = 0; MaterialArtifactFileHeader fileHeader; if (!ReadMaterialArtifactValue(data, offset, fileHeader)) { return LoadResult("Failed to parse material artifact header: " + path); } const std::string magic(fileHeader.magic, fileHeader.magic + 7); if (magic != "XCMAT01") { return LoadResult("Invalid material artifact magic: " + path); } auto material = std::make_unique(); material->m_path = path; material->m_name = path; material->m_guid = ResourceGUID::Generate(path); Containers::String materialName; Containers::String materialSourcePath; Containers::String shaderPath; Containers::String shaderPass; if (!ReadMaterialArtifactString(data, offset, materialName) || !ReadMaterialArtifactString(data, offset, materialSourcePath) || !ReadMaterialArtifactString(data, offset, shaderPath) || !ReadMaterialArtifactString(data, offset, shaderPass)) { return LoadResult("Failed to parse material artifact strings: " + path); } material->m_name = materialName.Empty() ? path : materialName; if (!materialSourcePath.Empty()) { material->m_path = materialSourcePath; material->m_guid = ResourceGUID::Generate(materialSourcePath); } if (!shaderPath.Empty()) { const ResourceHandle shaderHandle = LoadShaderHandle(shaderPath); if (shaderHandle.IsValid()) { material->SetShader(shaderHandle); } } if (!shaderPass.Empty()) { material->SetShaderPass(shaderPass); } MaterialArtifactHeader header; if (!ReadMaterialArtifactValue(data, offset, header)) { return LoadResult("Failed to parse material artifact body: " + path); } material->SetRenderQueue(header.renderQueue); material->SetRenderState(header.renderState); for (Core::uint32 tagIndex = 0; tagIndex < header.tagCount; ++tagIndex) { Containers::String tagName; Containers::String tagValue; if (!ReadMaterialArtifactString(data, offset, tagName) || !ReadMaterialArtifactString(data, offset, tagValue)) { return LoadResult("Failed to read material artifact tags: " + path); } material->SetTag(tagName, tagValue); } for (Core::uint32 propertyIndex = 0; propertyIndex < header.propertyCount; ++propertyIndex) { Containers::String propertyName; MaterialPropertyArtifact propertyArtifact; if (!ReadMaterialArtifactString(data, offset, propertyName) || !ReadMaterialArtifactValue(data, offset, propertyArtifact)) { return LoadResult("Failed to read material artifact properties: " + path); } MaterialProperty property; property.name = propertyName; property.type = static_cast(propertyArtifact.propertyType); property.value = propertyArtifact.value; ApplyMaterialProperty(*material, property); } for (Core::uint32 bindingIndex = 0; bindingIndex < header.textureBindingCount; ++bindingIndex) { Containers::String bindingName; Containers::String texturePath; if (!ReadMaterialArtifactString(data, offset, bindingName) || !ReadMaterialArtifactString(data, offset, texturePath)) { return LoadResult("Failed to read material artifact texture bindings: " + path); } if (!texturePath.Empty()) { material->SetTexturePath(bindingName, ResolveArtifactDependencyPath(texturePath, path)); } } material->m_isValid = true; material->RecalculateMemorySize(); return LoadResult(material.release()); } } // namespace MaterialLoader::MaterialLoader() = default; MaterialLoader::~MaterialLoader() = default; Containers::Array MaterialLoader::GetSupportedExtensions() const { Containers::Array extensions; extensions.PushBack("mat"); extensions.PushBack("material"); extensions.PushBack("json"); extensions.PushBack("xcmat"); return extensions; } bool MaterialLoader::CanLoad(const Containers::String& path) const { if (IsBuiltinMaterialPath(path)) { return true; } Containers::String ext = GetExtension(path).ToLower(); return ext == "mat" || ext == "material" || ext == "json" || ext == "xcmat"; } LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) { (void)settings; if (IsBuiltinMaterialPath(path)) { return CreateBuiltinMaterialResource(path); } const Containers::String ext = GetExtension(path).ToLower(); if (ext == "xcmat") { return LoadMaterialArtifact(path); } Containers::Array data = ReadFileData(path); Material* material = new Material(); material->m_path = path; material->m_name = path; material->m_guid = ResourceGUID::Generate(path); if (data.Empty() && !MaterialFileExists(path)) { delete material; return LoadResult("Failed to read material file: " + path); } if (!data.Empty() && !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; } } if (!TryParseMaterialTextureBindings(jsonText, material)) { return false; } return true; } } // namespace Resources } // namespace XCEngine