/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "LabelDrawable.hpp" #include "Scene/TextDrawable.hpp" #include "Scene/DrawEncoder.hpp" #include "Scene/IFontAtlasGenerator.hpp" #include "Base/Logger.hpp" #include namespace OpenVulkano::Scene { using namespace Math; LabelDrawable::LabelDrawable(const LabelDrawableSettings& settings, bool isBillboard) : Drawable(DrawEncoder::GetDrawEncoder(), DrawPhase::MAIN) { m_isBillboard = isBillboard; SetLabelSettings(settings); SetupShaders(); SetupBuffers(); m_backgroundGeometry.SetFreeAfterUpload(false); } void LabelDrawable::SetLabelSettings(const LabelDrawableSettings& settings) { m_settings = settings; m_labelData.hasRoundedCorners = settings.hasRoundedCorners; m_labelData.hasArrow = settings.hasArrow; m_labelData.cornerRadius = settings.cornerRadius; m_labelData.arrowLength = settings.arrowLength; } void LabelDrawable::AddText(TextDrawable* td, const std::string& text) { if (!td || text.empty()) { return; } if (!td->GetShader()) { Logger::RENDER->error("Text drawable shader is null, cannot add text to label drawable\n"); return; } TextDrawable& textDrawable = m_texts.emplace_back(td->GetConfig()); // do not render glyph's background textDrawable.GetConfig().backgroundColor.a = 0; if (m_isBillboard) { textDrawable.SetShader(GetBillboardTextShader(td->GetShader())); } else { textDrawable.SetShader(td->GetShader()); } double lineHeight; if (textDrawable.GetFontAtlasGenerator()) { textDrawable.SetFontAtlasGenerator(td->GetFontAtlasGenerator()); lineHeight = td->GetFontAtlasGenerator()->GetAtlasMetadata().lineHeight; } else { textDrawable.SetAtlasData(td->GetAtlasData()); lineHeight = td->GetAtlasData()->meta.lineHeight; } textDrawable.GenerateText(text, m_position); RecalculateBbox(textDrawable.GetBoundingBox()); // update position for next text entry m_position.y = m_bbox.GetMin().y - lineHeight; if (!m_settings.hasArrow) { if (m_backgroundGeometry.vertexCount != 4) { m_backgroundGeometry.Init(4, 6); uint32_t indices[6] = { 0, 1, 2, 0, 2, 3 }; m_backgroundGeometry.SetIndices(indices, 6); } } else { if (m_backgroundGeometry.vertexCount != 7) { m_backgroundGeometry.Init(7, 9); uint32_t indices[9] = { 0, 1, 2, 0, 2, 3, 4, 5, 6 }; m_backgroundGeometry.SetIndices(indices, 9); } } const auto& min = m_bbox.GetMin(); const auto& max = m_bbox.GetMax(); Vertex v, v2, v3, v4; v.color = v2.color = v3.color = v4.color = m_settings.backgroundColor; const float offset = 0.001; v.position = Vector3f(min.x - m_settings.horizontalOffset, min.y - m_settings.verticalOffset, min.z - offset); v2.position = Vector3f(max.x + m_settings.horizontalOffset, min.y - m_settings.verticalOffset, min.z - offset); v3.position = Vector3f(max.x + m_settings.horizontalOffset, max.y + m_settings.verticalOffset, min.z - offset); v4.position = Vector3f(min.x - m_settings.horizontalOffset, max.y + m_settings.verticalOffset, min.z - offset); if (!m_settings.hasArrow) { v.textureCoordinates = Vector3f(0, 0, 0); v2.textureCoordinates = Vector3f(1, 0, 0); v3.textureCoordinates = Vector3f(1, 1, 0); v4.textureCoordinates = Vector3f(0, 1, 0); } else { const float w = v2.position.x - v.position.x; const float h = v3.position.y - v2.position.y + m_settings.arrowLength; v.textureCoordinates = Vector3f(0, v.position.y / h, 0); v2.textureCoordinates = Vector3f(1, v2.position.y / h, 0); v3.textureCoordinates = Vector3f(1, 1, 0); v4.textureCoordinates = Vector3f(0, 1, 0); // arrow vertices Vertex a, b, c; a.position = Vector3f(v.position.x + w / 3, v.position.y, v.position.z); b.position = Vector3f(v.position.x + w / 2, v.position.y - m_settings.arrowLength, v.position.z); c.position = Vector3f(v2.position.x - w / 3, v.position.y, v.position.z); a.color = b.color = c.color = m_settings.backgroundColor; a.textureCoordinates = Vector3f(a.position.x / w, a.position.y / h, 0); b.textureCoordinates = Vector3f(b.position.x / w, 0, 0); c.textureCoordinates = Vector3f(c.position.x / w, c.position.y / h, 0); m_backgroundGeometry.vertices[4] = a; m_backgroundGeometry.vertices[5] = b; m_backgroundGeometry.vertices[6] = c; } m_backgroundGeometry.vertices[0] = v; m_backgroundGeometry.vertices[1] = v2; m_backgroundGeometry.vertices[2] = v3; m_backgroundGeometry.vertices[3] = v4; if (m_settings.hasRoundedCorners) { m_labelData.textSize.x = v2.position.x - v.position.x; m_labelData.textSize.y = v3.position.y - v.position.y; } } void LabelDrawable::SetBillboardSettings(const BillboardControlBlock& settings) { m_billboardSettings = settings; } void LabelDrawable::RecalculateBbox(const Math::AABB& other) { if (m_bbox.IsEmpty()) { m_bbox = other; } else { auto& currentMin = m_bbox.GetMin(); auto& currentMax = m_bbox.GetMax(); currentMin.x = std::min(currentMin.x, other.min.x); currentMin.y = std::min(currentMin.y, other.min.y); currentMax.x = std::max(currentMax.x, other.max.x); currentMax.y = std::max(currentMax.y, other.max.y); } } // if we have want our label to be billboard, then we'll create new shader and set it's vertex shader to billboard.vert // to avoid modifying input shader Shader* LabelDrawable::GetBillboardTextShader(Shader* src) { auto GetFragmentShaderName = [](const Shader& shaderToCheck) -> std::optional { const auto& shaderPrograms = shaderToCheck.shaderPrograms; auto pos = std::find_if(shaderPrograms.begin(), shaderPrograms.end(), [](const ShaderProgram& program) { return program.type == ShaderProgramType::FRAGMENT; }); return pos != shaderPrograms.end() ? std::optional(pos->name) : std::nullopt; }; std::optional inputFragmentName = GetFragmentShaderName(*src); if (!inputFragmentName) { throw std::runtime_error("Shader doesn't have fragment program"); } bool exists = false; int shaderIdx = 0; for (; shaderIdx < m_billboardTextShaders.size(); shaderIdx++) { std::optional name = GetFragmentShaderName(m_billboardTextShaders[shaderIdx]); if (name.value() == inputFragmentName.value()) { exists = true; break; } } if (!exists) { Shader& shader = m_billboardTextShaders.emplace_back(); shaderIdx = m_billboardTextShaders.size() - 1; shader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/billboard"); shader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, inputFragmentName.value()); shader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); shader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); DescriptorSetLayoutBinding binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; binding.stageFlags = ShaderProgramType::Type::VERTEX; DescriptorSetLayoutBinding binding2 = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; binding2.stageFlags = ShaderProgramType::FRAGMENT; shader.AddDescriptorSetLayoutBinding(binding2, 3); shader.AddDescriptorSetLayoutBinding(binding, 4); shader.depthWrite = false; shader.depthCompareOp = CompareOp::LESS_OR_EQUAL; shader.alphaBlend = true; shader.cullMode = CullMode::NONE; } return &m_billboardTextShaders[shaderIdx]; } void LabelDrawable::SetupShaders() { DescriptorSetLayoutBinding binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; if (!m_isBillboard) { m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/basic"); } else { m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/billboard"); // binding for billboard's buffer binding.stageFlags = ShaderProgramType::Type::VERTEX; m_backgroundShader.AddDescriptorSetLayoutBinding(binding, 4); } m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/label"); m_backgroundShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); m_backgroundShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING, 2); binding.stageFlags = ShaderProgramType::Type::FRAGMENT; m_backgroundShader.AddDescriptorSetLayoutBinding(binding, 5); m_backgroundShader.cullMode = CullMode::NONE; SetShader(&m_backgroundShader); } void LabelDrawable::SetupBuffers() { DescriptorSetLayoutBinding binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; binding.stageFlags = ShaderProgramType::Type::VERTEX; m_billboardBuffer.size = sizeof(BillboardControlBlock); m_billboardBuffer.data = &m_billboardSettings; m_billboardBuffer.setId = 4; m_billboardBuffer.binding = binding; binding.stageFlags = ShaderProgramType::Type::FRAGMENT; m_labelBuffer.size = sizeof(LabelUniformData); m_labelBuffer.data = &m_labelData; m_labelBuffer.setId = 5; m_labelBuffer.binding = binding; } }