2013-07-07 2 views
6

히트 곡 사출 물의 경로 (기본적으로 라인이지만이 예에서는 QPainterPath으로 나타냈음)가 다음과 같은 항목과 교차하는지 확인하려고합니다. 내 장면.라인과 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) 반환 : QPainterPath, QLineF에서 제공하는 기능을 사용하여이 점을 찾을 수있는 방법이 있는지 확실하지 않다 등 아래의 코드는 내가 할 노력하고있어 보여 빈 경로.

이 방법이 있습니까?

+0

당신이 당신의 질문에 대한 더 구체적인 태그를 추가하는 것이 좋을 수있다 Qt는 4 Qt는 5를 사용하고 있습니다 : 여기

는 고정 된 기능입니다. –

+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) - target 대신 elementAt (0)을 리턴해야합니다.

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/편집 할 수 있습니까? 그런 다음 픽스를 확인하고 대답을 업데이트 할 수 있습니다. – Mitch