查找直线和 QPainterPath 之间的交点
Posted
技术标签:
【中文标题】查找直线和 QPainterPath 之间的交点【英文标题】:Finding the point of intersection between a line and a QPainterPath 【发布时间】:2013-07-07 13:24:22 【问题描述】:我正在尝试确定即时命中弹丸的路径(基本上是一条线,但在我的示例中将其表示为 QPainterPath
)与场景中的项目相交的点。我不确定是否有办法使用QPainterPath
、QLineF
等提供的函数找到这一点。下面的代码说明了我正在尝试做的事情:
#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)
返回一个空路径。
有没有办法做到这一点?
【问题讨论】:
您使用的是 Qt 4 还是 Qt 5。最好为您的问题添加更具体的标签。 重复:***.com/questions/9393672/… 【参考方案1】:正如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 \lQPoint::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() 是一个整数),但可能有更简洁的方法来实现同样的目标。
【讨论】:
我已经创建了一个添加类似功能的建议:bugreports.qt-project.org/browse/QTBUG-32313 @AmusedToDeath - 我已经回滚了你的更改 - 这是几个月前接受的答案的重大代码更改 - 如果答案有问题,我建议先讨论它或创建你自己的答案。【参考方案2】:仅作记录(如果其他人踏入此处)。上面的答案非常好。如果第一个点已经是最近的点,则最接近点函数中可能会发生一个小错误。它应该返回 elementAt(0) 而不是 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;
【讨论】:
谢谢! :) 您能否在我的答案中显示错误的示例的修改版本中粘贴/编辑?然后我可以验证修复并更新答案。 谢谢!希望这可以避免我不得不用折线来近似形状。以上是关于查找直线和 QPainterPath 之间的交点的主要内容,如果未能解决你的问题,请参考以下文章
怎样在matlab中得到直线方程与plot出的曲线之间的交点坐标