File indexing completed on 2024-11-17 04:17:24
0001 /* 0002 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "hitdetector.h" 0008 0009 #include "../scene/scenegraph.h" 0010 #include "../scene/scenegraphitem.h" 0011 #include "../scene/scenegeometry_p.h" 0012 #include "../scene/view.h" 0013 0014 #include <QBrush> 0015 #include <QFontMetrics> 0016 0017 using namespace KOSMIndoorMap; 0018 0019 const SceneGraphItem* HitDetector::itemAt(QPointF pos, const SceneGraph& sg, const View* view) const 0020 { 0021 auto items = itemsAt(pos, sg, view); 0022 if (items.empty()) { 0023 return nullptr; 0024 } 0025 if (items.size() == 1) { 0026 return items[0]; 0027 } 0028 0029 // multiple candidates 0030 // (1) top element is non-transparent, use that: 0031 const auto top = items.back(); 0032 qDebug() << top->element.url() << itemFillAlpha(top); 0033 if (itemFillAlpha(top) >= 0.5f) { 0034 return top; 0035 } 0036 0037 // (2) in presence of transparency, use the smallest item at this position 0038 std::sort(items.begin(), items.end(), [view](auto lhs, auto rhs) { 0039 const auto lhsBbox = lhs->payload->boundingRect(view); 0040 const auto rhsBbox = rhs->payload->boundingRect(view); 0041 return (lhsBbox.width() * lhsBbox.height()) < (rhsBbox.width() * rhsBbox.height()); 0042 }); 0043 return items.front(); 0044 } 0045 0046 std::vector<const SceneGraphItem*> HitDetector::itemsAt(QPointF pos, const SceneGraph &sg, const View *view) const 0047 { 0048 std::vector<const SceneGraphItem*> result; 0049 for (const auto &item : sg.items()) { 0050 if (item.payload->renderPhases() == SceneGraphItemPayload::NoPhase || !item.payload->boundingRect(view).contains(view->mapScreenToScene(pos))) { 0051 continue; 0052 } 0053 if (!itemContainsPoint(item, pos, view)) { 0054 continue; 0055 } 0056 result.push_back(&item); 0057 } 0058 0059 return result; 0060 } 0061 0062 bool HitDetector::itemContainsPoint(const SceneGraphItem &item, QPointF screenPos, const View *view) const 0063 { 0064 if (const auto i = dynamic_cast<PolygonItem*>(item.payload.get())) { 0065 return itemContainsPoint(i, view->mapScreenToScene(screenPos)); 0066 } 0067 if (const auto i = dynamic_cast<MultiPolygonItem*>(item.payload.get())) { 0068 return itemContainsPoint(i, view->mapScreenToScene(screenPos)); 0069 } 0070 if (const auto i = dynamic_cast<PolylineItem*>(item.payload.get())) { 0071 return itemContainsPoint(i, view->mapScreenToScene(screenPos), view); 0072 } 0073 if (const auto i = dynamic_cast<LabelItem*>(item.payload.get())) { 0074 return itemContainsPoint(i, screenPos, view); 0075 } 0076 0077 return true; 0078 } 0079 0080 bool HitDetector::itemContainsPoint(const MultiPolygonItem *item, QPointF scenePos) const 0081 { 0082 return item->path.contains(scenePos); 0083 } 0084 0085 bool HitDetector::itemContainsPoint(const PolygonItem *item, QPointF scenePos) const 0086 { 0087 return item->polygon.containsPoint(scenePos, Qt::OddEvenFill); 0088 } 0089 0090 bool HitDetector::itemContainsPoint(const PolylineItem *item, QPointF scenePos, const View *view) const 0091 { 0092 if (item->path.size() < 2) { 0093 return false; 0094 } 0095 0096 const auto lineWidth = view->mapMetersToScene(item->pen.widthF()) 0097 + view->mapScreenDistanceToSceneDistance(item->casingPen.widthF()); 0098 0099 double dist = std::numeric_limits<double>::max(); 0100 // TODO do we need to wrap around here for closed lines? 0101 for (auto it = std::next(item->path.begin()); it != item->path.end(); ++it) { 0102 QLineF line(*std::prev(it), *it); 0103 dist = std::min(dist, SceneGeometry::distanceToLine(line, scenePos)); 0104 } 0105 0106 return dist <= lineWidth; 0107 } 0108 0109 bool HitDetector::itemContainsPoint(const LabelItem *item, QPointF screenPos, const View *view) const 0110 { 0111 // TODO item->angle != 0 0112 if (item->iconHidden) { 0113 return false; 0114 } 0115 0116 if (item->textHidden) { 0117 const auto hitBox = item->iconHitBox(view); 0118 return hitBox.contains(screenPos); 0119 } 0120 0121 const auto hitBox = item->shieldHitBox(view); 0122 return hitBox.contains(screenPos); 0123 } 0124 0125 float HitDetector::itemFillAlpha(const SceneGraphItem *item) const 0126 { 0127 if (const auto i = dynamic_cast<PolygonItem*>(item->payload.get())) { 0128 return std::max(i->fillBrush.color().alphaF(), i->textureBrush.color().alphaF()); 0129 } 0130 if (const auto i = dynamic_cast<MultiPolygonItem*>(item->payload.get())) { 0131 return std::max(i->fillBrush.color().alphaF(), i->textureBrush.color().alphaF()); 0132 } 0133 return 1.0f; 0134 }