#include "MaterialInspectorMaterialStateIO.h" #include "Utils/ProjectFileUtils.h" #include #include #include #include #include #include #include #include #include namespace XCEngine { namespace Editor { namespace { template std::string BufferToString(const std::array& buffer) { return std::string(buffer.data()); } std::string TrimCopy(const std::string& value) { return ProjectFileUtils::Trim(value); } size_t SkipWhitespace(const std::string& text, size_t position) { while (position < text.size() && std::isspace(static_cast(text[position])) != 0) { ++position; } return position; } bool ParseQuotedString( const std::string& text, size_t quotePosition, std::string& outValue, size_t* nextPosition = nullptr) { if (quotePosition >= text.size() || text[quotePosition] != '"') { return false; } std::string parsed; ++quotePosition; while (quotePosition < text.size()) { const char ch = text[quotePosition]; if (ch == '\\') { if (quotePosition + 1 >= text.size()) { return false; } parsed.push_back(text[quotePosition + 1]); quotePosition += 2; continue; } if (ch == '"') { outValue = parsed; if (nextPosition != nullptr) { *nextPosition = quotePosition + 1; } return true; } parsed.push_back(ch); ++quotePosition; } return false; } bool FindValueStart(const std::string& text, const char* key, size_t& outValuePosition) { const std::string token = std::string("\"") + key + "\""; const size_t keyPosition = text.find(token); if (keyPosition == std::string::npos) { return false; } const size_t colonPosition = text.find(':', keyPosition + token.length()); if (colonPosition == std::string::npos) { return false; } outValuePosition = SkipWhitespace(text, colonPosition + 1); return outValuePosition < text.size(); } bool TryExtractDelimitedValue( const std::string& text, const char* key, char openChar, char closeChar, std::string& outValue) { size_t valuePosition = 0; if (!FindValueStart(text, key, valuePosition) || text[valuePosition] != openChar) { return false; } int depth = 0; bool inString = false; size_t current = valuePosition; while (current < text.size()) { const char ch = text[current]; if (ch == '"' && (current == 0 || text[current - 1] != '\\')) { inString = !inString; } if (!inString) { if (ch == openChar) { ++depth; } else if (ch == closeChar) { --depth; if (depth == 0) { outValue = text.substr(valuePosition, current - valuePosition + 1); return true; } } } ++current; } return false; } bool TryExtractObject(const std::string& text, const char* key, std::string& outValue) { return TryExtractDelimitedValue(text, key, '{', '}', outValue); } bool TryExtractArray(const std::string& text, const char* key, std::string& outValue) { return TryExtractDelimitedValue(text, key, '[', ']', outValue); } bool CollectObjectKeys(const std::string& objectText, std::unordered_set& outKeys) { if (objectText.empty() || objectText.front() != '{' || objectText.back() != '}') { return false; } size_t position = 1; while (position < objectText.size()) { position = SkipWhitespace(objectText, position); if (position >= objectText.size()) { return false; } if (objectText[position] == '}') { return true; } std::string key; if (!ParseQuotedString(objectText, position, key, &position)) { return false; } outKeys.insert(key); position = SkipWhitespace(objectText, position); if (position >= objectText.size() || objectText[position] != ':') { return false; } position = SkipWhitespace(objectText, position + 1); if (position >= objectText.size()) { return false; } if (objectText[position] == '"') { std::string ignoredValue; if (!ParseQuotedString(objectText, position, ignoredValue, &position)) { return false; } } else if (objectText[position] == '[') { int depth = 0; bool inString = false; do { const char ch = objectText[position]; if (ch == '"' && (position == 0 || objectText[position - 1] != '\\')) { inString = !inString; } if (!inString) { if (ch == '[') { ++depth; } else if (ch == ']') { --depth; } } ++position; } while (position < objectText.size() && depth > 0); } else { while (position < objectText.size() && objectText[position] != ',' && objectText[position] != '}') { ++position; } } position = SkipWhitespace(objectText, position); if (position < objectText.size() && objectText[position] == ',') { ++position; } } return false; } bool CollectStringArrayValues(const std::string& arrayText, std::unordered_set& outValues) { if (arrayText.empty() || arrayText.front() != '[' || arrayText.back() != ']') { return false; } size_t position = 1; while (position < arrayText.size()) { position = SkipWhitespace(arrayText, position); if (position >= arrayText.size()) { return false; } if (arrayText[position] == ']') { return true; } std::string value; if (!ParseQuotedString(arrayText, position, value, &position)) { return false; } if (!TrimCopy(value).empty()) { outValues.insert(TrimCopy(value)); } position = SkipWhitespace(arrayText, position); if (position < arrayText.size() && arrayText[position] == ',') { ++position; } } return false; } struct MaterialAuthoringPresence { bool hasRenderStateOverride = false; std::unordered_set keywordValues; std::unordered_set propertyKeys; std::unordered_set textureKeys; }; MaterialAuthoringPresence ParseMaterialAuthoringPresence(const std::string& text) { MaterialAuthoringPresence presence; presence.hasRenderStateOverride = text.find("\"renderState\"") != std::string::npos; std::string propertiesObject; if (TryExtractObject(text, "properties", propertiesObject)) { CollectObjectKeys(propertiesObject, presence.propertyKeys); } std::string texturesObject; if (TryExtractObject(text, "textures", texturesObject)) { CollectObjectKeys(texturesObject, presence.textureKeys); } std::string keywordsArray; if (TryExtractArray(text, "keywords", keywordsArray)) { CollectStringArrayValues(keywordsArray, presence.keywordValues); } return presence; } int GetMaterialPropertyComponentCount(::XCEngine::Resources::MaterialPropertyType type) { switch (type) { case ::XCEngine::Resources::MaterialPropertyType::Float2: case ::XCEngine::Resources::MaterialPropertyType::Int2: return 2; case ::XCEngine::Resources::MaterialPropertyType::Float3: case ::XCEngine::Resources::MaterialPropertyType::Int3: return 3; case ::XCEngine::Resources::MaterialPropertyType::Float4: case ::XCEngine::Resources::MaterialPropertyType::Int4: return 4; case ::XCEngine::Resources::MaterialPropertyType::Float: case ::XCEngine::Resources::MaterialPropertyType::Int: case ::XCEngine::Resources::MaterialPropertyType::Bool: case ::XCEngine::Resources::MaterialPropertyType::Texture: case ::XCEngine::Resources::MaterialPropertyType::Cubemap: default: return 1; } } std::string FormatJsonFloat(float value) { char buffer[32] = {}; std::snprintf(buffer, sizeof(buffer), "%.6g", static_cast(value)); return std::string(buffer); } std::string EscapeJsonString(const std::string& value) { std::string escaped; escaped.reserve(value.size()); for (const char ch : value) { switch (ch) { case '\\': escaped += "\\\\"; break; case '"': escaped += "\\\""; break; case '\n': escaped += "\\n"; break; case '\r': escaped += "\\r"; break; case '\t': escaped += "\\t"; break; default: escaped.push_back(ch); break; } } return escaped; } std::string BuildMaterialPropertyValueText(const MaterialPropertyState& property) { using PropertyType = ::XCEngine::Resources::MaterialPropertyType; switch (property.type) { case PropertyType::Float: return FormatJsonFloat(property.floatValue[0]); case PropertyType::Float2: case PropertyType::Float3: case PropertyType::Float4: { const int componentCount = GetMaterialPropertyComponentCount(property.type); std::string text = "["; for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) { if (componentIndex > 0) { text += ", "; } text += FormatJsonFloat(property.floatValue[componentIndex]); } text += "]"; return text; } case PropertyType::Int: return std::to_string(property.intValue[0]); case PropertyType::Int2: case PropertyType::Int3: case PropertyType::Int4: { const int componentCount = GetMaterialPropertyComponentCount(property.type); std::string text = "["; for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) { if (componentIndex > 0) { text += ", "; } text += std::to_string(property.intValue[componentIndex]); } text += "]"; return text; } case PropertyType::Bool: return property.boolValue ? "true" : "false"; case PropertyType::Texture: case PropertyType::Cubemap: default: return std::string(); } } bool TryResolveMaterialTextureBindingPath( const ::XCEngine::Resources::Material& material, const Containers::String& propertyName, std::string& outPath) { for (Core::uint32 bindingIndex = 0; bindingIndex < material.GetTextureBindingCount(); ++bindingIndex) { if (material.GetTextureBindingName(bindingIndex) != propertyName) { continue; } Containers::String bindingPath = material.GetTextureBindingPath(bindingIndex); if (bindingPath.Empty()) { const ::XCEngine::Resources::AssetRef assetRef = material.GetTextureBindingAssetRef(bindingIndex); if (assetRef.IsValid()) { ::XCEngine::Resources::ResourceManager::Get().TryResolveAssetPath(assetRef, bindingPath); } } outPath = std::string(bindingPath.CStr()); return true; } return false; } MaterialPropertyState BuildMaterialPropertyState( const ::XCEngine::Resources::MaterialProperty& property, const ::XCEngine::Resources::Material& material) { MaterialPropertyState state; state.name = std::string(property.name.CStr()); state.type = property.type; for (size_t index = 0; index < state.floatValue.size(); ++index) { state.floatValue[index] = property.value.floatValue[index]; state.intValue[index] = property.value.intValue[index]; } state.boolValue = property.value.boolValue; if (IsTextureMaterialPropertyType(property.type)) { TryResolveMaterialTextureBindingPath(material, property.name, state.texturePath); } return state; } void CopyMaterialPropertyValue( const MaterialPropertyState& source, MaterialPropertyState& destination) { destination.type = source.type; destination.floatValue = source.floatValue; destination.intValue = source.intValue; destination.boolValue = source.boolValue; destination.texturePath = source.texturePath; destination.serialized = source.serialized; } std::vector BuildShaderDefaultPropertyStates( const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle) { if (!shaderHandle.IsValid()) { return {}; } ::XCEngine::Resources::Material scratchMaterial; scratchMaterial.SetShader(shaderHandle); return CollectMaterialPropertyStates(scratchMaterial); } std::vector BuildShaderKeywordStates( const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle, const std::vector& previousKeywords) { if (!shaderHandle.IsValid() || shaderHandle.Get() == nullptr) { return {}; } std::unordered_map previousKeywordStates; previousKeywordStates.reserve(previousKeywords.size()); for (const MaterialKeywordState& keyword : previousKeywords) { const std::string keywordValue = TrimCopy(keyword.value); if (!keywordValue.empty()) { previousKeywordStates.emplace(keywordValue, keyword); } } std::vector keywordStates; std::unordered_set seenKeywords; for (const ::XCEngine::Resources::ShaderPass& pass : shaderHandle->GetPasses()) { for (const ::XCEngine::Resources::ShaderKeywordDeclaration& declaration : pass.keywordDeclarations) { for (const Containers::String& option : declaration.options) { const Containers::String normalizedKeyword = ::XCEngine::Resources::NormalizeShaderKeywordToken(option); if (normalizedKeyword.Empty()) { continue; } const std::string keywordValue(normalizedKeyword.CStr()); if (!seenKeywords.insert(keywordValue).second) { continue; } MaterialKeywordState state; state.value = keywordValue; const auto previousKeywordIt = previousKeywordStates.find(keywordValue); if (previousKeywordIt != previousKeywordStates.end()) { state.serialized = previousKeywordIt->second.serialized; } keywordStates.push_back(std::move(state)); } } } return keywordStates; } constexpr int kCustomRenderQueuePresetIndex = 5; int ResolveRenderQueuePresetIndex(int renderQueue) { switch (renderQueue) { case static_cast(::XCEngine::Resources::MaterialRenderQueue::Background): return 0; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Geometry): return 1; case static_cast(::XCEngine::Resources::MaterialRenderQueue::AlphaTest): return 2; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Transparent): return 3; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Overlay): return 4; default: return kCustomRenderQueuePresetIndex; } } const char* SerializeRenderQueue(int renderQueue) { switch (renderQueue) { case static_cast(::XCEngine::Resources::MaterialRenderQueue::Background): return "background"; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Geometry): return "geometry"; case static_cast(::XCEngine::Resources::MaterialRenderQueue::AlphaTest): return "alpha_test"; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Transparent): return "transparent"; case static_cast(::XCEngine::Resources::MaterialRenderQueue::Overlay): return "overlay"; default: return nullptr; } } const char* SerializeCullMode(::XCEngine::Resources::MaterialCullMode mode) { switch (mode) { case ::XCEngine::Resources::MaterialCullMode::Front: return "front"; case ::XCEngine::Resources::MaterialCullMode::Back: return "back"; case ::XCEngine::Resources::MaterialCullMode::None: default: return "none"; } } const char* SerializeComparisonFunc(::XCEngine::Resources::MaterialComparisonFunc func) { switch (func) { case ::XCEngine::Resources::MaterialComparisonFunc::Never: return "never"; case ::XCEngine::Resources::MaterialComparisonFunc::Less: return "less"; case ::XCEngine::Resources::MaterialComparisonFunc::Equal: return "equal"; case ::XCEngine::Resources::MaterialComparisonFunc::LessEqual: return "less_equal"; case ::XCEngine::Resources::MaterialComparisonFunc::Greater: return "greater"; case ::XCEngine::Resources::MaterialComparisonFunc::NotEqual: return "not_equal"; case ::XCEngine::Resources::MaterialComparisonFunc::GreaterEqual: return "greater_equal"; case ::XCEngine::Resources::MaterialComparisonFunc::Always: default: return "always"; } } const char* SerializeBlendOp(::XCEngine::Resources::MaterialBlendOp op) { switch (op) { case ::XCEngine::Resources::MaterialBlendOp::Subtract: return "subtract"; case ::XCEngine::Resources::MaterialBlendOp::ReverseSubtract: return "reverse_subtract"; case ::XCEngine::Resources::MaterialBlendOp::Min: return "min"; case ::XCEngine::Resources::MaterialBlendOp::Max: return "max"; case ::XCEngine::Resources::MaterialBlendOp::Add: default: return "add"; } } const char* SerializeBlendFactor(::XCEngine::Resources::MaterialBlendFactor factor) { switch (factor) { case ::XCEngine::Resources::MaterialBlendFactor::Zero: return "zero"; case ::XCEngine::Resources::MaterialBlendFactor::One: return "one"; case ::XCEngine::Resources::MaterialBlendFactor::SrcColor: return "src_color"; case ::XCEngine::Resources::MaterialBlendFactor::InvSrcColor: return "one_minus_src_color"; case ::XCEngine::Resources::MaterialBlendFactor::SrcAlpha: return "src_alpha"; case ::XCEngine::Resources::MaterialBlendFactor::InvSrcAlpha: return "one_minus_src_alpha"; case ::XCEngine::Resources::MaterialBlendFactor::DstAlpha: return "dst_alpha"; case ::XCEngine::Resources::MaterialBlendFactor::InvDstAlpha: return "one_minus_dst_alpha"; case ::XCEngine::Resources::MaterialBlendFactor::DstColor: return "dst_color"; case ::XCEngine::Resources::MaterialBlendFactor::InvDstColor: return "one_minus_dst_color"; case ::XCEngine::Resources::MaterialBlendFactor::SrcAlphaSat: return "src_alpha_sat"; case ::XCEngine::Resources::MaterialBlendFactor::BlendFactor: return "blend_factor"; case ::XCEngine::Resources::MaterialBlendFactor::InvBlendFactor: return "one_minus_blend_factor"; case ::XCEngine::Resources::MaterialBlendFactor::Src1Color: return "src1_color"; case ::XCEngine::Resources::MaterialBlendFactor::InvSrc1Color: return "one_minus_src1_color"; case ::XCEngine::Resources::MaterialBlendFactor::Src1Alpha: return "src1_alpha"; case ::XCEngine::Resources::MaterialBlendFactor::InvSrc1Alpha: default: return "one_minus_src1_alpha"; } } const char* SerializeStencilOp(::XCEngine::Resources::MaterialStencilOp op) { switch (op) { case ::XCEngine::Resources::MaterialStencilOp::Zero: return "zero"; case ::XCEngine::Resources::MaterialStencilOp::Replace: return "replace"; case ::XCEngine::Resources::MaterialStencilOp::IncrSat: return "incrsat"; case ::XCEngine::Resources::MaterialStencilOp::DecrSat: return "decrsat"; case ::XCEngine::Resources::MaterialStencilOp::Invert: return "invert"; case ::XCEngine::Resources::MaterialStencilOp::IncrWrap: return "incrwrap"; case ::XCEngine::Resources::MaterialStencilOp::DecrWrap: return "decrwrap"; case ::XCEngine::Resources::MaterialStencilOp::Keep: default: return "keep"; } } } // namespace bool IsTextureMaterialPropertyType(::XCEngine::Resources::MaterialPropertyType type) { return type == ::XCEngine::Resources::MaterialPropertyType::Texture || type == ::XCEngine::Resources::MaterialPropertyType::Cubemap; } std::vector CollectMaterialPropertyStates( const ::XCEngine::Resources::Material& material) { std::vector<::XCEngine::Resources::MaterialProperty> properties = material.GetProperties(); const ::XCEngine::Resources::Shader* shader = material.GetShader(); if (shader != nullptr && !shader->GetProperties().Empty()) { std::unordered_map shaderPropertyOrder; shaderPropertyOrder.reserve(shader->GetProperties().Size()); for (size_t propertyIndex = 0; propertyIndex < shader->GetProperties().Size(); ++propertyIndex) { shaderPropertyOrder.emplace(std::string(shader->GetProperties()[propertyIndex].name.CStr()), propertyIndex); } std::sort( properties.begin(), properties.end(), [&shaderPropertyOrder](const auto& left, const auto& right) { const std::string leftName(left.name.CStr()); const std::string rightName(right.name.CStr()); const auto leftIt = shaderPropertyOrder.find(leftName); const auto rightIt = shaderPropertyOrder.find(rightName); const size_t leftOrder = leftIt != shaderPropertyOrder.end() ? leftIt->second : shaderPropertyOrder.size(); const size_t rightOrder = rightIt != shaderPropertyOrder.end() ? rightIt->second : shaderPropertyOrder.size(); if (leftOrder != rightOrder) { return leftOrder < rightOrder; } return leftName < rightName; }); } else { std::sort( properties.begin(), properties.end(), [](const auto& left, const auto& right) { return std::strcmp(left.name.CStr(), right.name.CStr()) < 0; }); } std::vector states; states.reserve(properties.size()); for (const ::XCEngine::Resources::MaterialProperty& property : properties) { states.push_back(BuildMaterialPropertyState(property, material)); } return states; } void SyncMaterialAssetStateWithShader( const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle, MaterialAssetState& state) { if (!shaderHandle.IsValid() || shaderHandle.Get() == nullptr) { state.keywords.clear(); state.properties.clear(); return; } std::unordered_map previousProperties; previousProperties.reserve(state.properties.size()); for (const MaterialPropertyState& property : state.properties) { if (!property.name.empty()) { previousProperties.emplace(property.name, property); } } std::vector nextProperties = BuildShaderDefaultPropertyStates(shaderHandle); for (MaterialPropertyState& property : nextProperties) { const auto previousPropertyIt = previousProperties.find(property.name); if (previousPropertyIt == previousProperties.end()) { continue; } if (previousPropertyIt->second.type != property.type) { continue; } CopyMaterialPropertyValue(previousPropertyIt->second, property); } state.properties = std::move(nextProperties); state.keywords = BuildShaderKeywordStates(shaderHandle, state.keywords); } bool ResetMaterialPropertyStateToShaderDefault( const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle, const std::string& propertyName, MaterialAssetState& state) { if (!shaderHandle.IsValid() || shaderHandle.Get() == nullptr || propertyName.empty()) { return false; } MaterialPropertyState* stateProperty = nullptr; for (MaterialPropertyState& property : state.properties) { if (property.name == propertyName) { stateProperty = &property; break; } } if (stateProperty == nullptr) { return false; } const std::vector defaultProperties = BuildShaderDefaultPropertyStates(shaderHandle); for (const MaterialPropertyState& defaultProperty : defaultProperties) { if (defaultProperty.name != propertyName || defaultProperty.type != stateProperty->type) { continue; } CopyMaterialPropertyValue(defaultProperty, *stateProperty); stateProperty->serialized = false; return true; } return false; } void ApplyMaterialAuthoringPresenceToState( const std::string& sourceText, MaterialAssetState& state) { const MaterialAuthoringPresence presence = ParseMaterialAuthoringPresence(sourceText); state.hasRenderStateOverride = presence.hasRenderStateOverride; for (MaterialKeywordState& keyword : state.keywords) { keyword.serialized = presence.keywordValues.find(TrimCopy(keyword.value)) != presence.keywordValues.end(); } for (MaterialPropertyState& property : state.properties) { if (IsTextureMaterialPropertyType(property.type)) { property.serialized = presence.textureKeys.find(property.name) != presence.textureKeys.end(); } else { property.serialized = presence.propertyKeys.find(property.name) != presence.propertyKeys.end(); } } } std::string BuildMaterialAssetFileText(const MaterialAssetState& state) { std::vector rootEntries; const std::string shaderPath = TrimCopy(BufferToString(state.shaderPath)); if (!shaderPath.empty()) { rootEntries.push_back(" \"shader\": \"" + EscapeJsonString(shaderPath) + "\""); } const int renderQueuePreset = ResolveRenderQueuePresetIndex(state.renderQueue); if (renderQueuePreset != kCustomRenderQueuePresetIndex) { rootEntries.push_back( " \"renderQueue\": \"" + std::string(SerializeRenderQueue(state.renderQueue)) + "\""); } else { rootEntries.push_back(" \"renderQueue\": " + std::to_string(state.renderQueue)); } std::vector tagEntries; for (const MaterialTagEditRow& row : state.tags) { const std::string tagName = TrimCopy(BufferToString(row.name)); if (tagName.empty()) { continue; } tagEntries.push_back( " \"" + EscapeJsonString(tagName) + "\": \"" + EscapeJsonString(TrimCopy(BufferToString(row.value))) + "\""); } if (!tagEntries.empty()) { std::string tagsObject = " \"tags\": {\n"; for (size_t tagIndex = 0; tagIndex < tagEntries.size(); ++tagIndex) { tagsObject += tagEntries[tagIndex]; if (tagIndex + 1 < tagEntries.size()) { tagsObject += ","; } tagsObject += "\n"; } tagsObject += " }"; rootEntries.push_back(tagsObject); } if (!state.keywords.empty()) { std::string keywordsArray = " \"keywords\": ["; bool firstKeyword = true; for (const MaterialKeywordState& keyword : state.keywords) { if (!keyword.serialized) { continue; } const std::string keywordValue = TrimCopy(keyword.value); if (keywordValue.empty()) { continue; } if (!firstKeyword) { keywordsArray += ", "; } keywordsArray += "\"" + EscapeJsonString(keywordValue) + "\""; firstKeyword = false; } keywordsArray += "]"; if (!firstKeyword) { rootEntries.push_back(keywordsArray); } } std::vector propertyEntries; std::vector textureEntries; for (const MaterialPropertyState& property : state.properties) { if (property.name.empty()) { continue; } if (IsTextureMaterialPropertyType(property.type)) { if (!property.serialized) { continue; } const std::string texturePath = TrimCopy(property.texturePath); if (!texturePath.empty()) { textureEntries.push_back( " \"" + EscapeJsonString(property.name) + "\": \"" + EscapeJsonString(texturePath) + "\""); } continue; } if (!property.serialized) { continue; } const std::string propertyValueText = BuildMaterialPropertyValueText(property); if (!propertyValueText.empty()) { propertyEntries.push_back( " \"" + EscapeJsonString(property.name) + "\": " + propertyValueText); } } if (!propertyEntries.empty()) { std::string propertiesObject = " \"properties\": {\n"; for (size_t propertyIndex = 0; propertyIndex < propertyEntries.size(); ++propertyIndex) { propertiesObject += propertyEntries[propertyIndex]; if (propertyIndex + 1 < propertyEntries.size()) { propertiesObject += ","; } propertiesObject += "\n"; } propertiesObject += " }"; rootEntries.push_back(propertiesObject); } if (!textureEntries.empty()) { std::string texturesObject = " \"textures\": {\n"; for (size_t textureIndex = 0; textureIndex < textureEntries.size(); ++textureIndex) { texturesObject += textureEntries[textureIndex]; if (textureIndex + 1 < textureEntries.size()) { texturesObject += ","; } texturesObject += "\n"; } texturesObject += " }"; rootEntries.push_back(texturesObject); } if (state.hasRenderStateOverride) { const ::XCEngine::Resources::MaterialRenderState& renderState = state.renderState; std::vector renderStateEntries; renderStateEntries.push_back( " \"cull\": \"" + std::string(SerializeCullMode(renderState.cullMode)) + "\""); renderStateEntries.push_back(std::string(" \"depthTest\": ") + (renderState.depthTestEnable ? "true" : "false")); renderStateEntries.push_back(std::string(" \"depthWrite\": ") + (renderState.depthWriteEnable ? "true" : "false")); renderStateEntries.push_back( " \"depthFunc\": \"" + std::string(SerializeComparisonFunc(renderState.depthFunc)) + "\""); renderStateEntries.push_back(std::string(" \"blendEnable\": ") + (renderState.blendEnable ? "true" : "false")); renderStateEntries.push_back( " \"srcBlend\": \"" + std::string(SerializeBlendFactor(renderState.srcBlend)) + "\""); renderStateEntries.push_back( " \"dstBlend\": \"" + std::string(SerializeBlendFactor(renderState.dstBlend)) + "\""); renderStateEntries.push_back( " \"srcBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.srcBlendAlpha)) + "\""); renderStateEntries.push_back( " \"dstBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.dstBlendAlpha)) + "\""); renderStateEntries.push_back( " \"blendOp\": \"" + std::string(SerializeBlendOp(renderState.blendOp)) + "\""); renderStateEntries.push_back( " \"blendOpAlpha\": \"" + std::string(SerializeBlendOp(renderState.blendOpAlpha)) + "\""); renderStateEntries.push_back(" \"colorWriteMask\": " + std::to_string(renderState.colorWriteMask)); if (renderState.depthBiasFactor != 0.0f || renderState.depthBiasUnits != 0) { renderStateEntries.push_back( " \"offset\": [" + FormatJsonFloat(renderState.depthBiasFactor) + ", " + std::to_string(renderState.depthBiasUnits) + "]"); } if (renderState.stencil.enabled) { std::vector stencilEntries; stencilEntries.push_back(" \"enabled\": true"); stencilEntries.push_back(" \"ref\": " + std::to_string(renderState.stencil.reference)); stencilEntries.push_back(" \"readMask\": " + std::to_string(renderState.stencil.readMask)); stencilEntries.push_back(" \"writeMask\": " + std::to_string(renderState.stencil.writeMask)); stencilEntries.push_back( " \"comp\": \"" + std::string(SerializeComparisonFunc(renderState.stencil.front.func)) + "\""); stencilEntries.push_back( " \"pass\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.passOp)) + "\""); stencilEntries.push_back( " \"fail\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.failOp)) + "\""); stencilEntries.push_back( " \"zFail\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.depthFailOp)) + "\""); stencilEntries.push_back( " \"compBack\": \"" + std::string(SerializeComparisonFunc(renderState.stencil.back.func)) + "\""); stencilEntries.push_back( " \"passBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.passOp)) + "\""); stencilEntries.push_back( " \"failBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.failOp)) + "\""); stencilEntries.push_back( " \"zFailBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.depthFailOp)) + "\""); std::string stencilObject = " \"stencil\": {\n"; for (size_t stencilIndex = 0; stencilIndex < stencilEntries.size(); ++stencilIndex) { stencilObject += stencilEntries[stencilIndex]; if (stencilIndex + 1 < stencilEntries.size()) { stencilObject += ","; } stencilObject += "\n"; } stencilObject += " }"; renderStateEntries.push_back(stencilObject); } std::string renderStateObject = " \"renderState\": {\n"; for (size_t renderStateIndex = 0; renderStateIndex < renderStateEntries.size(); ++renderStateIndex) { renderStateObject += renderStateEntries[renderStateIndex]; if (renderStateIndex + 1 < renderStateEntries.size()) { renderStateObject += ","; } renderStateObject += "\n"; } renderStateObject += " }"; rootEntries.push_back(renderStateObject); } std::string json = "{\n"; for (size_t entryIndex = 0; entryIndex < rootEntries.size(); ++entryIndex) { json += rootEntries[entryIndex]; if (entryIndex + 1 < rootEntries.size()) { json += ","; } json += "\n"; } json += "}\n"; return json; } } // namespace Editor } // namespace XCEngine