diff --git a/drape_frontend/backend_renderer.cpp b/drape_frontend/backend_renderer.cpp index 346dc33529..cac9cc2afb 100644 --- a/drape_frontend/backend_renderer.cpp +++ b/drape_frontend/backend_renderer.cpp @@ -186,7 +186,7 @@ void BackendRenderer::AcceptMessage(ref_ptr message) case Message::AddRoute: { ref_ptr msg = message; - m_routeBuilder->Build(msg->GetRoutePolyline(), msg->GetColor(), m_texMng); + m_routeBuilder->Build(msg->GetRoutePolyline(), msg->GetTurns(), msg->GetColor(), m_texMng); break; } case Message::RemoveRoute: diff --git a/drape_frontend/drape_engine.cpp b/drape_frontend/drape_engine.cpp index 64e61c77c4..9778d6173f 100644 --- a/drape_frontend/drape_engine.cpp +++ b/drape_frontend/drape_engine.cpp @@ -314,10 +314,10 @@ bool DrapeEngine::GetMyPosition(m2::PointD & myPosition) return hasPosition; } -void DrapeEngine::AddRoute(m2::PolylineD const & routePolyline, dp::Color const & color) +void DrapeEngine::AddRoute(m2::PolylineD const & routePolyline, vector const & turns, dp::Color const & color) { m_threadCommutator->PostMessage(ThreadsCommutator::ResourceUploadThread, - make_unique_dp(routePolyline, color), + make_unique_dp(routePolyline, turns, color), MessagePriority::Normal); } diff --git a/drape_frontend/drape_engine.hpp b/drape_frontend/drape_engine.hpp index 83a420522a..4efb3a911c 100644 --- a/drape_frontend/drape_engine.hpp +++ b/drape_frontend/drape_engine.hpp @@ -98,7 +98,7 @@ public: void DeselectObject(); bool GetMyPosition(m2::PointD & myPosition); - void AddRoute(m2::PolylineD const & routePolyline, dp::Color const & color); + void AddRoute(m2::PolylineD const & routePolyline, vector const & turns, dp::Color const & color); void RemoveRoute(bool deactivateFollowing); void SetWidgetLayout(gui::TWidgetsLayoutInfo && info); diff --git a/drape_frontend/frontend_renderer.cpp b/drape_frontend/frontend_renderer.cpp index e9e2b4aeb5..dfc432a6bd 100755 --- a/drape_frontend/frontend_renderer.cpp +++ b/drape_frontend/frontend_renderer.cpp @@ -500,7 +500,6 @@ void FrontendRenderer::RenderScene(ScreenBase const & modelView) m_selectionShape->Render(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); } - m_routeRenderer->Render(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); m_myPositionController->Render(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); for (; currentRenderGroup < m_renderGroups.size(); ++currentRenderGroup) @@ -521,6 +520,9 @@ void FrontendRenderer::RenderScene(ScreenBase const & modelView) RenderSingleGroup(modelView, make_ref(group)); } + GLFunctions::glClearDepth(); + m_routeRenderer->Render(modelView, make_ref(m_gpuProgramManager), m_generalUniforms); + GLFunctions::glClearDepth(); if (m_guiRenderer != nullptr) diff --git a/drape_frontend/message_subclasses.hpp b/drape_frontend/message_subclasses.hpp index 27206aed0b..9509674283 100644 --- a/drape_frontend/message_subclasses.hpp +++ b/drape_frontend/message_subclasses.hpp @@ -497,8 +497,9 @@ private: class AddRouteMessage : public Message { public: - AddRouteMessage(m2::PolylineD const & routePolyline, dp::Color const & color) + AddRouteMessage(m2::PolylineD const & routePolyline, vector const & turns, dp::Color const & color) : m_routePolyline(routePolyline) + , m_turns(turns) , m_color(color) {} @@ -506,10 +507,12 @@ public: m2::PolylineD const & GetRoutePolyline() { return m_routePolyline; } dp::Color const & GetColor() const { return m_color; } + vector const & GetTurns() const { return m_turns; } private: m2::PolylineD m_routePolyline; dp::Color m_color; + vector m_turns; }; class RemoveRouteMessage : public Message diff --git a/drape_frontend/route_builder.cpp b/drape_frontend/route_builder.cpp index 2f5029d996..a4e80511d3 100644 --- a/drape_frontend/route_builder.cpp +++ b/drape_frontend/route_builder.cpp @@ -12,7 +12,8 @@ RouteBuilder::RouteBuilder(RouteBuilder::TFlushRouteFn const & flushRouteFn) , m_batcher(make_unique_dp(ESTIMATE_BUFFER_SIZE, ESTIMATE_BUFFER_SIZE)) {} -void RouteBuilder::Build(m2::PolylineD const & routePolyline, dp::Color const & color, ref_ptr textures) +void RouteBuilder::Build(m2::PolylineD const & routePolyline, vector const & turns, + dp::Color const & color, ref_ptr textures) { CommonViewParams params; params.m_depth = 0.0f; @@ -26,6 +27,7 @@ void RouteBuilder::Build(m2::PolylineD const & routePolyline, dp::Color const & routeData.m_arrowTextureRect = textureRect; routeData.m_joinsBounds = shape.GetJoinsBounds(); routeData.m_length = shape.GetLength(); + routeData.m_turns = turns; auto flushRoute = [this, &routeData](dp::GLState const & state, drape_ptr && bucket) { diff --git a/drape_frontend/route_builder.hpp b/drape_frontend/route_builder.hpp index c2ed30cca9..e6df10e3c8 100644 --- a/drape_frontend/route_builder.hpp +++ b/drape_frontend/route_builder.hpp @@ -22,6 +22,7 @@ struct RouteData m2::RectF m_arrowTextureRect; vector m_joinsBounds; double m_length; + vector m_turns; }; class RouteBuilder @@ -31,7 +32,8 @@ public: RouteBuilder(TFlushRouteFn const & flushRouteFn); - void Build(m2::PolylineD const & routePolyline, dp::Color const & color, ref_ptr textures); + void Build(m2::PolylineD const & routePolyline, vector const & turns, + dp::Color const & color, ref_ptr textures); private: TFlushRouteFn m_flushRouteFn; diff --git a/drape_frontend/route_renderer.cpp b/drape_frontend/route_renderer.cpp index a7eb2e81a0..196b5dde45 100644 --- a/drape_frontend/route_renderer.cpp +++ b/drape_frontend/route_renderer.cpp @@ -22,11 +22,13 @@ float const halfWidthInPixel[] = 2.0f, 2.5f, 3.5f, 5.0f, 7.5f, 10.0f, 14.0f, 18.0f, 36.0f, }; +int const arrowAppearingZoomLevel = 14; + int const arrowPartsCount = 3; double const arrowHeightFactor = 96.0 / 36.0; double const arrowAspect = 400.0 / 192.0; double const arrowTailSize = 20.0 / 400.0; -double const arrowHeadSize = 124.0 / 400.0; +double const arrowHeadSize = 120.0 / 400.0; struct RouteSegment { @@ -41,42 +43,82 @@ struct RouteSegment {} }; -int FindNearestAvailableSegment(bool isTail, double start, double end, vector const & segments) +int CheckForIntersection(double start, double end, vector const & segments) { - // check if distance is inside unavailable segment - int startIndex = -1; - int endIndex = -1; for (size_t i = 0; i < segments.size(); i++) { - if (!segments[i].m_isAvailable && start >= segments[i].m_start && start <= segments[i].m_end) - startIndex = i; + if (segments[i].m_isAvailable) + continue; - if (!segments[i].m_isAvailable && end >= segments[i].m_start && end <= segments[i].m_end) - endIndex = i; + if ((start >= segments[i].m_start && start <= segments[i].m_end) || + (end >= segments[i].m_start && end <= segments[i].m_end) || + (start < segments[i].m_start && end > segments[i].m_end)) + return i; } + return -1; +} + +int FindNearestAvailableSegment(double start, double end, vector const & segments) +{ + double const threshold = 0.8; + + // check if distance intersects unavailable segment + int index = CheckForIntersection(start, end, segments); // find nearest available segment if necessary - int index = max(startIndex, endIndex); if (index != -1) { double const len = end - start; - if (isTail) + for (int i = index; i < (int)segments.size(); i++) { - for (int i = index; i >= 0; i--) - if (segments[i].m_isAvailable && len <= (segments[i].m_end - segments[i].m_start)) - return (int)i; - } - else - { - for (int i = index; i < (int)segments.size(); i++) - if (segments[i].m_isAvailable && len <= (segments[i].m_end - segments[i].m_start)) - return (int)i; + double const factor = (segments[i].m_end - segments[i].m_start) / len; + if (segments[i].m_isAvailable && factor > threshold) + return (int)i; } } return -1; } +void MergeAndClipBorders(vector & borders, double scale, double arrowTextureWidth) +{ + if (borders.empty()) + return; + + // mark groups + for (size_t i = 0; i < borders.size() - 1; i++) + { + if (borders[i].m_endDistance >= borders[i + 1].m_startDistance) + borders[i + 1].m_groupIndex = borders[i].m_groupIndex; + } + + // merge groups + int lastGroup = 0; + size_t lastGroupIndex = 0; + for (size_t i = 1; i < borders.size(); i++) + { + if (borders[i].m_groupIndex != lastGroup) + { + borders[lastGroupIndex].m_endDistance = borders[i - 1].m_endDistance; + lastGroupIndex = i; + lastGroup = borders[i].m_groupIndex; + } + else + { + borders[i].m_groupIndex = -1; + } + } + borders[lastGroupIndex].m_endDistance = borders.back().m_endDistance; + + // clip groups + auto const iter = remove_if(borders.begin(), borders.end(), + [&scale, &arrowTextureWidth](ArrowBorders const & borders) + { + return borders.m_groupIndex == -1; + }); + borders.erase(iter, borders.end()); +} + } RouteGraphics::RouteGraphics(dp::GLState const & state, @@ -123,51 +165,63 @@ void RouteRenderer::Render(ScreenBase const & screen, ref_ptrRender(); // arrows rendering - double arrowHalfWidth = halfWidth * arrowHeightFactor; - uniformStorage.SetFloatValue("u_halfWidth", arrowHalfWidth, arrowHalfWidth * screen.GetScale()); - uniformStorage.SetFloatValue("u_textureRect", m_routeData.m_arrowTextureRect.minX(), - m_routeData.m_arrowTextureRect.minY(), - m_routeData.m_arrowTextureRect.maxX(), - m_routeData.m_arrowTextureRect.maxY()); + if (truncedZoom >= arrowAppearingZoomLevel) + RenderArrow(graphics, halfWidth, screen, mng, commonUniforms); + } +} - m_turnPoints = { 0.0091, 0.0109 }; - double const textureWidth = 2.0 * arrowHalfWidth * arrowAspect; - vector arrowBorders; - CalculateArrowBorders(0.001, screen.GetScale(), textureWidth, arrowHalfWidth * screen.GetScale(), arrowBorders); +void RouteRenderer::RenderArrow(RouteGraphics const & graphics, float halfWidth, ScreenBase const & screen, + ref_ptr mng, dp::UniformValuesStorage const & commonUniforms) +{ + double const arrowHalfWidth = halfWidth * arrowHeightFactor; + double const arrowSize = 0.001; + double const textureWidth = 2.0 * arrowHalfWidth * arrowAspect; - ref_ptr prgArrow = mng->GetProgram(gpu::ROUTE_ARROW_PROGRAM); - prgArrow->Bind(); - dp::ApplyState(graphics.m_state, prgArrow); - dp::ApplyUniforms(commonUniforms, prgArrow); + dp::UniformValuesStorage uniformStorage; + uniformStorage.SetFloatValue("u_halfWidth", arrowHalfWidth, arrowHalfWidth * screen.GetScale()); + uniformStorage.SetFloatValue("u_textureRect", m_routeData.m_arrowTextureRect.minX(), + m_routeData.m_arrowTextureRect.minY(), + m_routeData.m_arrowTextureRect.maxX(), + m_routeData.m_arrowTextureRect.maxY()); - size_t const elementsCount = 16; - vector borders(elementsCount, 0.0); - size_t index = 0; - for (size_t i = 0; i < arrowBorders.size(); i++) + // calculate arrows + vector arrowBorders; + CalculateArrowBorders(arrowSize, screen.GetScale(), textureWidth, arrowHalfWidth * screen.GetScale(), arrowBorders); + + // bind shaders + ref_ptr prgArrow = mng->GetProgram(gpu::ROUTE_ARROW_PROGRAM); + prgArrow->Bind(); + dp::ApplyState(graphics.m_state, prgArrow); + dp::ApplyUniforms(commonUniforms, prgArrow); + + // split arrow's data by 16-elements buckets + size_t const elementsCount = 16; + vector borders(elementsCount, 0.0); + size_t index = 0; + for (size_t i = 0; i < arrowBorders.size(); i++) + { + borders[index++] = arrowBorders[i].m_startDistance; + borders[index++] = arrowBorders[i].m_startTexCoord; + borders[index++] = arrowBorders[i].m_endDistance; + borders[index++] = arrowBorders[i].m_endTexCoord; + + // fill rests by zeros + if (i == arrowBorders.size() - 1) { - borders[index++] = arrowBorders[i].m_startDistance; - borders[index++] = arrowBorders[i].m_startTexCoord; - borders[index++] = arrowBorders[i].m_endDistance; - borders[index++] = arrowBorders[i].m_endTexCoord; + for (size_t j = index; j < elementsCount; j++) + borders[j] = 0.0; - // fill rests by zeros - if (i == arrowBorders.size() - 1) - { - for (size_t j = index; j < elementsCount; j++) - borders[j] = 0.0; + index = elementsCount; + } - index = elementsCount; - } + // render arrow's parts + if (index == elementsCount) + { + index = 0; + uniformStorage.SetMatrix4x4Value("u_arrowBorders", borders.data()); - // render arrow's parts - if (index == elementsCount) - { - index = 0; - uniformStorage.SetMatrix4x4Value("u_arrowBorders", borders.data()); - - dp::ApplyUniforms(uniformStorage, prgArrow); - graphics.m_buffer->Render(); - } + dp::ApplyUniforms(uniformStorage, prgArrow); + graphics.m_buffer->Render(); } } } @@ -196,49 +250,6 @@ void RouteRenderer::UpdateDistanceFromBegin(double distanceFromBegin) m_distanceFromBegin = distanceFromBegin; } -void RouteRenderer::MergeAndClipBorders(vector & borders, double scale, double arrowTextureWidth) -{ - if (borders.empty()) - return; - - // mark groups - for (size_t i = 0; i < borders.size() - 1; i++) - { - if (borders[i].m_endDistance >= borders[i + 1].m_startDistance) - borders[i + 1].m_groupIndex = borders[i].m_groupIndex; - } - - // merge groups - int lastGroup = 0; - size_t lastGroupIndex = 0; - for (size_t i = 1; i < borders.size(); i++) - { - if (borders[i].m_groupIndex != lastGroup) - { - borders[lastGroupIndex].m_endDistance = borders[i - 1].m_endDistance; - lastGroupIndex = i; - lastGroup = borders[i].m_groupIndex; - } - else - { - borders[i].m_groupIndex = -1; - } - } - borders[lastGroupIndex].m_endDistance = borders.back().m_endDistance; - - // clip groups - auto const iter = remove_if(borders.begin(), borders.end(), - [&scale, &arrowTextureWidth](ArrowBorders const & borders) - { - if (borders.m_groupIndex == -1) - return true; - - double const distanceInPixels = (borders.m_endDistance - borders.m_startDistance) * 0.9 / scale; - return distanceInPixels < (arrowHeadSize + arrowTailSize) * arrowTextureWidth; - }); - borders.erase(iter, borders.end()); -} - void RouteRenderer::ApplyJoinsBounds(double arrowTextureWidth, double joinsBoundsScalar, double glbTailLength, double glbHeadLength, double scale, vector & borders) { @@ -261,23 +272,15 @@ void RouteRenderer::ApplyJoinsBounds(double arrowTextureWidth, double joinsBound } segments.back().m_end = m_routeData.m_length; - // shift tail and head of arrow if necessary + // shift head of arrow if necessary bool needMerge = false; for (size_t i = 0; i < borders.size(); i++) { - int tailIndex = FindNearestAvailableSegment(true /* isTail */, borders[i].m_startDistance, - borders[i].m_startDistance + glbTailLength, segments); - if (tailIndex != -1) - { - borders[i].m_startDistance = segments[tailIndex].m_end - glbTailLength; - needMerge = true; - } - - int headIndex = FindNearestAvailableSegment(false /* isTail */, borders[i].m_endDistance - glbHeadLength, + int headIndex = FindNearestAvailableSegment(borders[i].m_endDistance - glbHeadLength, borders[i].m_endDistance, segments); if (headIndex != -1) { - borders[i].m_endDistance = segments[headIndex].m_start + glbHeadLength; + borders[i].m_endDistance = min(m_routeData.m_length, segments[headIndex].m_start + glbHeadLength); needMerge = true; } } @@ -290,30 +293,34 @@ void RouteRenderer::ApplyJoinsBounds(double arrowTextureWidth, double joinsBound void RouteRenderer::CalculateArrowBorders(double arrowLength, double scale, double arrowTextureWidth, double joinsBoundsScalar, vector & borders) { - if (m_turnPoints.empty()) + if (m_routeData.m_turns.empty()) return; - double const halfLen = 0.5 * arrowLength; + double halfLen = 0.5 * arrowLength; double const glbTextureWidth = arrowTextureWidth * scale; double const glbTailLength = arrowTailSize * glbTextureWidth; double const glbHeadLength = arrowHeadSize * glbTextureWidth; - borders.reserve(m_turnPoints.size() * arrowPartsCount); + borders.reserve(m_routeData.m_turns.size() * arrowPartsCount); + + double const halfTextureWidth = 0.5 * glbTextureWidth; + if (halfLen < halfTextureWidth) + halfLen = halfTextureWidth; // initial filling - for (size_t i = 0; i < m_turnPoints.size(); i++) + for (size_t i = 0; i < m_routeData.m_turns.size(); i++) { ArrowBorders arrowBorders; arrowBorders.m_groupIndex = (int)i; - arrowBorders.m_startDistance = m_turnPoints[i] - halfLen; - arrowBorders.m_endDistance = m_turnPoints[i] + halfLen; + arrowBorders.m_startDistance = max(0.0, m_routeData.m_turns[i] - halfLen * 0.8); + arrowBorders.m_endDistance = min(m_routeData.m_length, m_routeData.m_turns[i] + halfLen * 1.2); borders.push_back(arrowBorders); } // merge intersected borders and clip them MergeAndClipBorders(borders, scale, arrowTextureWidth); - // apply joins bounds to prevent draw arrow's head and tail on a join + // apply joins bounds to prevent draw arrow's head on a join ApplyJoinsBounds(arrowTextureWidth, joinsBoundsScalar, glbTailLength, glbHeadLength, scale, borders); diff --git a/drape_frontend/route_renderer.hpp b/drape_frontend/route_renderer.hpp index 00d6a5c150..3756d5c136 100644 --- a/drape_frontend/route_renderer.hpp +++ b/drape_frontend/route_renderer.hpp @@ -52,15 +52,16 @@ public: private: void CalculateArrowBorders(double arrowLength, double scale, double arrowTextureWidth, double joinsBoundsScalar, vector & borders); - void MergeAndClipBorders(vector & borders, double scale, double arrowTextureWidth); void ApplyJoinsBounds(double arrowTextureWidth, double joinsBoundsScalar, double glbTailLength, double glbHeadLength, double scale, vector & borders); + void RenderArrow(RouteGraphics const & graphics, float halfWidth, ScreenBase const & screen, + ref_ptr mng, dp::UniformValuesStorage const & commonUniforms); + vector m_routeGraphics; double m_distanceFromBegin; RouteData m_routeData; - vector m_turnPoints; }; } // namespace df diff --git a/map/framework.cpp b/map/framework.cpp index dc4457fe49..19f7edc314 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1960,6 +1960,7 @@ void Framework::CloseRouting() void Framework::InsertRoute(Route const & route) { ASSERT_THREAD_CHECKER(m_threadChecker, ("InsertRoute")); + ASSERT(m_drapeEngine != nullptr, ()); if (route.GetPoly().GetSize() < 2) { @@ -1967,9 +1968,15 @@ void Framework::InsertRoute(Route const & route) return; } - ASSERT(m_drapeEngine != nullptr, ()); - m_drapeEngine->AddRoute(route.GetPoly(), dp::Color(110, 180, 240, 200)); - + vector turns; + turns::TurnsGeomT const & turnsGeom = route.GetTurnsGeometry(); + if (!turnsGeom.empty()) + { + turns.reserve(turnsGeom.size()); + for (size_t i = 0; i < turnsGeom.size(); i++) + turns.push_back(turnsGeom[i].m_mercatorDistance); + } + m_drapeEngine->AddRoute(route.GetPoly(), turns, dp::Color(110, 180, 240, 160)); // TODO(@kuznetsov): Maybe we need some of this stuff //track.SetName(route.GetName());