co-maps/libs/drape_frontend/gps_track_renderer.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

317 lines
10 KiB
C++
Raw Permalink Normal View History

2025-11-22 13:58:55 +01:00
#include "drape_frontend/gps_track_renderer.hpp"
#include "drape_frontend/color_constants.hpp"
#include "drape_frontend/map_shape.hpp"
#include "drape_frontend/shape_view_params.hpp"
#include "drape_frontend/visual_params.hpp"
#include "shaders/programs.hpp"
#include "drape/vertex_array_buffer.hpp"
#include "indexer/map_style_reader.hpp"
#include <algorithm>
#include <array>
namespace df
{
namespace
{
df::ColorConstant const kTrackUnknownDistanceColor = "TrackUnknownDistance";
df::ColorConstant const kTrackCarSpeedColor = "TrackCarSpeed";
df::ColorConstant const kTrackPlaneSpeedColor = "TrackPlaneSpeed";
df::ColorConstant const kTrackHumanSpeedColor = "TrackHumanSpeed";
int constexpr kMinVisibleZoomLevel = 5;
uint32_t constexpr kAveragePointsCount = 512;
// Radius of circles depending on zoom levels.
std::array<float, 20> constexpr kRadiusInPixel = {
// 1 2 3 4 5 6 7 8 9 10
0.8f, 0.8f, 1.5f, 2.5f, 2.5f, 2.5f, 2.5f, 2.5f, 2.5f, 2.5f,
// 11 12 13 14 15 16 17 18 19 20
2.5f, 2.5f, 2.5f, 2.5f, 3.0f, 4.0f, 4.5f, 4.5f, 5.0f, 5.5f};
double constexpr kHumanSpeed = 2.6; // meters per second
double constexpr kCarSpeed = 6.2; // meters per second
uint8_t constexpr kMinDayAlpha = 90;
uint8_t constexpr kMaxDayAlpha = 144;
uint8_t constexpr kMinNightAlpha = 50;
uint8_t constexpr kMaxNightAlpha = 102;
double constexpr kUnknownDistanceTime = 5 * 60; // seconds
double constexpr kDistanceScalar = 0.4;
#ifdef DEBUG
bool GpsPointsSortPredicate(GpsTrackPoint const & pt1, GpsTrackPoint const & pt2)
{
return pt1.m_id < pt2.m_id;
}
#endif
} // namespace
GpsTrackRenderer::GpsTrackRenderer(TRenderDataRequestFn const & dataRequestFn)
: m_dataRequestFn(dataRequestFn)
, m_needUpdate(false)
, m_waitForRenderData(false)
, m_radius(0.0f)
{
ASSERT(m_dataRequestFn != nullptr, ());
m_points.reserve(kAveragePointsCount);
m_handlesCache.reserve(8);
}
void GpsTrackRenderer::AddRenderData(ref_ptr<dp::GraphicsContext> context, ref_ptr<gpu::ProgramManager> mng,
drape_ptr<CirclesPackRenderData> && renderData)
{
drape_ptr<CirclesPackRenderData> data = std::move(renderData);
ref_ptr<dp::GpuProgram> program = mng->GetProgram(gpu::Program::CirclePoint);
program->Bind();
data->m_bucket->GetBuffer()->Build(context, program);
m_renderData.push_back(std::move(data));
m_waitForRenderData = false;
}
void GpsTrackRenderer::ClearRenderData()
{
m_renderData.clear();
m_handlesCache.clear();
m_waitForRenderData = false;
m_needUpdate = true;
}
void GpsTrackRenderer::UpdatePoints(std::vector<GpsTrackPoint> const & toAdd, std::vector<uint32_t> const & toRemove)
{
bool recreateSpline = false;
if (!toRemove.empty())
{
size_t const szBefore = m_points.size();
base::EraseIf(m_points, [&toRemove](GpsTrackPoint const & pt) { return base::IsExist(toRemove, pt.m_id); });
if (szBefore > m_points.size()) // if removed any
{
recreateSpline = true;
m_needUpdate = true;
}
}
if (!toAdd.empty())
{
ASSERT(is_sorted(toAdd.begin(), toAdd.end(), GpsPointsSortPredicate), ());
ASSERT(m_points.empty() || GpsPointsSortPredicate(m_points.back(), toAdd.front()), ());
m_points.insert(m_points.end(), toAdd.begin(), toAdd.end());
m_needUpdate = true;
}
if (recreateSpline) // Recreate Spline only if Remove (Clear) was invoked.
{
m_pointsSpline = m2::Spline(m_points.size());
for (auto const & p : m_points)
m_pointsSpline.AddPoint(p.m_point);
}
else // Simple append points otherwise.
{
for (auto const & p : toAdd)
m_pointsSpline.AddPoint(p.m_point);
}
}
size_t GpsTrackRenderer::GetAvailablePointsCount() const
{
size_t pointsCount = 0;
for (size_t i = 0; i < m_renderData.size(); i++)
pointsCount += m_renderData[i]->m_pointsCount;
return pointsCount;
}
dp::Color GpsTrackRenderer::CalculatePointColor(size_t pointIndex, m2::PointD const & curPoint, double lengthFromStart,
double fullLength) const
{
ASSERT_LESS(pointIndex, m_points.size(), ());
if (pointIndex + 1 == m_points.size())
return dp::Color::Transparent();
GpsTrackPoint const & start = m_points[pointIndex];
GpsTrackPoint const & end = m_points[pointIndex + 1];
double startAlpha = kMinDayAlpha;
double endAlpha = kMaxDayAlpha;
auto const style = GetStyleReader().GetCurrentStyle();
if (style == MapStyle::MapStyleDefaultDark)
{
startAlpha = kMinNightAlpha;
endAlpha = kMaxNightAlpha;
}
double const ta = math::Clamp(lengthFromStart / fullLength, 0.0, 1.0);
double const alpha = startAlpha * (1.0 - ta) + endAlpha * ta;
if ((end.m_timestamp - start.m_timestamp) > kUnknownDistanceTime)
{
dp::Color const color = df::GetColorConstant(df::kTrackUnknownDistanceColor);
return dp::Color(color.GetRed(), color.GetGreen(), color.GetBlue(), static_cast<uint8_t>(alpha));
}
double const length = (end.m_point - start.m_point).Length();
double const dist = (curPoint - start.m_point).Length();
double const td = math::Clamp(dist / length, 0.0, 1.0);
double const speed = std::max(start.m_speedMPS * (1.0 - td) + end.m_speedMPS * td, 0.0);
dp::Color const color = GetColorBySpeed(speed);
return dp::Color(color.GetRed(), color.GetGreen(), color.GetBlue(), static_cast<uint8_t>(alpha));
}
dp::Color GpsTrackRenderer::GetColorBySpeed(double speed) const
{
if (speed > kHumanSpeed && speed <= kCarSpeed)
return df::GetColorConstant(df::kTrackCarSpeedColor);
else if (speed > kCarSpeed)
return df::GetColorConstant(df::kTrackPlaneSpeedColor);
return df::GetColorConstant(df::kTrackHumanSpeedColor);
}
void GpsTrackRenderer::RenderTrack(ref_ptr<dp::GraphicsContext> context, ref_ptr<gpu::ProgramManager> mng,
ScreenBase const & screen, int zoomLevel, FrameValues const & frameValues)
{
if (zoomLevel < kMinVisibleZoomLevel)
return;
if (m_needUpdate)
{
// Skip rendering if there is no any point.
if (m_points.empty())
{
m_needUpdate = false;
return;
}
// Check if there are render data.
if (m_renderData.empty() && !m_waitForRenderData)
{
m_dataRequestFn(kAveragePointsCount);
m_waitForRenderData = true;
}
if (m_waitForRenderData)
return;
m_radius = CalculateRadius(screen, kRadiusInPixel);
double const currentScaleGtoP = 1.0 / screen.GetScale();
double const radiusMercator = m_radius / currentScaleGtoP;
double const diameterMercator = 2.0 * radiusMercator;
double const step = diameterMercator + kDistanceScalar * diameterMercator;
// Update points' positions and colors.
ASSERT(!m_renderData.empty(), ());
m_handlesCache.clear();
for (size_t i = 0; i < m_renderData.size(); i++)
{
auto & bucket = m_renderData[i]->m_bucket;
ASSERT_EQUAL(bucket->GetOverlayHandlesCount(), 1, ());
CirclesPackHandle * handle = static_cast<CirclesPackHandle *>(bucket->GetOverlayHandle(0).get());
handle->Clear();
m_handlesCache.push_back(std::make_pair(handle, 0));
}
m_pivot = screen.GlobalRect().Center();
size_t cacheIndex = 0;
if (m_points.size() == 1)
{
dp::Color const color = GetColorBySpeed(m_points.front().m_speedMPS);
m2::PointD const pt = MapShape::ConvertToLocal(m_points.front().m_point, m_pivot, kShapeCoordScalar);
m_handlesCache[cacheIndex].first->SetPoint(0, pt, m_radius, color);
m_handlesCache[cacheIndex].second++;
}
else
{
m2::Spline::iterator it;
it.Attach(m_pointsSpline);
auto const fullLength = it.GetFullLength();
double lengthFromStart = 0.0;
while (!it.BeginAgain())
{
m2::PointD const pt = it.m_pos;
m2::RectD pointRect(pt.x - radiusMercator, pt.y - radiusMercator, pt.x + radiusMercator, pt.y + radiusMercator);
if (screen.ClipRect().IsIntersect(pointRect))
{
dp::Color const color = CalculatePointColor(it.GetIndex(), pt, lengthFromStart, fullLength);
m2::PointD const convertedPt = MapShape::ConvertToLocal(pt, m_pivot, kShapeCoordScalar);
m_handlesCache[cacheIndex].first->SetPoint(m_handlesCache[cacheIndex].second, convertedPt, m_radius, color);
m_handlesCache[cacheIndex].second++;
if (m_handlesCache[cacheIndex].second >= m_handlesCache[cacheIndex].first->GetPointsCount())
cacheIndex++;
if (cacheIndex >= m_handlesCache.size())
{
m_dataRequestFn(kAveragePointsCount);
m_waitForRenderData = true;
return;
}
}
lengthFromStart += step;
it.Advance(step);
}
#ifdef GPS_TRACK_SHOW_RAW_POINTS
for (size_t i = 0; i < m_points.size(); i++)
{
m2::PointD const convertedPt = MapShape::ConvertToLocal(m_points[i].m_point, m_pivot, kShapeCoordScalar);
m_handlesCache[cacheIndex].first->SetPoint(m_handlesCache[cacheIndex].second, convertedPt, m_radius * 1.2,
dp::Color(0, 0, 255, 255));
m_handlesCache[cacheIndex].second++;
if (m_handlesCache[cacheIndex].second >= m_handlesCache[cacheIndex].first->GetPointsCount())
cacheIndex++;
if (cacheIndex >= m_handlesCache.size())
{
m_dataRequestFn(kAveragePointsCount);
m_waitForRenderData = true;
return;
}
}
#endif
}
m_needUpdate = false;
}
if (m_handlesCache.empty() || m_handlesCache.front().second == 0)
return;
ASSERT_LESS_OR_EQUAL(m_renderData.size(), m_handlesCache.size(), ());
// Render points.
gpu::MapProgramParams params;
frameValues.SetTo(params);
math::Matrix<float, 4, 4> mv = screen.GetModelView(m_pivot, kShapeCoordScalar);
params.m_modelView = glsl::make_mat4(mv.m_data);
ref_ptr<dp::GpuProgram> program = mng->GetProgram(gpu::Program::CirclePoint);
program->Bind();
ASSERT_GREATER(m_renderData.size(), 0, ());
dp::RenderState const & state = m_renderData.front()->m_state;
dp::ApplyState(context, program, state);
mng->GetParamsSetter()->Apply(context, program, params);
for (size_t i = 0; i < m_renderData.size(); i++)
if (m_handlesCache[i].second != 0)
m_renderData[i]->m_bucket->Render(context, state.GetDrawAsLine());
}
void GpsTrackRenderer::Update()
{
m_needUpdate = true;
}
void GpsTrackRenderer::Clear()
{
m_points.clear();
m_pointsSpline.Clear();
ClearRenderData();
}
} // namespace df