diff --git a/drape_frontend/route_renderer.cpp b/drape_frontend/route_renderer.cpp index 8b7d1ad3aa..3d4c747cce 100644 --- a/drape_frontend/route_renderer.cpp +++ b/drape_frontend/route_renderer.cpp @@ -479,8 +479,10 @@ void RouteRenderer::RenderSubroute(ref_ptr context, ref_ptr params.m_fakeColor = glsl::ToVec4(df::GetColorConstant(kRouteFakeColor)); params.m_fakeOutlineColor = glsl::ToVec4(df::GetColorConstant(kRouteFakeOutlineColor)); - ref_ptr prg = mng->GetProgram(style.m_pattern.m_isDashed ? - gpu::Program::RouteDash : gpu::Program::Route); + auto routeType = subrouteData->m_subroute->m_routeType; + ref_ptr prg = mng->GetProgram( + (routeType == df::RouteType::Pedestrian || routeType == df::RouteType::Bicycle)? gpu::Program::RouteDifficulty: + style.m_pattern.m_isDashed ? gpu::Program::RouteDash : gpu::Program::Route); prg->Bind(); dp::ApplyState(context, prg, state); mng->GetParamsSetter()->Apply(context, prg, params); diff --git a/drape_frontend/route_shape.cpp b/drape_frontend/route_shape.cpp index 4c61d20e22..5959ffc5d3 100644 --- a/drape_frontend/route_shape.cpp +++ b/drape_frontend/route_shape.cpp @@ -576,6 +576,24 @@ drape_ptr RouteShape::CacheRoute(ref_ptr segmentsColors.emplace_back(color.GetRedF(), color.GetGreenF(), color.GetBlueF(), alpha); } } + else if (!subroute->m_slopes.empty()) + { + segmentsColors.reserve(endIndex - startIndex); + for (size_t i = startIndex; i < endIndex; ++i) + { + auto slope = abs(subroute->m_slopes[i]); + dp::Color color; + if (slope < 0.1) + color = dp::Color::Transparent(); + else if (slope < 0.2) + color = dp::Color(0xDF, 0xE2, 0x00, 0xFF); + else if (slope < 0.3) + color = dp::Color(0xD5, 0x5F, 0x00, 0xFF); + else + color = dp::Color(0xD5, 0x1F, 0x00, 0xFF); + segmentsColors.emplace_back(color.GetRedF(), color.GetGreenF(), color.GetBlueF(), color.GetAlphaF()); + } + } auto subrouteData = make_unique_dp(); subrouteData->m_subrouteId = subrouteId; @@ -592,7 +610,8 @@ drape_ptr RouteShape::CacheRoute(ref_ptr static_cast(subroute->m_baseDepthIndex * rs::kDepthPerSubroute), geometryBufferData); - auto state = CreateRenderState(subroute->m_style[styleIndex].m_pattern.m_isDashed ? + auto state = CreateRenderState(!subroute->m_slopes.empty()? gpu::Program::RouteDifficulty: + subroute->m_style[styleIndex].m_pattern.m_isDashed ? gpu::Program::RouteDash : gpu::Program::Route, DepthLayer::GeometryLayer); state.SetColorTexture(textures->GetSymbolsTexture()); diff --git a/drape_frontend/route_shape.hpp b/drape_frontend/route_shape.hpp index 0380d5441d..e63bf7ebce 100644 --- a/drape_frontend/route_shape.hpp +++ b/drape_frontend/route_shape.hpp @@ -142,6 +142,7 @@ struct Subroute m2::PolylineD m_polyline; std::vector m_turns; std::vector m_traffic; + std::vector m_slopes; double m_baseDistance = 0.0; double m_baseDepthIndex = 0.0; float m_maxPixelWidth = -1.0f; diff --git a/map/chart_generator.cpp b/map/chart_generator.cpp index 71cc1b7b1a..ac23da23d0 100644 --- a/map/chart_generator.cpp +++ b/map/chart_generator.cpp @@ -112,7 +112,8 @@ void ReflectChartData(vector & chartData) bool NormalizeChartData(vector const & distanceDataM, geometry::Altitudes const & altitudeDataM, size_t resultPointCount, - vector & uniformAltitudeDataM) + vector & uniformAltitudeDataM, + vector & uniformSlopeDataM) { double constexpr kEpsilon = 1e-6; @@ -134,11 +135,13 @@ bool NormalizeChartData(vector const & distanceDataM, return true; } - auto const calculateAltitude = [&](double distFormStartM) { + using TAltitudeSlope = pair; + + auto const calculateAltitudeSlope = [&](double distFormStartM) { if (distFormStartM <= distanceDataM.front()) - return static_cast(altitudeDataM.front()); + return TAltitudeSlope(static_cast(altitudeDataM.front()), 0); if (distFormStartM >= distanceDataM.back()) - return static_cast(altitudeDataM.back()); + return TAltitudeSlope(static_cast(altitudeDataM.back()), 0); auto const lowerIt = lower_bound(distanceDataM.cbegin(), distanceDataM.cend(), distFormStartM); size_t const nextPointIdx = distance(distanceDataM.cbegin(), lowerIt); @@ -146,20 +149,24 @@ bool NormalizeChartData(vector const & distanceDataM, size_t const prevPointIdx = nextPointIdx - 1; if (base::AlmostEqualAbs(distanceDataM[prevPointIdx], distanceDataM[nextPointIdx], kEpsilon)) - return static_cast(altitudeDataM[prevPointIdx]); + return TAltitudeSlope(static_cast(altitudeDataM[prevPointIdx]), 0); double const k = (altitudeDataM[nextPointIdx] - altitudeDataM[prevPointIdx]) / (distanceDataM[nextPointIdx] - distanceDataM[prevPointIdx]); - return static_cast(altitudeDataM[prevPointIdx]) + - k * (distFormStartM - distanceDataM[prevPointIdx]); + return TAltitudeSlope(static_cast(altitudeDataM[prevPointIdx]) + + k * (distFormStartM - distanceDataM[prevPointIdx]), k); }; double const routeLenM = distanceDataM.back(); uniformAltitudeDataM.resize(resultPointCount); + uniformSlopeDataM.resize(resultPointCount); double const stepLenM = resultPointCount <= 1 ? 0.0 : routeLenM / (resultPointCount - 1); - for (size_t i = 0; i < resultPointCount; ++i) - uniformAltitudeDataM[i] = calculateAltitude(static_cast(i) * stepLenM); + for (size_t i = 0; i < resultPointCount; ++i) { + auto altitudeSlope = calculateAltitudeSlope(static_cast(i) * stepLenM); + uniformAltitudeDataM[i] = altitudeSlope.first; + uniformSlopeDataM[i] = altitudeSlope.second; + } return true; } @@ -212,6 +219,7 @@ bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl, } bool GenerateChartByPoints(uint32_t width, uint32_t height, vector const & geometry, + vector const & slopes, MapStyle mapStyle, vector & frameBuffer) { frameBuffer.clear(); @@ -221,7 +229,7 @@ bool GenerateChartByPoints(uint32_t width, uint32_t height, vector c agg::rgba8 const kBackgroundColor = agg::rgba8(255, 255, 255, 0); agg::rgba8 const kLineColor = GetLineColor(mapStyle); agg::rgba8 const kCurveColor = GetCurveColor(mapStyle); - double constexpr kLineWidthPxl = 2.0; + double constexpr kLineWidthPxl = 3.0; using TBlender = BlendAdaptor; using TPixelFormat = agg::pixfmt_custom_blend_rgba; @@ -264,14 +272,30 @@ bool GenerateChartByPoints(uint32_t width, uint32_t height, vector c agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, kCurveColor); // Chart line. - TPath path_adaptor(geometry, false); - TStroke stroke(path_adaptor); - stroke.width(kLineWidthPxl); - stroke.line_cap(agg::round_cap); - stroke.line_join(agg::round_join); + for (size_t i = 1; i < geometry.size(); ++i) { + auto x = geometry[i].x; + auto y = geometry[i].y; + auto slope = abs(slopes[i]); + + agg::path_storage segment; + segment.move_to(x, y); + segment.line_to(geometry[i-1].x, geometry[i-1].y); + agg::conv_stroke strokeSegment(segment); + strokeSegment.width(kLineWidthPxl); + rasterizer.add_path(strokeSegment); + + agg::rgba8 color; + if (slope < 0.1) + color = kLineColor; + else if (slope < 0.2) + color = agg::rgba8(0xDF, 0xE2, 0x00, 0xFF); + else if (slope < 0.3) + color = agg::rgba8(0xD5, 0x5F, 0x00, 0xFF); + else + color = agg::rgba8(0xD5, 0x1F, 0x00, 0xFF); + agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, color); + } - rasterizer.add_path(stroke); - agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, kLineColor); return true; } @@ -287,7 +311,8 @@ bool GenerateChart(uint32_t width, uint32_t height, vector const & dista } vector uniformAltitudeDataM; - if (!NormalizeChartData(distanceDataM, altitudeDataM, width, uniformAltitudeDataM)) + vector uniformSlopeDataM; + if (!NormalizeChartData(distanceDataM, altitudeDataM, width, uniformAltitudeDataM, uniformSlopeDataM)) return false; vector yAxisDataPxl; @@ -305,6 +330,6 @@ bool GenerateChart(uint32_t width, uint32_t height, vector const & dista geometry[i] = m2::PointD(i * oneSegLenPix, yAxisDataPxl[i]); } - return GenerateChartByPoints(width, height, geometry, mapStyle, frameBuffer); + return GenerateChartByPoints(width, height, geometry, uniformSlopeDataM, mapStyle, frameBuffer); } } // namespace maps diff --git a/map/chart_generator.hpp b/map/chart_generator.hpp index 946546cca4..966b2b2232 100644 --- a/map/chart_generator.hpp +++ b/map/chart_generator.hpp @@ -21,7 +21,8 @@ void ReflectChartData(std::vector & chartData); /// This method is used to generalize and evenly distribute points of the chart. bool NormalizeChartData(std::vector const & distanceDataM, geometry::Altitudes const & altitudeDataM, size_t resultPointCount, - std::vector & uniformAltitudeDataM); + std::vector & uniformAltitudeDataM, + std::vector & uniformSlopeDataM); /// \brief fills |yAxisDataPxl|. |yAxisDataPxl| is formed to pevent displaying /// big waves on the chart in case of small deviation in absolute values in |yAxisData|. @@ -42,7 +43,9 @@ bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl, /// \param frameBuffer is a vector for a result image. It's resized in this method. /// It's filled with RGBA(8888) image date. bool GenerateChartByPoints(uint32_t width, uint32_t height, - std::vector const & geometry, MapStyle mapStyle, + std::vector const & geometry, + std::vector const & slopes, + MapStyle mapStyle, std::vector & frameBuffer); bool GenerateChart(uint32_t width, uint32_t height, std::vector const & distanceDataM, diff --git a/map/map_tests/chart_generator_tests.cpp b/map/map_tests/chart_generator_tests.cpp index 36a302e915..2da6dff58d 100644 --- a/map/map_tests/chart_generator_tests.cpp +++ b/map/map_tests/chart_generator_tests.cpp @@ -81,7 +81,8 @@ UNIT_TEST(NormalizeChartData_SmokeTest) geometry::Altitudes const altitudeDataM = {0, 0, 0}; vector uniformAltitudeDataM; - TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM), + vector uniformSlopeDataM; + TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM), ()); vector const expectedUniformAltitudeDataM = {0.0, 0.0}; @@ -94,7 +95,8 @@ UNIT_TEST(NormalizeChartData_NoResultPointTest) geometry::Altitudes const altitudeDataM = {0, 0, 0}; vector uniformAltitudeDataM; - TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 0 /* resultPointCount */, uniformAltitudeDataM), + vector uniformSlopeDataM; + TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 0 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM), ()); TEST(uniformAltitudeDataM.empty(), ()); @@ -106,7 +108,8 @@ UNIT_TEST(NormalizeChartData_NoPointTest) geometry::Altitudes const altitudeDataM = {}; vector uniformAltitudeDataM; - TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM), + vector uniformSlopeDataM; + TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM), ()); TEST(uniformAltitudeDataM.empty(), ()); @@ -118,7 +121,8 @@ UNIT_TEST(NormalizeChartData_Test) geometry::Altitudes const altitudeDataM = {-9, 0, 9, 18}; vector uniformAltitudeDataM; - TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 10 /* resultPointCount */, uniformAltitudeDataM), + vector uniformSlopeDataM; + TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 10 /* resultPointCount */, uniformAltitudeDataM, uniformSlopeDataM), ()); vector const expectedUniformAltitudeDataM = {-9.0, -6.0, -3.0, 0.0, 3.0, @@ -158,11 +162,12 @@ UNIT_TEST(GenerateYAxisChartData_Test) UNIT_TEST(GenerateChartByPoints_NoGeometryTest) { vector const geometry = {}; + vector const slopes = {}; size_t constexpr width = 100; size_t constexpr height = 40; vector frameBuffer; - TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); + TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */, 0 /* expectedA */); } @@ -170,11 +175,12 @@ UNIT_TEST(GenerateChartByPoints_NoGeometryTest) UNIT_TEST(GenerateChartByPoints_OnePointTest) { vector const geometry = {{20.0, 20.0}}; + vector const slopes = {0.0}; size_t constexpr width = 40; size_t constexpr height = 40; vector frameBuffer; - TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); + TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */, 0 /* expectedA */); } @@ -182,12 +188,13 @@ UNIT_TEST(GenerateChartByPoints_OnePointTest) UNIT_TEST(GenerateChartByPoints_Test) { vector const geometry = {{0.0, 0.0}, {10.0, 10.0}}; + vector const slopes = {1.0, 0.0}; size_t constexpr width = 40; size_t constexpr height = 40; vector frameBuffer; - TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); + TEST(maps::GenerateChartByPoints(width, height, geometry, slopes, MapStyleDefaultLight /* mapStyle */, frameBuffer), ()); TEST_EQUAL(frameBuffer.size(), width * height * kAltitudeChartBPP, ()); TEST(IsColor(frameBuffer, 0 /* startColorIdx */, 30 /* expectedR */, 150 /* expectedG */, diff --git a/map/routing_manager.cpp b/map/routing_manager.cpp index fab7dd21e1..8242f223df 100644 --- a/map/routing_manager.cpp +++ b/map/routing_manager.cpp @@ -81,6 +81,29 @@ void FillTrafficForRendering(vector const & segments, traffic.push_back(s.GetTraffic()); } +void FillSlopesForRendering(Route::SubrouteAttrs const & subrouteAttrs, + vector const & segments, + vector & slopes) +{ + slopes.clear(); + slopes.reserve(segments.size()); + auto prevAltitude = subrouteAttrs.GetStart().GetAltitude(); + auto prevPoint = subrouteAttrs.GetStart().GetPoint(); + + for (auto const & s: segments) + { + auto altitude = s.GetJunction().GetAltitude(); + auto point = s.GetJunction().GetPoint(); + auto height = altitude - prevAltitude; + auto length = mercator::DistanceOnEarth(prevPoint, point); + float slope = (length <= 1 || abs(height) <= 1)? 0.0f: height / length; + slopes.push_back(slope); + + prevAltitude = altitude; + prevPoint = point; + } +} + RouteMarkData GetLastPassedPoint(BookmarkManager * bmManager, vector const & points) { ASSERT_GREATER_OR_EQUAL(points.size(), 2, ()); @@ -710,13 +733,15 @@ bool RoutingManager::InsertRoute(Route const & route) case RouterType::Pedestrian: { subroute->m_routeType = df::RouteType::Pedestrian; - subroute->AddStyle(df::SubrouteStyle(df::kRoutePedestrian, df::RoutePattern(4.0, 2.0))); + subroute->AddStyle(df::SubrouteStyle(df::kRoutePedestrian)); + FillSlopesForRendering(route.GetSubrouteAttrs(subrouteIndex), segments, subroute->m_slopes); break; } case RouterType::Bicycle: { subroute->m_routeType = df::RouteType::Bicycle; - subroute->AddStyle(df::SubrouteStyle(df::kRouteBicycle, df::RoutePattern(8.0, 2.0))); + subroute->AddStyle(df::SubrouteStyle(df::kRouteBicycle)); + FillSlopesForRendering(route.GetSubrouteAttrs(subrouteIndex), segments, subroute->m_slopes); FillTurnsDistancesForRendering(segments, subroute->m_baseDistance, subroute->m_turns); break; } diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt index 22fd0ef9b5..020b3dc804 100644 --- a/shaders/CMakeLists.txt +++ b/shaders/CMakeLists.txt @@ -39,6 +39,7 @@ set(shader_files GL/route_arrow.fsh.glsl GL/route_arrow.vsh.glsl GL/route_dash.fsh.glsl + GL/route_difficulty.fsh.glsl GL/route_marker.fsh.glsl GL/route_marker.vsh.glsl GL/ruler.vsh.glsl diff --git a/shaders/GL/route_difficulty.fsh.glsl b/shaders/GL/route_difficulty.fsh.glsl new file mode 100644 index 0000000000..cbc280c879 --- /dev/null +++ b/shaders/GL/route_difficulty.fsh.glsl @@ -0,0 +1,34 @@ +// Warning! Beware to use this shader. "discard" command may significally reduce performance. +// Unfortunately some CG algorithms cannot be implemented on OpenGL ES 2.0 without discarding +// fragments from depth buffer. + +varying vec3 v_length; +varying vec4 v_color; + +#ifdef SAMSUNG_GOOGLE_NEXUS +uniform sampler2D u_colorTex; +#endif + +uniform vec4 u_color; +uniform vec2 u_pattern; +uniform vec4 u_maskColor; + +uniform vec2 u_fakeBorders; +uniform vec4 u_fakeColor; + +const float kAntialiasingThreshold = 0.92; + +void main() +{ + if (v_length.x < v_length.z) + discard; + + vec2 coefs = step(v_length.xx, u_fakeBorders); + coefs.y = 1.0 - coefs.y; + vec4 mainColor = mix(u_color, u_fakeColor, coefs.x); + mainColor = mix(mainColor, u_fakeColor, coefs.y); + + float a = (1.0 - smoothstep(kAntialiasingThreshold, 1.0, abs(v_length.y))) * 0.8; + vec4 color = vec4(mix(mainColor.rgb, v_color.rgb, v_color.a), a); + gl_FragColor = samsungGoogleNexusWorkaround(color); +} diff --git a/shaders/GL/shader_index.txt b/shaders/GL/shader_index.txt index 6103d27589..1ce3dfde75 100644 --- a/shaders/GL/shader_index.txt +++ b/shaders/GL/shader_index.txt @@ -27,6 +27,7 @@ Transit transit.vsh.glsl transit.fsh.glsl TransitMarker transit_marker.vsh.glsl transit_marker.fsh.glsl Route route.vsh.glsl route.fsh.glsl RouteDash route.vsh.glsl route_dash.fsh.glsl +RouteDifficulty route.vsh.glsl route_difficulty.fsh.glsl RouteArrow route_arrow.vsh.glsl route_arrow.fsh.glsl RouteMarker route_marker.vsh.glsl route_marker.fsh.glsl CirclePoint circle_point.vsh.glsl circle_point.fsh.glsl diff --git a/shaders/Metal/route.metal b/shaders/Metal/route.metal index 39e0a0f412..05ca1235be 100644 --- a/shaders/Metal/route.metal +++ b/shaders/Metal/route.metal @@ -118,6 +118,25 @@ fragment float4 fsRouteDash(const RouteFragment_T in [[stage_in]], return color; } +fragment float4 fsRouteDifficulty(const RouteFragment_T in [[stage_in]], + constant Uniforms_T & uniforms [[buffer(1)]]) +{ + if (in.lengthParams.x < in.lengthParams.z) + discard_fragment(); + + constexpr float kAntialiasingThreshold = 0.92; + + float2 fb = uniforms.u_fakeBorders; + float2 coefs = step(in.lengthParams.xx, fb); + coefs.y = 1.0 - coefs.y; + float4 mainColor = mix(uniforms.u_color, uniforms.u_fakeColor, coefs.x); + mainColor = mix(mainColor, uniforms.u_fakeColor, coefs.y); + + float a = (1.0 - smoothstep(kAntialiasingThreshold, 1.0, abs(v_length.y))) * 0.8; + float4 color = float4(mix(mainColor.rgb, in.color.rgb, in.color.a), a); + return color; +} + // RouteArrow typedef struct diff --git a/shaders/metal_program_pool.mm b/shaders/metal_program_pool.mm index 11dca8c56f..35387c49ac 100644 --- a/shaders/metal_program_pool.mm +++ b/shaders/metal_program_pool.mm @@ -76,6 +76,7 @@ std::array(Program::ProgramsCount)> const kMeta ProgramInfo("vsTransitMarker", "fsTransitMarker", {{0, 2}}), // TransitMarker ProgramInfo("vsRoute", "fsRoute", {{0, 3}}), // Route ProgramInfo("vsRoute", "fsRouteDash", {{0, 3}}), // RouteDash + ProgramInfo("vsRoute", "fsRouteDifficulty", {{0, 3}}), // RouteDifficulty ProgramInfo("vsRouteArrow", "fsRouteArrow", {{0, 2}}), // RouteArrow ProgramInfo("vsRouteMarker", "fsRouteMarker", {{0, 2}}), // RouteMarker ProgramInfo("vsCirclePoint", "fsCirclePoint", {{0, 0}, {1, 2}}), // CirclePoint diff --git a/shaders/program_params.hpp b/shaders/program_params.hpp index c2bea7925a..26e7359abf 100644 --- a/shaders/program_params.hpp +++ b/shaders/program_params.hpp @@ -111,6 +111,7 @@ struct ALIGNMENT RouteProgramParams BIND_PROGRAMS(RouteProgramParams, Program::Route, Program::RouteDash, + Program::RouteDifficulty, Program::RouteArrow, Program::RouteMarker) }; diff --git a/shaders/programs.hpp b/shaders/programs.hpp index ca74e7a067..9164f2fd81 100644 --- a/shaders/programs.hpp +++ b/shaders/programs.hpp @@ -39,6 +39,7 @@ enum class Program TransitMarker, Route, RouteDash, + RouteDifficulty, RouteArrow, RouteMarker, CirclePoint, @@ -102,6 +103,7 @@ inline std::string DebugPrint(Program p) case Program::TransitMarker: return "TransitMarker"; case Program::Route: return "Route"; case Program::RouteDash: return "RouteDash"; + case Program::RouteDifficulty: return "RouteDifficulty"; case Program::RouteArrow: return "RouteArrow"; case Program::RouteMarker: return "RouteMarker"; case Program::CirclePoint: return "CirclePoint";