2013-07-07 12 views
6

Ich versuche, den Punkt zu bestimmen, wo ein Hitscan Projektil den Weg (im Grunde eine Linie, aber ich habe es als QPainterPath in meinem Beispiel dargestellt) schneidet mit einem Element in meine Szene. Ich bin nicht sicher, ob es einen Weg gibt, diesen Punkt mit den Funktionen von QPainterPath zur Verfügung gestellt zu finden, QLineF usw. Der folgende Code veranschaulicht, was ich versuche zu tun:Den Schnittpunkt zwischen einer Linie und einem QPainterPath finden

#include <QtWidgets> 

bool hit(const QPainterPath &projectilePath, QGraphicsScene *scene, QPointF &hitPos) 
{ 
    const QList<QGraphicsItem *> itemsInPath = scene->items(projectilePath, Qt::IntersectsItemBoundingRect); 
    if (!itemsInPath.isEmpty()) { 
     const QPointF projectileStartPos = projectilePath.elementAt(0); 
     float shortestDistance = std::numeric_limits<float>::max(); 
     QGraphicsItem *closest = 0; 
     foreach (QGraphicsItem *item, itemsInPath) { 
      QPointF distanceAsPoint = item->pos() - projectileStartPos; 
      float distance = abs(distanceAsPoint.x() + distanceAsPoint.y()); 
      if (distance < shortestDistance) { 
       shortestDistance = distance; 
       closest = item; 
      } 
     } 
     QPainterPath targetShape = closest->mapToScene(closest->shape()); 
     // hitPos = /* the point at which projectilePath hits targetShape */ 
     hitPos = closest->pos(); // incorrect; always gives top left 
     qDebug() << projectilePath.intersects(targetShape); // true 
     qDebug() << projectilePath.intersected(targetShape); // QPainterPath: Element count=0 
     // To show that they do actually intersect.. 
     QPen p1(Qt::green); 
     p1.setWidth(2); 
     QPen p2(Qt::blue); 
     p2.setWidth(2); 
     scene->addPath(projectilePath, p1); 
     scene->addPath(targetShape, p2); 
     return true; 
    } 
    return false; 
} 

int main(int argc, char *argv[]) 
{ 
    QApplication app(argc, argv); 

    QGraphicsView view; 
    view.setViewportUpdateMode(QGraphicsView::FullViewportUpdate); 
    QGraphicsScene *scene = new QGraphicsScene; 
    view.setScene(scene); 
    view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 
    view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 

    QGraphicsItem *target = scene->addRect(0, 0, 25, 25); 
    target->setTransformOriginPoint(QPointF(12.5, 12.5)); 
    target->setRotation(35); 
    target->setPos(100, 100); 

    QPainterPath projectilePath; 
    projectilePath.moveTo(200, 200); 
    projectilePath.lineTo(0, 0); 
    projectilePath.lineTo(200, 200); 

    QPointF hitPos; 
    if (hit(projectilePath, scene, hitPos)) { 
     scene->addEllipse(hitPos.x() - 2, hitPos.y() - 2, 4, 4, QPen(Qt::red)); 
    } 

    scene->addPath(projectilePath, QPen(Qt::DashLine)); 
    scene->addText("start")->setPos(180, 150); 
    scene->addText("end")->setPos(20, 0); 

    view.show(); 

    return app.exec(); 
} 

projectilePath.intersects(targetShape) kehrt true, aber projectilePath.intersected(targetShape) kehrt ein leerer Pfad.

Gibt es einen Weg, dies zu erreichen?

+0

verwenden Sie Qt 4 oder Qt 5. Es kann gut sein, das spezifischere Tag zu Ihrer Frage hinzufügen. –

+0

Duplizieren von: http://stackoverflow.com/questions/9393672/intersection-point-of-qpainterpath-and-line-find-qpainterpath-y-by-x – Mitch

Antwort

5

Wie die Antwort auf Intersection point of QPainterPath and line (find QPainterPath y by x) zeigt, berücksichtigt QPainterPath::intersected() nur Pfade, die Flächen füllen. Der rechteckige Weg Trick, der auch erwähnte es kann wie folgt realisiert werden:

#include <QtWidgets> 

/*! 
    Returns the closest element (position) in \a sourcePath to \a target, 
    using \l{QPoint::manhattanLength()} to determine the distances. 
*/ 
QPointF closestPointTo(const QPointF &target, const QPainterPath &sourcePath) 
{ 
    Q_ASSERT(!sourcePath.isEmpty()); 
    QPointF shortestDistance = sourcePath.elementAt(0) - target; 
    qreal shortestLength = shortestDistance.manhattanLength(); 
    for (int i = 1; i < sourcePath.elementCount(); ++i) { 
     const QPointF distance(sourcePath.elementAt(i) - target); 
     const qreal length = distance.manhattanLength(); 
     if (length < shortestLength) { 
      shortestDistance = sourcePath.elementAt(i); 
      shortestLength = length; 
     } 
    } 
    return shortestDistance; 
} 

/*! 
    Returns \c true if \a projectilePath intersects with any items in \a scene, 
    setting \a hitPos to the position of the intersection. 
*/ 
bool hit(const QPainterPath &projectilePath, QGraphicsScene *scene, QPointF &hitPos) 
{ 
    const QList<QGraphicsItem *> itemsInPath = scene->items(projectilePath, Qt::IntersectsItemBoundingRect); 
    if (!itemsInPath.isEmpty()) { 
     const QPointF projectileStartPos = projectilePath.elementAt(0); 
     float shortestDistance = std::numeric_limits<float>::max(); 
     QGraphicsItem *closest = 0; 
     foreach (QGraphicsItem *item, itemsInPath) { 
      QPointF distanceAsPoint = item->pos() - projectileStartPos; 
      float distance = abs(distanceAsPoint.x() + distanceAsPoint.y()); 
      if (distance < shortestDistance) { 
       shortestDistance = distance; 
       closest = item; 
      } 
     } 

     QPainterPath targetShape = closest->mapToScene(closest->shape()); 
     // QLineF has normalVector(), which is useful for extending our path to a rectangle. 
     // The path needs to be a rectangle, as QPainterPath::intersected() only accounts 
     // for intersections between fill areas, which projectilePath doesn't have. 
     QLineF pathAsLine(projectileStartPos, projectilePath.elementAt(1)); 
     // Extend the first point in the path out by 1 pixel. 
     QLineF startEdge = pathAsLine.normalVector(); 
     startEdge.setLength(1); 
     // Swap the points in the line so the normal vector is at the other end of the line. 
     pathAsLine.setPoints(pathAsLine.p2(), pathAsLine.p1()); 
     QLineF endEdge = pathAsLine.normalVector(); 
     // The end point is currently pointing the wrong way; move it to face the same 
     // direction as startEdge. 
     endEdge.setLength(-1); 
     // Now we can create a rectangle from our edges. 
     QPainterPath rectPath(startEdge.p1()); 
     rectPath.lineTo(startEdge.p2()); 
     rectPath.lineTo(endEdge.p2()); 
     rectPath.lineTo(endEdge.p1()); 
     rectPath.lineTo(startEdge.p1()); 
     // Visualize the rectangle that we created. 
     scene->addPath(rectPath, QPen(QBrush(Qt::blue), 2)); 
     // Visualize the intersection of the rectangle with the item. 
     scene->addPath(targetShape.intersected(rectPath), QPen(QBrush(Qt::cyan), 2)); 
     // The hit position will be the element (point) of the rectangle that is the 
     // closest to where the projectile was fired from. 
     hitPos = closestPointTo(projectileStartPos, targetShape.intersected(rectPath)); 

     return true; 
    } 
    return false; 
} 

int main(int argc, char *argv[]) 
{ 
    QApplication app(argc, argv); 

    QGraphicsView view; 
    QGraphicsScene *scene = new QGraphicsScene; 
    view.setScene(scene); 
    view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 
    view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 

    QGraphicsItem *target = scene->addRect(0, 0, 25, 25); 
    target->setTransformOriginPoint(QPointF(12.5, 12.5)); 
    target->setRotation(35); 
    target->setPos(100, 100); 

    QPainterPath projectilePath; 
    projectilePath.moveTo(200, 200); 
    projectilePath.lineTo(0, 0); 
    projectilePath.lineTo(200, 200); 

    QPointF hitPos; 
    if (hit(projectilePath, scene, hitPos)) { 
     scene->addEllipse(hitPos.x() - 2, hitPos.y() - 2, 4, 4, QPen(Qt::red)); 
    } 

    scene->addPath(projectilePath, QPen(Qt::DashLine)); 
    scene->addText("start")->setPos(180, 150); 
    scene->addText("end")->setPos(20, 0); 

    view.show(); 

    return app.exec(); 
} 

Diese ziemlich gut hat Präzision (± 1 Pixel, da QLineF::length() eine ganze Zahl), aber es könnte eine ordentliche Art und Weise sein, das gleiche zu erreichen Ding.

+0

Ich habe einen Vorschlag für ähnliche Funktionalität erstellt hinzugefügt : https://bugreports.qt-project.org/browse/QTBUG-32313 – Mitch

+0

@AmusedToDeath - Ich habe Ihre Änderungen rückgängig gemacht - es war eine signifikante Codeänderung zu einer akzeptierten Antwort von mehreren Monaten - wenn es ein Problem gibt Bei der Antwort empfehle ich, zuerst darüber zu diskutieren oder eine eigene Antwort zu erstellen. – Krease

+0

Ich bin auch neugierig zu wissen, warum es falsch war. :) – Mitch

1

Nur für die Aufzeichnung (und wenn jemand anderes hier tritt). Die obige Antwort ist ausgezeichnet. Es gibt nur einen kleinen Fehler in der Funktion "engster Punkt", der passieren kann, wenn der erste Punkt bereits der nächste ist. Es sollte elementAt (0) anstelle von elementAt (0) - target zurückgeben. Hier

ist die feste Funktion:

QPointF closestPointTo(const QPointF &target, const QPainterPath &sourcePath) 
{ 
    Q_ASSERT(!sourcePath.isEmpty()); 

    QPointF shortestDistance; 
    qreal shortestLength = std::numeric_limits<int>::max(); 

    for (int i = 0; i < sourcePath.elementCount(); ++i) { 
     const QPointF distance(sourcePath.elementAt(i) - target); 
     const qreal length = distance.manhattanLength(); 
     if (length < shortestLength) { 
      shortestDistance = sourcePath.elementAt(i); 
      shortestLength = length; 
     } 
    } 

    return shortestDistance; 
} 
+0

Danke! :) Kannst du bitte pastebin/edit in einer modifizierten Version des Beispiels in meiner Antwort, die den Bug zeigt? Dann kann ich das Update überprüfen und die Antwort aktualisieren. – Mitch