From e256fceb3c29b3878d8ea886bca5eb9c819739c9 Mon Sep 17 00:00:00 2001 From: Zeno Koller Date: Wed, 15 Apr 2026 08:40:14 +0200 Subject: [PATCH 1/4] Fix hit test for LineString labels --- .../symbol/Tiled2dMapVectorSymbolObject.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp index 447438ef4..1556cff37 100644 --- a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp +++ b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp @@ -1055,9 +1055,21 @@ std::optional> Tiled2dMapVectorSymbolO } } else { - if ((labelObject && labelObject->boundingBoxCircles.has_value() && CollisionUtil::checkCirclesCollision(*labelObject->boundingBoxCircles, clickHitCircle)) - || (iconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(iconBoundingBoxViewportAligned, clickHitCircle)) - || (stretchIconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(stretchIconBoundingBoxViewportAligned, clickHitCircle))) { + const bool hasLabelCircles = labelObject && labelObject->boundingBoxCircles.has_value(); + std::vector projectedLabelCircles; + if (hasLabelCircles) { + projectedLabelCircles.reserve(labelObject->boundingBoxCircles->size()); + for (const auto &circle : *labelObject->boundingBoxCircles) { + auto projectedCircle = CollisionUtil::getProjectedCircle(CollisionCircleD(circle.x, circle.y, circle.radius), collisionEnvironment); + if (projectedCircle) { + projectedLabelCircles.push_back(*projectedCircle); + } + } + } + const bool labelHit = !projectedLabelCircles.empty() && CollisionUtil::checkCirclesCollision(projectedLabelCircles, clickHitCircle); + const bool iconHit = iconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(iconBoundingBoxViewportAligned, clickHitCircle); + const bool stretchIconHit = stretchIconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(stretchIconBoundingBoxViewportAligned, clickHitCircle); + if (labelHit || iconHit || stretchIconHit) { return std::make_tuple(Coord(systemIdentifier, coordinate.x, coordinate.y, 0.0), featureContext->getFeatureInfo(stringTable)); } } From 14e6f50c62057565660639b13bf77ffc8e98e516 Mon Sep 17 00:00:00 2001 From: Zeno Koller Date: Wed, 15 Apr 2026 10:55:48 +0200 Subject: [PATCH 2/4] Optimize collision detection code --- shared/public/CollisionUtil.h | 10 ++++++++++ .../vector/symbol/Tiled2dMapVectorSymbolObject.cpp | 14 ++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/shared/public/CollisionUtil.h b/shared/public/CollisionUtil.h index 1811f2719..a6193a4de 100644 --- a/shared/public/CollisionUtil.h +++ b/shared/public/CollisionUtil.h @@ -260,4 +260,14 @@ class CollisionUtil { return RectD {originX, originY, std::abs(width), std::abs(height)}; } } + + static bool checkProjectedCirclesCollision(const std::vector &circles, const CircleD &circle2, CollisionEnvironment &env, int32_t addSpacing = 0) { + for (const auto &circle : circles) { + auto projectedCircle = getProjectedCircle(CollisionCircleD(circle.x, circle.y, circle.radius), env); + if (projectedCircle && checkCircleCollision(*projectedCircle, circle2, addSpacing)) { + return true; + } + } + return false; + } }; diff --git a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp index 1556cff37..29f6102f3 100644 --- a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp +++ b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp @@ -1055,18 +1055,8 @@ std::optional> Tiled2dMapVectorSymbolO } } else { - const bool hasLabelCircles = labelObject && labelObject->boundingBoxCircles.has_value(); - std::vector projectedLabelCircles; - if (hasLabelCircles) { - projectedLabelCircles.reserve(labelObject->boundingBoxCircles->size()); - for (const auto &circle : *labelObject->boundingBoxCircles) { - auto projectedCircle = CollisionUtil::getProjectedCircle(CollisionCircleD(circle.x, circle.y, circle.radius), collisionEnvironment); - if (projectedCircle) { - projectedLabelCircles.push_back(*projectedCircle); - } - } - } - const bool labelHit = !projectedLabelCircles.empty() && CollisionUtil::checkCirclesCollision(projectedLabelCircles, clickHitCircle); + const bool labelHit = labelObject && labelObject->boundingBoxCircles.has_value() && + CollisionUtil::checkProjectedCirclesCollision(*labelObject->boundingBoxCircles, clickHitCircle, collisionEnvironment); const bool iconHit = iconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(iconBoundingBoxViewportAligned, clickHitCircle); const bool stretchIconHit = stretchIconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(stretchIconBoundingBoxViewportAligned, clickHitCircle); if (labelHit || iconHit || stretchIconHit) { From adadbe3feb504f2a42ebb88c8ac9f5cb595e8361 Mon Sep 17 00:00:00 2001 From: Zeno Koller Date: Wed, 15 Apr 2026 10:58:54 +0200 Subject: [PATCH 3/4] Add early returns to label collision detection --- .../vector/symbol/Tiled2dMapVectorSymbolObject.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp index 29f6102f3..5a99646b8 100644 --- a/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp +++ b/shared/src/map/layers/tiled/vector/symbol/Tiled2dMapVectorSymbolObject.cpp @@ -1057,9 +1057,17 @@ std::optional> Tiled2dMapVectorSymbolO } else { const bool labelHit = labelObject && labelObject->boundingBoxCircles.has_value() && CollisionUtil::checkProjectedCirclesCollision(*labelObject->boundingBoxCircles, clickHitCircle, collisionEnvironment); + if (labelHit) { + return std::make_tuple(Coord(systemIdentifier, coordinate.x, coordinate.y, 0.0), featureContext->getFeatureInfo(stringTable)); + } + const bool iconHit = iconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(iconBoundingBoxViewportAligned, clickHitCircle); + if (iconHit) { + return std::make_tuple(Coord(systemIdentifier, coordinate.x, coordinate.y, 0.0), featureContext->getFeatureInfo(stringTable)); + } + const bool stretchIconHit = stretchIconBoundingBoxViewportAligned.width != 0 && CollisionUtil::checkRectCircleCollision(stretchIconBoundingBoxViewportAligned, clickHitCircle); - if (labelHit || iconHit || stretchIconHit) { + if (stretchIconHit) { return std::make_tuple(Coord(systemIdentifier, coordinate.x, coordinate.y, 0.0), featureContext->getFeatureInfo(stringTable)); } } From 240c4f47f7020d280e1005fc1bf5f5dba027bec2 Mon Sep 17 00:00:00 2001 From: Zeno Koller Date: Wed, 15 Apr 2026 11:43:23 +0200 Subject: [PATCH 4/4] Add overloaded getProjectedCircle with unpacked args and make const --- shared/public/CollisionUtil.h | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/shared/public/CollisionUtil.h b/shared/public/CollisionUtil.h index a6193a4de..68cc5ee45 100644 --- a/shared/public/CollisionUtil.h +++ b/shared/public/CollisionUtil.h @@ -106,7 +106,7 @@ class CollisionUtil { {} }; - static std::optional getProjectedCircle(const CollisionCircleD &circle, CollisionEnvironment &env) { + static std::optional getProjectedCircle(double x, double y, double radius, CollisionEnvironment &env) { if (env.is3d) { // Earth center env.temp2.x = 0 - env.origin.x; @@ -119,8 +119,8 @@ class CollisionUtil { double earthCenterZ = env.temp1.z / env.temp1.w; double sinX, cosX, sinY, cosY; - lut::sincos(circle.y, sinY, cosY); - lut::sincos(circle.x, sinX, cosX); + lut::sincos(y, sinY, cosY); + lut::sincos(x, sinX, cosX); env.temp2.x = 1.0 * sinY * cosX - env.origin.x; env.temp2.y = 1.0 * cosY - env.origin.y; @@ -143,10 +143,10 @@ class CollisionUtil { double originX = env.temp1.x * env.halfWidth + env.halfWidth; double originY = env.temp1.y * env.halfHeight + env.halfHeight; - return CircleD {originX, originY, circle.radius}; + return CircleD {originX, originY, radius}; } else { - env.temp2.x = circle.x - env.origin.x; - env.temp2.y = circle.y - env.origin.y; + env.temp2.x = x - env.origin.x; + env.temp2.y = y - env.origin.y; env.temp2.z = 0.0; env.temp2.w = 1.0; @@ -155,8 +155,8 @@ class CollisionUtil { double originX = (env.temp1.x / env.temp1.w) * env.halfWidth + env.halfWidth; double originY = (env.temp1.y / env.temp1.w) * env.halfHeight + env.halfHeight; - env.temp2.x = circle.radius; - env.temp2.y = circle.radius; + env.temp2.x = radius; + env.temp2.y = radius; env.temp2.z = 0.0; env.temp2.w = 0.0; @@ -169,6 +169,10 @@ class CollisionUtil { } } + static std::optional getProjectedCircle(const CollisionCircleD &circle, CollisionEnvironment &env) { + return getProjectedCircle(circle.x, circle.y, circle.radius, env); + } + static std::optional getProjectedRectangle(const CollisionRectD &rectangle, CollisionEnvironment &env) { if (env.is3d) { // Earth center @@ -263,7 +267,7 @@ class CollisionUtil { static bool checkProjectedCirclesCollision(const std::vector &circles, const CircleD &circle2, CollisionEnvironment &env, int32_t addSpacing = 0) { for (const auto &circle : circles) { - auto projectedCircle = getProjectedCircle(CollisionCircleD(circle.x, circle.y, circle.radius), env); + const auto &projectedCircle = getProjectedCircle(circle.x, circle.y, circle.radius, env); if (projectedCircle && checkCircleCollision(*projectedCircle, circle2, addSpacing)) { return true; }