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 }