6

我有一个应用程序,我在场景中放置了几条曲线。我正在寻找一种简单的方法来检测用户是否按下了线路。当我画多条线时太不准确了boundingRect()intersects()所以我做了这个功能,除了线条是垂直的,它就像一个梦一样工作。

selectionMargin是用户设置的全局变量(默认值 = 0.5)。它会调整边距,以确保选择检查的准确性。名称基于每个子线的线性函数,y = ax + b。Pos 是 mousePressEvent 的位置。

bool GraphApp::pointInPath(QPainterPath path, QPointF pos)
{
    qreal posY = pos.y();
    qreal posX = pos.x();

    for (int i = 0; i < path.elementCount()-1; ++i) {
        if (posX < path.elementAt(i + 1).x && posX > path.elementAt(i).x) {
            qreal dy = path.elementAt(i + 1).y - path.elementAt(i).y;
            qreal dx = path.elementAt(i + 1).x - path.elementAt(i).x;
            qreal a = dy / dx;
            qreal b = path.elementAt(i).y - (path.elementAt(i).x * a);

            if (selectionMargin == 0.0)
                selectionMargin = 0.5;

            qreal lowerBound = (a * posX + b) + selectionMargin;
            qreal upperBound = (a * posX + b) - selectionMargin;

            if (posY < lowerBound && posY > upperBound)
                return true;
        }
    }
    return false;
}

因此,当我从垂直线覆盖的区域发送 mousePressEvent 时,该函数似乎返回 false。我的第一个想法是 if 句:

if (posX < path.elementAt(i + 1).x && posX > path.elementAt(i).x)

关于如何在没有 if 句的情况下实现这一点的任何其他想法?

我还看到其他人正在努力寻找一种好方法来检查 a 是否QPainterPath包含一个没有boundingRect()andintersects()函数的点,所以这可能也可以用于其他人:)

编辑:据我所知,contains()使用boundingRect(). 所以我不认为这是一个合适的解决方案

4

1 回答 1

4

我曾经需要比你类似的东西。我需要测试两条路径的相似性。因此,我从点列表创建了一条路径(我希望您不需要更复杂的路径,因为该解决方案对于一般的 QPaintingPaths 来说会变得极其困难)。这条路径是使用给定的“容差”构建的,这是你的selectionMargin.

该函数返回一个 QPainterPath,它“在给定的折线周围绘制一个区域”。然后可以填充该区域,并生成与tolerance使用圆帽和圆形连接选项的笔宽绘制原始多段线相同的图像。

您也可以,这就是您想要做的,检查给定点是否包含在此路径中。请注意,QPainterPath::contains检查点是否位于路径定义的封闭区域内。例如,这个封闭区域对于单个线段是空的,对于两个线段是三角形,所以如果您contains直接在路径上使用这不是您想要的(正如我在对您的问题的第三条评论中提到的)。

QPainterPath intersectionTestPath(QList<QPointF> input, qreal tolerance)
{
    //will be the result
    QPainterPath path;

    //during the loop, p1 is the "previous" point, initially the first one
    QPointF p1 = input.takeFirst(); 

    //begin with a circle around the start point
    path.addEllipse(p1, tolerance, tolerance); 

    //input now starts with the 2nd point (there was a takeFirst)
    foreach(QPointF p2, input) 
    {
        //note: during the algorithm, the pair of points (p1, p2)
        //      describes the line segments defined by input.

        //offset = the distance vector from p1 to p2
        QPointF offset = p2 - p1;

        //normalize offset to length of tolerance
        qreal length = sqrt(offset.x() * offset.x() + offset.y() * offset.y());
        offset *= tolerance / length;

        //"rotate" the offset vector 90 degrees to the left and right
        QPointF leftOffset(-offset.y(), offset.x());
        QPointF rightOffset(offset.y(), -offset.x());

        //if (p1, p2) goes downwards, then left lies to the left and
        //right to the right of the source path segment
        QPointF left1 = p1 + leftOffset; 
        QPointF left2 = p2 + leftOffset;
        QPointF right1 = p1 + rightOffset;
        QPointF right2 = p2 + rightOffset;

        //rectangular connection from p1 to p2
        {
            QPainterPath p;
            p.moveTo(left1);
            p.lineTo(left2);
            p.lineTo(right2);
            p.lineTo(right1);
            p.lineTo(left1);
            path += p; //add this to the result path
        }

        //circle around p2
        {
            QPainterPath p;
            p.addEllipse(p2, tolerance, tolerance);
            path += p; //add this to the result path
        }

        p1 = p2;
    }

    //This does some simplification; you should use this if you call
    //path.contains() multiple times on a pre-calculated path, but
    //you won't need this if you construct a new path for every call
    //to path.contains().
    return path.simplified();
}
于 2012-07-31T12:59:19.247 回答