aboutsummaryrefslogtreecommitdiffstats
path: root/plugin/spatialnavigation360.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/spatialnavigation360.cpp')
-rw-r--r--plugin/spatialnavigation360.cpp210
1 files changed, 210 insertions, 0 deletions
diff --git a/plugin/spatialnavigation360.cpp b/plugin/spatialnavigation360.cpp
new file mode 100644
index 0000000..9898b07
--- /dev/null
+++ b/plugin/spatialnavigation360.cpp
@@ -0,0 +1,210 @@
+#include "spatialnavigation360.h"
+#include <QQuickItem>
+#include "cursornavigationattached.h"
+#include <algorithm>
+#include <QtMath>
+
+SpatialNavigation360::SpatialNavigation360()
+{
+
+}
+
+//test if point is contained in at least one of the given quadrants
+bool isPointIncluded(const std::vector<bool> &q, const QPointF &p, const QPointF &o)
+{
+ return (q[0] && (p.x()-o.x()) >= 0 && (p.y()-o.y()) >= 0) ||
+ (q[1] && (p.x()-o.x()) >= 0 && (p.y()-o.y()) < 0) ||
+ (q[2] && (p.x()-o.x()) < 0 && (p.y()-o.y()) < 0) ||
+ (q[3] && (p.x()-o.x()) < 0 && (p.y()-o.y()) >= 0);
+}
+
+//test if rect is contained in at least one of the given quadrants
+bool isRectIncluded(const std::vector<bool> &q, const QRectF &rect, const QPointF &origin)
+{
+ return !rect.contains(origin) &&
+ (isPointIncluded(q, rect.topLeft(), origin) ||
+ isPointIncluded(q, rect.bottomRight(), origin));
+}
+
+//test if angle is between start and end angles
+bool angleIsBetween(qreal angle, qreal begin, qreal end)
+{
+ if (begin > end)
+ return angle >= begin || angle <= end;
+ else
+ return angle >= begin && angle <= end;
+}
+
+//test if 2 sectors overlap
+bool sectorsOverlap(qreal begin1, qreal end1,
+ qreal begin2, qreal end2)
+{
+ return angleIsBetween(begin1, begin2, end2) ||
+ angleIsBetween(end1, begin2, end2) ||
+ angleIsBetween(begin2, begin1, end1) ||
+ angleIsBetween(end2, begin1, end1);
+}
+
+qreal pointDistance(const QPointF& p1, const QPointF& p2)
+{
+ qreal dx=p1.x()-p2.x();
+ qreal dy=p1.y()-p2.y();
+ return qSqrt(qPow(dx,2)+qPow(dy,2));
+}
+
+//minimum distance between 2 rects, calculated from their edges
+qreal rectDistance(const QRectF& rect1, const QRectF& rect2)
+{
+ bool left = rect2.bottomRight().x() < rect1.topLeft().x();
+ bool right = rect2.topLeft().x() > rect1.bottomRight().x();
+ bool bottom = rect2.topLeft().y() > rect1.bottomRight().y();
+ bool top = rect2.bottomRight().y() < rect1.topLeft().y();
+
+ if (top && left)
+ return pointDistance(rect1.topLeft(), rect2.bottomRight());
+ else if (left && bottom)
+ return pointDistance(rect1.bottomLeft(), rect2.topRight());
+ else if (bottom && right)
+ return pointDistance(rect1.bottomRight(), rect2.topLeft());
+ else if (right && top)
+ return pointDistance(rect1.topRight(), rect2.bottomRight());
+ else if (left)
+ return rect1.x() - rect2.bottomRight().x();
+ else if (right)
+ return rect2.x() - rect1.bottomRight().x();
+ else if (bottom)
+ return rect2.y() - rect1.bottomRight().y();
+ else if (top)
+ return rect1.y() - rect2.bottomRight().y();//y2 - y1b
+ else //rectangles overlap
+ return 0;
+}
+
+//get the widest arc that is less than 180 degrees, that this item covers, clockwise around the origin
+std::pair<qreal, qreal> getSector(const QRectF &rect, const QPointF &origin)
+{
+ std::pair<qreal, qreal> angles;
+ angles = std::minmax({std::atan2(rect.topLeft().y()-origin.y(), rect.topLeft().x()-origin.x()),
+ std::atan2(rect.topRight().y()-origin.y(), rect.topRight().x()-origin.x()),
+ std::atan2(rect.bottomRight().y()-origin.y(), rect.bottomRight().x()-origin.x()),
+ std::atan2(rect.bottomLeft().y()-origin.y(), rect.bottomLeft().x()-origin.x())
+ });
+
+ //if delta larger than 180, invert min and max
+ if (angles.second-angles.first > M_PI) {
+ qreal temp = angles.first;
+ angles.first = angles.second;
+ angles.second = temp;
+ }
+
+ return angles;
+}
+
+CursorNavigationAttached* SpatialNavigation360::getNextCandidate(
+ const QList<CursorNavigationAttached*> &candidates,
+ const CursorNavigationAttached *currentItem,
+ const CursorNavigationCommand& cmd)
+{
+ /* -angle should be between -180 and 180 in relation to the x axis, clockwise around the origin
+ * -depending on the input angle, find min and max x and y values which items must have
+ * -calculate angles for items points that are within the limits. from those angles, pick min and max (widest sector the item covers)
+ * -items that overlap the current item center, should be ignored
+ * -items that are within seaqrch beams angle limits, are stored along the angle ranges they have
+ * -if there are more than 1 stored items, we select the item that cuts the exact selection vector.
+ * if no item overlaps the center, pick the one closest to the current item
+ * -remember to use current item's coord system as the reference!!!
+ */
+
+ qWarning() << "##### navigation360: start, angle = " << cmd.angle;
+
+ if (candidates.isEmpty())
+ return nullptr;
+
+ if (!currentItem && candidates.size()) {
+ qDebug() << "the spatial chooser falling back to first child" << candidates.first();
+ return candidates.first();
+ }
+
+ //booleans for quadrants
+ std::vector<bool> quadrants(4);
+
+ //define selector beam sector
+// int angle1_deg, angle2_deg;
+ qreal angle1, angle2;
+
+ angle1 = CursorNavigationCommand::fitAngle(cmd.angle - cmd.angleTolerance);
+ angle2 = CursorNavigationCommand::fitAngle(cmd.angle + cmd.angleTolerance);
+
+
+// angle1_deg = (cmd.angle - cmd.angleTolerance) % 360;
+// if (angle1_deg < 0)
+// angle1_deg = 360 + angle1_deg;
+// angle2_deg = (cmd.angle + cmd.angleTolerance) % 360;
+
+// int a=angle2_deg-angle1_deg;
+// while (a > 0) {
+// quadrants[(angle1_deg/90+a/90) % 4] = true;
+// a -= 90;
+// }
+
+ quadrants[0] = sectorsOverlap(angle1, angle2, 0, M_PI_2);
+ quadrants[1] = sectorsOverlap(angle1, angle2, M_PI_2, M_PI);
+ quadrants[2] = sectorsOverlap(angle1, angle2, -M_PI, -M_PI_2);
+ quadrants[3] = sectorsOverlap(angle1, angle2, -M_PI_2, 0);
+
+ qWarning() << "navigation360: quadrants = " << quadrants;
+
+ const QRectF currentItemSceneRect = currentItem->item()->mapRectToScene(
+ QRectF( 0, 0, currentItem->item()->width(), currentItem->item()->height() ));
+
+ const QPointF origin = currentItemSceneRect.center();
+ qWarning() << "origin = " << origin;
+
+ //item that overlaps the center of the selector beam
+ CursorNavigationAttached* directHitItem = nullptr;
+ qreal directHitDistance = -1;
+ //item that overlaps selector beam does not overlap with the center
+ CursorNavigationAttached* withinToleranceItem = nullptr;
+ qreal withinToleranceDistance = -1;
+
+
+ for (auto iter: candidates)
+ {
+ const QRectF itemSceneRect = iter->item()->mapRectToScene(
+ QRectF( 0, 0, iter->item()->width(), iter->item()->height() ));
+
+ if (iter == currentItem || !iter->item()->isVisible() || !iter->item()->isEnabled())
+ continue;
+
+ //if (isRectIncluded(quadrants, itemSceneRect, origin)) {
+
+ std::pair<qreal,qreal> sector = getSector(itemSceneRect, origin);
+ qWarning() << "item " << iter->item() << " rect = " << itemSceneRect << " sector " << sector;
+
+ if (angleIsBetween(cmd.angle, sector.first, sector.second)) {
+ qWarning() << "is direct hit";
+ qreal dist = rectDistance(itemSceneRect, currentItemSceneRect);
+ if (!directHitItem || dist < directHitDistance) {
+ directHitDistance = dist;
+ directHitItem = iter;
+ }
+ } else if (!directHitItem && sectorsOverlap(angle1, angle2, sector.first, sector.second)) {
+ qWarning() << "is within tolerances";
+ qreal dist = rectDistance(itemSceneRect, currentItemSceneRect);
+ if (dist < withinToleranceDistance) {
+ withinToleranceDistance = dist;
+ withinToleranceItem = iter;
+ }
+ }
+ //}
+ }
+
+ qWarning() << "##### end, directHit = " <<
+ directHitItem << " withinTolerances = " << withinToleranceItem;
+
+ if (directHitItem)
+ return directHitItem;
+
+ return withinToleranceItem;
+
+}