diff --git a/drape_frontend/drape_frontend_tests/CMakeLists.txt b/drape_frontend/drape_frontend_tests/CMakeLists.txt index 4484e5ad15..9d6cb89d90 100644 --- a/drape_frontend/drape_frontend_tests/CMakeLists.txt +++ b/drape_frontend/drape_frontend_tests/CMakeLists.txt @@ -25,6 +25,7 @@ set( SRC compile_shaders_test.cpp navigator_test.cpp + path_text_test.cpp shader_def_for_tests.cpp shader_def_for_tests.hpp user_event_stream_tests.cpp diff --git a/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro b/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro index 660eeee57d..49d1473aed 100644 --- a/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro +++ b/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro @@ -35,6 +35,7 @@ SOURCES += \ ../../testing/testingmain.cpp \ compile_shaders_test.cpp \ navigator_test.cpp \ + path_text_test.cpp \ shader_def_for_tests.cpp \ user_event_stream_tests.cpp \ diff --git a/drape_frontend/drape_frontend_tests/path_text_test.cpp b/drape_frontend/drape_frontend_tests/path_text_test.cpp new file mode 100644 index 0000000000..00f2487096 --- /dev/null +++ b/drape_frontend/drape_frontend_tests/path_text_test.cpp @@ -0,0 +1,57 @@ +#include "testing/testing.hpp" + +#include "drape_frontend/path_text_handle.hpp" + +#include "base/logging.hpp" + +namespace +{ +bool IsSmooth(m2::Spline const & spline) +{ + for (size_t i = 0, sz = spline.GetDirections().size(); i + 1 < sz; ++i) + { + if (!df::IsValidSplineTurn(spline.GetDirections()[i], spline.GetDirections()[i + 1])) + return false; + } + return true; +} +} + +UNIT_TEST(Rounding_Spline) +{ + m2::Spline spline1; + df::AddPointAndRound(spline1, m2::PointD(0, 200)); + df::AddPointAndRound(spline1, m2::PointD(0, 0)); + df::AddPointAndRound(spline1, m2::PointD(200, 0)); + TEST(IsSmooth(spline1), ()); + TEST(spline1.GetSize() == 8, ()); + + m2::Spline spline2; + df::AddPointAndRound(spline2, m2::PointD(-200, 0)); + df::AddPointAndRound(spline2, m2::PointD(0, 0)); + df::AddPointAndRound(spline2, m2::PointD(200, 200)); + df::AddPointAndRound(spline2, m2::PointD(400, 200)); + TEST(IsSmooth(spline2), ()); + TEST(spline2.GetSize() == 8, ()); + + m2::Spline spline3; + df::AddPointAndRound(spline3, m2::PointD(200, 100)); + df::AddPointAndRound(spline3, m2::PointD(0, 0)); + df::AddPointAndRound(spline3, m2::PointD(200, 0)); + TEST(!IsSmooth(spline3), ()); + TEST(spline3.GetSize() == 3, ()); + + m2::Spline spline4; + df::AddPointAndRound(spline4, m2::PointD(-200, 5)); + df::AddPointAndRound(spline4, m2::PointD(0, 0)); + df::AddPointAndRound(spline4, m2::PointD(200, 5)); + TEST(IsSmooth(spline4), ()); + TEST(spline4.GetSize() == 3, ()); + + m2::Spline spline5; + df::AddPointAndRound(spline5, m2::PointD(200, 5)); + df::AddPointAndRound(spline5, m2::PointD(0, 0)); + df::AddPointAndRound(spline5, m2::PointD(200, -5)); + TEST(!IsSmooth(spline5), ()); + TEST(spline5.GetSize() == 3, ()); +} diff --git a/drape_frontend/path_text_handle.cpp b/drape_frontend/path_text_handle.cpp index 38f83212d5..595e0b3164 100644 --- a/drape_frontend/path_text_handle.cpp +++ b/drape_frontend/path_text_handle.cpp @@ -1,8 +1,107 @@ #include "drape_frontend/path_text_handle.hpp" +#include "drape_frontend/visual_params.hpp" + +#include "base/math.hpp" namespace df { +namespace +{ +double const kValidSplineTurn = 15 * math::pi / 180; +double const kCosTurn = cos(kValidSplineTurn); +double const kSinTurn = sin(kValidSplineTurn); +double const kRoundStep = 23; +int const kMaxStepsCount = 7; + +bool RoundCorner(m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3, + int leftStepsCount, std::vector & roundedCorner) +{ + roundedCorner.clear(); + + double p1p2Length = (p2 - p1).Length(); + double const p2p3Length = (p3 - p2).Length(); + + auto const dir1 = (p2 - p1) / p1p2Length; + auto const dir2 = (p3 - p2) / p2p3Length; + + if (IsValidSplineTurn(dir1, dir2)) + return false; + + double const vs = df::VisualParams::Instance().GetVisualScale(); + double const kMinCornerDist = 1.0; + if ((p3 - p1).SquaredLength() < kMinCornerDist * vs) + { + roundedCorner.push_back(p1); + return false; + } + m2::PointD const np1 = p2 - dir1 * std::min(kRoundStep * vs, + p1p2Length - p1p2Length / max(leftStepsCount - 1, 2)); + p1p2Length = (p2 - np1).Length(); + double const cosCorner = m2::DotProduct(-dir1, dir2); + double const sinCorner = fabs(m2::CrossProduct(-dir1, dir2)); + + double const p2p3IntersectionLength = (p1p2Length * kSinTurn) / (kSinTurn * cosCorner + kCosTurn * sinCorner); + + if (p2p3IntersectionLength >= p2p3Length) + { + roundedCorner.push_back(np1); + return false; + } + else + { + m2::PointD const p = p2 + dir2 * p2p3IntersectionLength; + roundedCorner.push_back(np1); + roundedCorner.push_back(p); + return !IsValidSplineTurn((p - np1).Normalize(), dir2); + } +} + +void ReplaceLastCorner(std::vector const & roundedCorner, m2::Spline & spline) +{ + if (roundedCorner.empty()) + return; + spline.ReplacePoint(roundedCorner.front()); + for (size_t i = 1, sz = roundedCorner.size(); i < sz; ++i) + spline.AddPoint(roundedCorner[i]); +} +} // namespace + +bool IsValidSplineTurn(m2::PointD const & normalizedDir1, + m2::PointD const & normalizedDir2) +{ + double const dotProduct = m2::DotProduct(normalizedDir1, normalizedDir2); + double const kEps = 1e-5; + return dotProduct > kCosTurn || fabs(dotProduct - kCosTurn) < kEps; +} + +void AddPointAndRound(m2::Spline & spline, m2::PointD const & pt) +{ + if (spline.GetSize() < 2) + { + spline.AddPoint(pt); + return; + } + + auto const dir1 = spline.GetDirections().back(); + auto const dir2 = (pt - spline.GetPath().back()).Normalize(); + + double const dotProduct = m2::DotProduct(dir1, dir2); + if (dotProduct < kCosTurn) + { + int leftStepsCount = static_cast(acos(dotProduct) / kValidSplineTurn); + std::vector roundedCorner; + while (leftStepsCount > 0 && leftStepsCount <= kMaxStepsCount && + RoundCorner(spline.GetPath()[spline.GetSize() - 2], + spline.GetPath().back(), pt, leftStepsCount--, roundedCorner)) + { + ReplaceLastCorner(roundedCorner, spline); + } + ReplaceLastCorner(roundedCorner, spline); + } + spline.AddPoint(pt); +} + PathTextContext::PathTextContext(m2::SharedSpline const & spline) : m_globalSpline(spline) {} @@ -68,7 +167,7 @@ void PathTextContext::Update(ScreenBase const & screen) } continue; } - pixelSpline.AddPoint(screen.PtoP3d(pos)); + AddPointAndRound(pixelSpline, screen.PtoP3d(pos)); } if (pixelSpline.GetSize() > 1) @@ -79,18 +178,10 @@ void PathTextContext::Update(ScreenBase const & screen) for (size_t i = 0, sz = m_globalOffsets.size(); i < sz; ++i) { - if (screen.isPerspective()) + m2::PointD const pt2d = screen.GtoP(m_globalPivots[i]); + if (!screen.IsReverseProjection3d(pt2d)) { - m2::PointD const pt2d = screen.GtoP(m_globalPivots[i]); - if (!screen.IsReverseProjection3d(pt2d)) - { - m_centerPointIters.push_back(GetProjectedPoint(m_pixel3dSplines, screen.PtoP3d(pt2d))); - m_centerGlobalPivots.push_back(m_globalPivots[i]); - } - } - else - { - m_centerPointIters.push_back(m_pixel3dSplines.front().GetPoint(m_globalOffsets[i] / screen.GetScale())); + m_centerPointIters.push_back(GetProjectedPoint(m_pixel3dSplines, screen.PtoP3d(pt2d))); m_centerGlobalPivots.push_back(m_globalPivots[i]); } } diff --git a/drape_frontend/path_text_handle.hpp b/drape_frontend/path_text_handle.hpp index d7a644ddaf..c6bf52f3dc 100644 --- a/drape_frontend/path_text_handle.hpp +++ b/drape_frontend/path_text_handle.hpp @@ -71,4 +71,7 @@ private: m2::PointD m_globalPivot; float const m_depth; }; + +bool IsValidSplineTurn(m2::PointD const & normalizedDir1, m2::PointD const & normalizedDir2); +void AddPointAndRound(m2::Spline & spline, m2::PointD const & pt); } // namespace df diff --git a/drape_frontend/text_layout.cpp b/drape_frontend/text_layout.cpp index 11df700bec..2cd9293f5e 100644 --- a/drape_frontend/text_layout.cpp +++ b/drape_frontend/text_layout.cpp @@ -439,7 +439,7 @@ bool PathTextLayout::CacheDynamicGeometry(m2::Spline::iterator const & iter, flo penIter = endIter; } - glsl::vec2 pxPivot = glsl::ToVec2(iter.m_pos); + m2::PointD const pxPivot = iter.m_pos; buffer.resize(4 * m_metrics.size()); glsl::vec4 const pivot(glsl::ToVec2(MapShape::ConvertToLocal(globalPivot, m_tileCenter, @@ -447,14 +447,18 @@ bool PathTextLayout::CacheDynamicGeometry(m2::Spline::iterator const & iter, flo for (size_t i = 0; i < m_metrics.size(); ++i) { GlyphRegion const & g = m_metrics[i]; - m2::PointF pxSize = g.GetPixelSize() * m_textSizeRatio; + m2::PointF const pxSize = g.GetPixelSize() * m_textSizeRatio; + float const xAdvance = g.GetAdvanceX() * m_textSizeRatio; - m2::PointD const pxBase = penIter.m_pos; - m2::PointD const pxShiftBase = penIter.m_pos + penIter.m_dir; + m2::PointD const baseVector = penIter.m_pos - pxPivot; + m2::PointD const currentTangent = penIter.m_avrDir.Normalize(); - glsl::vec2 tangent = advanceSign * glsl::normalize(glsl::ToVec2(pxShiftBase - pxBase)); - glsl::vec2 normal = glsl::normalize(glsl::vec2(-tangent.y, tangent.x)); - glsl::vec2 formingVector = (glsl::ToVec2(pxBase) - pxPivot) + (halfFontSize * normal); + penIter.Advance(advanceSign * xAdvance); + m2::PointD const newTangent = penIter.m_avrDir.Normalize(); + + glsl::vec2 tangent = glsl::ToVec2(newTangent); + glsl::vec2 normal = glsl::vec2(-tangent.y, tangent.x); + glsl::vec2 formingVector = glsl::ToVec2(baseVector) + halfFontSize * normal; float const xOffset = g.GetOffsetX() * m_textSizeRatio; float const yOffset = g.GetOffsetY() * m_textSizeRatio; @@ -469,12 +473,12 @@ bool PathTextLayout::CacheDynamicGeometry(m2::Spline::iterator const & iter, flo buffer[baseIndex + 2] = TDV(pivot, formingVector + normal * bottomVector + tangent * (pxSize.x + xOffset)); buffer[baseIndex + 3] = TDV(pivot, formingVector + normal * upVector + tangent * (pxSize.x + xOffset)); - float const xAdvance = g.GetAdvanceX() * m_textSizeRatio; - glsl::vec2 currentTangent = glsl::ToVec2(penIter.m_dir); - penIter.Advance(advanceSign * xAdvance); - float const dotProduct = glsl::dot(currentTangent, glsl::ToVec2(penIter.m_dir)); - if (dotProduct < kValidSplineTurn) - return false; + if (i > 0) + { + float const dotProduct = m2::DotProduct(currentTangent, newTangent); + if (dotProduct < kValidSplineTurn) + return false; + } } return true; }