mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-20 13:23:59 +00:00
Organic Maps sources as of 02.04.2025 (fad26bbf22ac3da75e01e62aa01e5c8e11861005)
To expand with full Organic Maps and Maps.ME commits history run: git remote add om-historic [om-historic.git repo url] git fetch --tags om-historic git replace squashed-history historic-commits
This commit is contained in:
361
drape_frontend/path_text_handle.cpp
Normal file
361
drape_frontend/path_text_handle.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
#include "drape_frontend/path_text_handle.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace df
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
double const kValidPathSplineTurn = 15 * math::pi / 180;
|
||||
double const kCosTurn = cos(kValidPathSplineTurn);
|
||||
double const kSinTurn = sin(kValidPathSplineTurn);
|
||||
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<m2::PointD> & 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 / std::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<m2::PointD> const & roundedCorner, m2::SplineEx & 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::SplineEx & 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<int>(acos(dotProduct) / kValidPathSplineTurn);
|
||||
std::vector<m2::PointD> 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)
|
||||
{}
|
||||
|
||||
void PathTextContext::SetLayout(drape_ptr<PathTextLayout> && layout, double baseGtoPScale)
|
||||
{
|
||||
m_layout = std::move(layout);
|
||||
m_globalOffsets.clear();
|
||||
m_globalPivots.clear();
|
||||
PathTextLayout::CalculatePositions(m_globalSpline->GetLength(), baseGtoPScale,
|
||||
m_layout->GetPixelLength(), m_globalOffsets);
|
||||
m_globalPivots.reserve(m_globalOffsets.size());
|
||||
for (auto const offset : m_globalOffsets)
|
||||
m_globalPivots.push_back(m_globalSpline->GetPoint(offset).m_pos);
|
||||
}
|
||||
|
||||
ref_ptr<PathTextLayout> const PathTextContext::GetLayout() const
|
||||
{
|
||||
return make_ref(m_layout);
|
||||
}
|
||||
|
||||
void PathTextContext::BeforeUpdate()
|
||||
{
|
||||
m_updated = false;
|
||||
}
|
||||
|
||||
std::vector<double> const & PathTextContext::GetOffsets() const
|
||||
{
|
||||
return m_globalOffsets;
|
||||
}
|
||||
|
||||
bool PathTextContext::GetPivot(size_t textIndex, m2::PointD & pivot,
|
||||
m2::Spline::iterator & centerPointIter) const
|
||||
{
|
||||
if (textIndex >= m_centerGlobalPivots.size())
|
||||
return false;
|
||||
|
||||
pivot = m_centerGlobalPivots[textIndex];
|
||||
centerPointIter = m_centerPointIters[textIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathTextContext::Update(ScreenBase const & screen)
|
||||
{
|
||||
if (m_updated)
|
||||
return;
|
||||
m_updated = true;
|
||||
|
||||
m_pixel3dSplines.clear();
|
||||
m_centerPointIters.clear();
|
||||
m_centerGlobalPivots.clear();
|
||||
|
||||
m2::SplineEx pixelSpline(m_globalSpline->GetSize());
|
||||
for (auto pos : m_globalSpline->GetPath())
|
||||
{
|
||||
pos = screen.GtoP(pos);
|
||||
if (screen.IsReverseProjection3d(pos))
|
||||
{
|
||||
if (pixelSpline.GetSize() > 1)
|
||||
m_pixel3dSplines.push_back(std::move(pixelSpline));
|
||||
pixelSpline.Clear();
|
||||
continue;
|
||||
}
|
||||
AddPointAndRound(pixelSpline, screen.PtoP3d(pos));
|
||||
}
|
||||
|
||||
if (pixelSpline.GetSize() > 1)
|
||||
m_pixel3dSplines.emplace_back(std::move(pixelSpline));
|
||||
|
||||
if (m_pixel3dSplines.empty())
|
||||
return;
|
||||
|
||||
ASSERT_EQUAL(m_globalPivots.size(), m_globalOffsets.size(), ());
|
||||
for (auto const & pivot : m_globalPivots)
|
||||
{
|
||||
m2::PointD const pt2d = screen.GtoP(pivot);
|
||||
if (!screen.IsReverseProjection3d(pt2d))
|
||||
{
|
||||
auto projectionIter = GetProjectedPoint(screen.PtoP3d(pt2d));
|
||||
if (!projectionIter.IsAttached())
|
||||
continue;
|
||||
m_centerPointIters.push_back(projectionIter);
|
||||
m_centerGlobalPivots.push_back(pivot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m2::Spline::iterator PathTextContext::GetProjectedPoint(m2::PointD const & pt) const
|
||||
{
|
||||
double minDist = std::numeric_limits<double>::max();
|
||||
double resStep = 0.;
|
||||
m2::SplineEx const * resSpline = nullptr;
|
||||
|
||||
for (auto const & spline : m_pixel3dSplines)
|
||||
{
|
||||
auto const & path = spline.GetPath();
|
||||
if (path.size() < 2)
|
||||
continue;
|
||||
|
||||
auto const & lengths = spline.GetLengths();
|
||||
auto const & dirs = spline.GetDirections();
|
||||
|
||||
double step = 0;
|
||||
for (size_t i = 0, sz = path.size() - 1; i < sz; ++i)
|
||||
{
|
||||
double const segLength = lengths[i];
|
||||
m2::PointD const & segDir = dirs[i];
|
||||
|
||||
double const t = m2::DotProduct(segDir, pt - path[i]);
|
||||
|
||||
m2::PointD nearestPt;
|
||||
double nearestStep;
|
||||
if (t <= 0)
|
||||
{
|
||||
nearestPt = path[i];
|
||||
nearestStep = step;
|
||||
}
|
||||
else if (t >= segLength)
|
||||
{
|
||||
nearestPt = path[i + 1];
|
||||
nearestStep = step + segLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
nearestPt = path[i] + segDir * t;
|
||||
nearestStep = step + t;
|
||||
}
|
||||
|
||||
double const dist = pt.SquaredLength(nearestPt);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
resSpline = &spline;
|
||||
resStep = nearestStep;
|
||||
|
||||
if (minDist < kMwmPointAccuracy)
|
||||
goto end;
|
||||
}
|
||||
|
||||
step += segLength;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
return resSpline ? resSpline->GetPoint(resStep) : m2::Spline::iterator();
|
||||
}
|
||||
|
||||
|
||||
PathTextHandle::PathTextHandle(dp::OverlayID const & id, std::shared_ptr<PathTextContext> const & context,
|
||||
float depth, uint32_t textIndex, uint64_t priority,
|
||||
ref_ptr<dp::TextureManager> textureManager, int minVisibleScale, bool isBillboard)
|
||||
: TextHandle(id, context->GetLayout()->GetGlyphs(), dp::Center, priority,
|
||||
textureManager, minVisibleScale, isBillboard)
|
||||
, m_context(context)
|
||||
, m_textIndex(textIndex)
|
||||
, m_depth(depth)
|
||||
{
|
||||
m_buffer.resize(4 * m_context->GetLayout()->GetGlyphCount());
|
||||
}
|
||||
|
||||
bool PathTextHandle::Update(ScreenBase const & screen)
|
||||
{
|
||||
if (!df::TextHandle::Update(screen))
|
||||
return false;
|
||||
|
||||
m_context->Update(screen);
|
||||
|
||||
m2::Spline::iterator centerPointIter;
|
||||
if (!m_context->GetPivot(m_textIndex, m_globalPivot, centerPointIter))
|
||||
return false;
|
||||
|
||||
return m_context->GetLayout()->CacheDynamicGeometry(centerPointIter, m_depth, m_globalPivot, m_buffer);
|
||||
}
|
||||
|
||||
void PathTextHandle::BeforeUpdate()
|
||||
{
|
||||
m_context->BeforeUpdate();
|
||||
}
|
||||
|
||||
m2::RectD PathTextHandle::GetPixelRect(ScreenBase const & screen, bool perspective) const
|
||||
{
|
||||
m2::PointD const pixelPivot(screen.GtoP(m_globalPivot));
|
||||
|
||||
if (perspective)
|
||||
{
|
||||
if (IsBillboard())
|
||||
{
|
||||
m2::RectD r = GetPixelRect(screen, false);
|
||||
m2::PointD pixelPivotPerspective = screen.PtoP3d(pixelPivot);
|
||||
r.Offset(-pixelPivot);
|
||||
r.Offset(pixelPivotPerspective);
|
||||
|
||||
return r;
|
||||
}
|
||||
return GetPixelRectPerspective(screen);
|
||||
}
|
||||
|
||||
m2::RectD result;
|
||||
for (gpu::TextDynamicVertex const & v : m_buffer)
|
||||
result.Add(pixelPivot + m2::PointD(glsl::ToPoint(v.m_normal)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PathTextHandle::GetPixelShape(ScreenBase const & screen, bool perspective, Rects & rects) const
|
||||
{
|
||||
m2::PointD const pixelPivot(screen.GtoP(m_globalPivot));
|
||||
for (size_t quadIndex = 0; quadIndex < m_buffer.size(); quadIndex += 4)
|
||||
{
|
||||
m2::RectF r;
|
||||
r.Add(m2::PointF(pixelPivot) + glsl::ToPoint(m_buffer[quadIndex].m_normal));
|
||||
r.Add(m2::PointF(pixelPivot) + glsl::ToPoint(m_buffer[quadIndex + 1].m_normal));
|
||||
r.Add(m2::PointF(pixelPivot) + glsl::ToPoint(m_buffer[quadIndex + 2].m_normal));
|
||||
r.Add(m2::PointF(pixelPivot) + glsl::ToPoint(m_buffer[quadIndex + 3].m_normal));
|
||||
|
||||
if (perspective)
|
||||
{
|
||||
if (IsBillboard())
|
||||
{
|
||||
m2::PointD const pxPivotPerspective = screen.PtoP3d(pixelPivot);
|
||||
|
||||
r.Offset(m2::PointF(-pixelPivot));
|
||||
r.Offset(m2::PointF(pxPivotPerspective));
|
||||
}
|
||||
else
|
||||
{
|
||||
r = m2::RectF(GetPerspectiveRect(m2::RectD(r), screen));
|
||||
}
|
||||
}
|
||||
|
||||
bool const needAddRect = perspective ? !screen.IsReverseProjection3d(m2::PointD(r.Center())) : true;
|
||||
if (needAddRect)
|
||||
rects.emplace_back(std::move(r));
|
||||
}
|
||||
}
|
||||
|
||||
void PathTextHandle::GetAttributeMutation(ref_ptr<dp::AttributeBufferMutator> mutator) const
|
||||
{
|
||||
// We always update normals for visible text paths.
|
||||
SetForceUpdateNormals(IsVisible());
|
||||
TextHandle::GetAttributeMutation(mutator);
|
||||
}
|
||||
|
||||
bool PathTextHandle::Enable3dExtention() const
|
||||
{
|
||||
// Do not extend overlays for path texts.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PathTextHandle::HasLinearFeatureShape() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace df
|
||||
Reference in New Issue
Block a user