2013-07-07 60 views
6

我试图确定一个hitscan子弹的路径(基本上是一条线,但在我的例子中它代表它为QPainterPath)与点我的场景。我不知道是否有发现使用QPainterPathQLineF提供的功能,这一点的方式等。下面的代码说明了什么,我试图做的:找到一条线和一条QPainterPath之间的交点

#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)回报true,但projectilePath.intersected(targetShape)回报一条空路。

有没有办法做到这一点?

+0

你使用Qt 4或5的Qt它可以很好的更为具体的代码添加到您的问题。 –

+0

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

回答

5

由于Intersection point of QPainterPath and line (find QPainterPath y by x)的回答指出,QPainterPath::intersected()只考虑有填充区域的路径。矩形路径特技其也提到有可以实现这样的:

#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(); 
} 

这具有相当良好的精度(±1像素,由于QLineF::length()是整数),但也有可能是实现该方法的更合适的方法事情。

+0

我已经创建了一个类似功能的建议添加:https://bugreports.qt-project.org/browse/QTBUG-32313 – Mitch

+0

@AmusedToDeath - 我已回滚您的更改 - 这是对几个月前接受的答案的重大代码更改 - 如果出现问题有了答案,我建议先讨论它或创建自己的答案。 – Krease

+0

我也很想知道为什么它是错的。 :) – Mitch

1

只是为了记录(如果别人在这里步骤)。以上答案非常好。如果第一个点已经是最接近的一个,那么可能会发生nearestPoint函数中的一个小错误。它应该返回elementAt(0)而不是elementAt(0) - target。

这里是固定功能:

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

谢谢! :)你能否在我的回答中修改版本的示例中的pastebin/edit来显示错误?然后我可以验证修复并更新答案。 – Mitch