#include "drape_frontend/line_shape.hpp" #include "drape_frontend/line_shape_helper.hpp" #include "drape_frontend/visual_params.hpp" #include "drape/attribute_provider.hpp" #include "drape/batcher.hpp" #include "drape/glsl_types.hpp" #include "drape/glsl_func.hpp" #include "drape/shader_def.hpp" #include "drape/support_manager.hpp" #include "drape/texture_manager.hpp" #include "drape/utils/vertex_decl.hpp" #include "indexer/scales.hpp" #include "base/logging.hpp" namespace df { namespace { class TextureCoordGenerator { public: TextureCoordGenerator(dp::TextureManager::StippleRegion const & region) : m_region(region) , m_maskLength(static_cast(m_region.GetMaskPixelLength())) {} glsl::vec4 GetTexCoordsByDistance(float distance) const { return GetTexCoords(distance / m_maskLength); } glsl::vec4 GetTexCoords(float offset) const { m2::RectF const & texRect = m_region.GetTexRect(); return glsl::vec4(offset, texRect.minX(), texRect.SizeX(), texRect.Center().y); } float GetMaskLength() const { return m_maskLength; } dp::TextureManager::StippleRegion const & GetRegion() const { return m_region; } private: dp::TextureManager::StippleRegion const m_region; float const m_maskLength; }; struct BaseBuilderParams { dp::TextureManager::ColorRegion m_color; float m_pxHalfWidth; float m_depth; dp::LineCap m_cap; dp::LineJoin m_join; }; template class BaseLineBuilder : public ILineShapeInfo { public: BaseLineBuilder(BaseBuilderParams const & params, size_t geomsSize, size_t joinsSize) : m_params(params) , m_colorCoord(glsl::ToVec2(params.m_color.GetTexRect().Center())) { m_geometry.reserve(geomsSize); m_joinGeom.reserve(joinsSize); } dp::BindingInfo const & GetBindingInfo() override { return TVertex::GetBindingInfo(); } ref_ptr GetLineData() override { return make_ref(m_geometry.data()); } size_t GetLineSize() override { return m_geometry.size(); } ref_ptr GetJoinData() override { return make_ref(m_joinGeom.data()); } size_t GetJoinSize() override { return m_joinGeom.size(); } float GetHalfWidth() { return m_params.m_pxHalfWidth; } dp::BindingInfo const & GetCapBindingInfo() override { return GetBindingInfo(); } dp::GLState GetCapState() override { return GetState(); } ref_ptr GetCapData() override { return ref_ptr(); } size_t GetCapSize() override { return 0; } float GetSide(bool isLeft) const { return isLeft ? 1.0 : -1.0; } protected: using V = TVertex; using TGeometryBuffer = vector; TGeometryBuffer m_geometry; TGeometryBuffer m_joinGeom; vector m_normalBuffer; BaseBuilderParams m_params; glsl::vec2 const m_colorCoord; }; class SolidLineBuilder : public BaseLineBuilder { using TBase = BaseLineBuilder; using TNormal = gpu::LineVertex::TNormal; struct CapVertex { using TPosition = gpu::LineVertex::TPosition; using TNormal = gpu::LineVertex::TNormal; using TTexCoord = gpu::LineVertex::TTexCoord; CapVertex() {} CapVertex(TPosition const & pos, TNormal const & normal, TTexCoord const & color) : m_position(pos) , m_normal(normal) , m_color(color) {} TPosition m_position; TNormal m_normal; TTexCoord m_color; }; using TCapBuffer = vector; public: using BuilderParams = BaseBuilderParams; SolidLineBuilder(BuilderParams const & params, size_t pointsInSpline) : TBase(params, pointsInSpline * 2, (pointsInSpline - 2) * 8) {} dp::GLState GetState() override { dp::GLState state(gpu::LINE_PROGRAM, dp::GLState::GeometryLayer); state.SetColorTexture(m_params.m_color.GetTexture()); return state; } dp::BindingInfo const & GetCapBindingInfo() override { if (m_params.m_cap == dp::ButtCap) return TBase::GetCapBindingInfo(); static unique_ptr s_capInfo; if (s_capInfo == nullptr) { dp::BindingFiller filler(3); filler.FillDecl("a_position"); filler.FillDecl("a_normal"); filler.FillDecl("a_colorTexCoords"); s_capInfo.reset(new dp::BindingInfo(filler.m_info)); } return *s_capInfo; } dp::GLState GetCapState() override { if (m_params.m_cap == dp::ButtCap) return TBase::GetCapState(); dp::GLState state(gpu::CAP_JOIN_PROGRAM, dp::GLState::GeometryLayer); state.SetColorTexture(m_params.m_color.GetTexture()); state.SetDepthFunction(gl_const::GLLess); return state; } ref_ptr GetCapData() override { return make_ref(m_capGeometry.data()); } size_t GetCapSize() override { return m_capGeometry.size(); } void SubmitVertex(glsl::vec3 const & pivot, glsl::vec2 const & normal, bool isLeft) { float const halfWidth = GetHalfWidth(); m_geometry.emplace_back(V(pivot, TNormal(halfWidth * normal, halfWidth * GetSide(isLeft)), m_colorCoord)); } void SubmitJoin(glsl::vec2 const & pos) { if (m_params.m_join == dp::RoundJoin) CreateRoundCap(pos); } void SubmitCap(glsl::vec2 const & pos) { if (m_params.m_cap != dp::ButtCap) CreateRoundCap(pos); } private: void CreateRoundCap(glsl::vec2 const & pos) { m_capGeometry.reserve(8); float const radius = GetHalfWidth(); m_capGeometry.push_back(CapVertex(CapVertex::TPosition(pos, m_params.m_depth), CapVertex::TNormal(-radius, radius, radius), CapVertex::TTexCoord(m_colorCoord))); m_capGeometry.push_back(CapVertex(CapVertex::TPosition(pos, m_params.m_depth), CapVertex::TNormal(-radius, -radius, radius), CapVertex::TTexCoord(m_colorCoord))); m_capGeometry.push_back(CapVertex(CapVertex::TPosition(pos, m_params.m_depth), CapVertex::TNormal(radius, radius, radius), CapVertex::TTexCoord(m_colorCoord))); m_capGeometry.push_back(CapVertex(CapVertex::TPosition(pos, m_params.m_depth), CapVertex::TNormal(radius, -radius, radius), CapVertex::TTexCoord(m_colorCoord))); } private: TCapBuffer m_capGeometry; }; class SimpleSolidLineBuilder : public BaseLineBuilder { using TBase = BaseLineBuilder; public: using BuilderParams = BaseBuilderParams; SimpleSolidLineBuilder(BuilderParams const & params, size_t pointsInSpline, int lineWidth) : TBase(params, pointsInSpline, 0) , m_lineWidth(lineWidth) {} dp::GLState GetState() override { dp::GLState state(gpu::AREA_OUTLINE_PROGRAM, dp::GLState::GeometryLayer); state.SetColorTexture(m_params.m_color.GetTexture()); state.SetDrawAsLine(true); state.SetLineWidth(m_lineWidth); return state; } void SubmitVertex(glsl::vec3 const & pivot) { m_geometry.emplace_back(V(pivot, m_colorCoord)); } private: int m_lineWidth; }; class DashedLineBuilder : public BaseLineBuilder { using TBase = BaseLineBuilder; using TNormal = gpu::LineVertex::TNormal; public: struct BuilderParams : BaseBuilderParams { dp::TextureManager::StippleRegion m_stipple; float m_glbHalfWidth; float m_baseGtoP; }; DashedLineBuilder(BuilderParams const & params, size_t pointsInSpline) : TBase(params, pointsInSpline * 8, (pointsInSpline - 2) * 8) , m_texCoordGen(params.m_stipple) , m_baseGtoPScale(params.m_baseGtoP) {} int GetDashesCount(float const globalLength) const { float const pixelLen = globalLength * m_baseGtoPScale; return static_cast((pixelLen + m_texCoordGen.GetMaskLength() - 1) / m_texCoordGen.GetMaskLength()); } dp::GLState GetState() override { dp::GLState state(gpu::DASHED_LINE_PROGRAM, dp::GLState::GeometryLayer); state.SetColorTexture(m_params.m_color.GetTexture()); state.SetMaskTexture(m_texCoordGen.GetRegion().GetTexture()); return state; } void SubmitVertex(glsl::vec3 const & pivot, glsl::vec2 const & normal, bool isLeft, float offsetFromStart) { float const halfWidth = GetHalfWidth(); m_geometry.emplace_back(V(pivot, TNormal(halfWidth * normal, halfWidth * GetSide(isLeft)), m_colorCoord, m_texCoordGen.GetTexCoordsByDistance(offsetFromStart))); } private: TextureCoordGenerator m_texCoordGen; float const m_baseGtoPScale; }; } // namespace LineShape::LineShape(m2::SharedSpline const & spline, LineViewParams const & params) : m_params(params) , m_spline(spline) , m_isSimple(false) { ASSERT_GREATER(m_spline->GetPath().size(), 1, ()); } template void LineShape::Construct(TBuilder & builder) const { ASSERT(false, ("No implementation")); } // Specialization optimized for dashed lines. template <> void LineShape::Construct(DashedLineBuilder & builder) const { vector const & path = m_spline->GetPath(); ASSERT_GREATER(path.size(), 1, ()); // build geometry for (size_t i = 1; i < path.size(); ++i) { if (path[i].EqualDxDy(path[i - 1], 1.0E-5)) continue; glsl::vec2 const p1 = glsl::ToVec2(ConvertToLocal(path[i - 1], m_params.m_tileCenter, kShapeCoordScalar)); glsl::vec2 const p2 = glsl::ToVec2(ConvertToLocal(path[i], m_params.m_tileCenter, kShapeCoordScalar)); glsl::vec2 tangent, leftNormal, rightNormal; CalculateTangentAndNormals(p1, p2, tangent, leftNormal, rightNormal); // calculate number of steps to cover line segment float const initialGlobalLength = static_cast((path[i] - path[i - 1]).Length()); int const steps = max(1, builder.GetDashesCount(initialGlobalLength)); float const maskSize = glsl::length(p2 - p1) / steps; float const offsetSize = initialGlobalLength / steps; // generate vertices float currentSize = 0; glsl::vec3 currentStartPivot = glsl::vec3(p1, m_params.m_depth); for (int step = 0; step < steps; step++) { currentSize += maskSize; glsl::vec3 const newPivot = glsl::vec3(p1 + tangent * currentSize, m_params.m_depth); builder.SubmitVertex(currentStartPivot, rightNormal, false /* isLeft */, 0.0); builder.SubmitVertex(currentStartPivot, leftNormal, true /* isLeft */, 0.0); builder.SubmitVertex(newPivot, rightNormal, false /* isLeft */, offsetSize); builder.SubmitVertex(newPivot, leftNormal, true /* isLeft */, offsetSize); currentStartPivot = newPivot; } } } // Specialization optimized for solid lines. template <> void LineShape::Construct(SolidLineBuilder & builder) const { vector const & path = m_spline->GetPath(); ASSERT_GREATER(path.size(), 1, ()); // skip joins generation float const kJoinsGenerationThreshold = 2.5f; bool generateJoins = true; if (builder.GetHalfWidth() <= kJoinsGenerationThreshold) generateJoins = false; // build geometry glsl::vec2 firstPoint = glsl::ToVec2(ConvertToLocal(path.front(), m_params.m_tileCenter, kShapeCoordScalar)); glsl::vec2 lastPoint; bool hasConstructedSegments = false; for (size_t i = 1; i < path.size(); ++i) { if (path[i].EqualDxDy(path[i - 1], 1.0E-5)) continue; glsl::vec2 const p1 = glsl::ToVec2(ConvertToLocal(path[i - 1], m_params.m_tileCenter, kShapeCoordScalar)); glsl::vec2 const p2 = glsl::ToVec2(ConvertToLocal(path[i], m_params.m_tileCenter, kShapeCoordScalar)); glsl::vec2 tangent, leftNormal, rightNormal; CalculateTangentAndNormals(p1, p2, tangent, leftNormal, rightNormal); glsl::vec3 const startPoint = glsl::vec3(p1, m_params.m_depth); glsl::vec3 const endPoint = glsl::vec3(p2, m_params.m_depth); builder.SubmitVertex(startPoint, rightNormal, false /* isLeft */); builder.SubmitVertex(startPoint, leftNormal, true /* isLeft */); builder.SubmitVertex(endPoint, rightNormal, false /* isLeft */); builder.SubmitVertex(endPoint, leftNormal, true /* isLeft */); // generate joins if (generateJoins && i < path.size() - 1) builder.SubmitJoin(p2); lastPoint = p2; hasConstructedSegments = true; } if (hasConstructedSegments) { builder.SubmitCap(firstPoint); builder.SubmitCap(lastPoint); } } // Specialization optimized for simple solid lines. template <> void LineShape::Construct(SimpleSolidLineBuilder & builder) const { vector const & path = m_spline->GetPath(); ASSERT_GREATER(path.size(), 1, ()); // Build geometry. for (size_t i = 0; i < path.size(); ++i) { glsl::vec2 const p = glsl::ToVec2(ConvertToLocal(path[i], m_params.m_tileCenter, kShapeCoordScalar)); builder.SubmitVertex(glsl::vec3(p, m_params.m_depth)); } } bool LineShape::CanBeSimplified(int & lineWidth) const { // Disable simplification for world map. if (m_params.m_zoomLevel > 0 && m_params.m_zoomLevel <= scales::GetUpperCountryScale()) return false; static float width = min(2.5f, static_cast(dp::SupportManager::Instance().GetMaxLineWidth())); if (m_params.m_width <= width) { lineWidth = max(1, static_cast(m_params.m_width)); return true; } lineWidth = 1; return false; } void LineShape::Prepare(ref_ptr textures) const { float const pxHalfWidth = m_params.m_width / 2.0f; dp::TextureManager::ColorRegion colorRegion; textures->GetColorRegion(m_params.m_color, colorRegion); auto commonParamsBuilder = [&](BaseBuilderParams & p) { p.m_cap = m_params.m_cap; p.m_color = colorRegion; p.m_depth = m_params.m_depth; p.m_join = m_params.m_join; p.m_pxHalfWidth = pxHalfWidth; }; if (m_params.m_pattern.empty()) { int lineWidth = 1; m_isSimple = CanBeSimplified(lineWidth); if (m_isSimple) { SimpleSolidLineBuilder::BuilderParams p; commonParamsBuilder(p); auto builder = make_unique(p, m_spline->GetPath().size(), lineWidth); Construct(*builder); m_lineShapeInfo = move(builder); } else { SolidLineBuilder::BuilderParams p; commonParamsBuilder(p); auto builder = make_unique(p, m_spline->GetPath().size()); Construct(*builder); m_lineShapeInfo = move(builder); } } else { dp::TextureManager::StippleRegion maskRegion; textures->GetStippleRegion(m_params.m_pattern, maskRegion); DashedLineBuilder::BuilderParams p; commonParamsBuilder(p); p.m_stipple = maskRegion; p.m_baseGtoP = m_params.m_baseGtoPScale; p.m_glbHalfWidth = pxHalfWidth / m_params.m_baseGtoPScale; auto builder = make_unique(p, m_spline->GetPath().size()); Construct(*builder); m_lineShapeInfo = move(builder); } } void LineShape::Draw(ref_ptr batcher, ref_ptr textures) const { if (!m_lineShapeInfo) Prepare(textures); ASSERT(m_lineShapeInfo != nullptr, ()); dp::GLState state = m_lineShapeInfo->GetState(); dp::AttributeProvider provider(1, m_lineShapeInfo->GetLineSize()); provider.InitStream(0, m_lineShapeInfo->GetBindingInfo(), m_lineShapeInfo->GetLineData()); if (!m_isSimple) { batcher->InsertListOfStrip(state, make_ref(&provider), dp::Batcher::VertexPerQuad); size_t const joinSize = m_lineShapeInfo->GetJoinSize(); if (joinSize > 0) { dp::AttributeProvider joinsProvider(1, joinSize); joinsProvider.InitStream(0, m_lineShapeInfo->GetBindingInfo(), m_lineShapeInfo->GetJoinData()); batcher->InsertTriangleList(state, make_ref(&joinsProvider)); } size_t const capSize = m_lineShapeInfo->GetCapSize(); if (capSize > 0) { dp::AttributeProvider capProvider(1, capSize); capProvider.InitStream(0, m_lineShapeInfo->GetCapBindingInfo(), m_lineShapeInfo->GetCapData()); batcher->InsertListOfStrip(m_lineShapeInfo->GetCapState(), make_ref(&capProvider), dp::Batcher::VertexPerQuad); } } else { batcher->InsertLineStrip(state, make_ref(&provider)); } } } // namespace df