Каков наилучший способ реализации интерактивной сплайн-кривой в QGraphicsView? - PullRequest
1 голос
/ 10 июня 2019

Я разрабатываю приложение, которое позволит пользователю загружать изображение трещины скалы и применять сплайн-аппроксимацию к этой трещине.

Для этой цели у меня есть QGraphicsView, который отображает загруженное изображение. Как только изображение загружено, у пользователя есть возможность рисовать точки на scene, которые я хочу последовательно связать линиями. Эти соединенные точки сделают кривую, и я хочу быть интерактивной. Под интерактивом я подразумеваю, что я хочу, чтобы пользователь мог перетаскивать точки, не нарушая кривую, делая линии, соединяющие точки, перемещаться вместе с точкой. Я также хочу, чтобы пользователь мог удалять выбранные точки, чтобы смежные точки оставались подключенными. Это не все функции, которые я хочу, но я думаю, вы поняли идею.

Я создал класс MeasurePoint, который наследуется от QGraphicsItem и содержит всю информацию об одной отдельной точке. Имеет следующие поля:

int xPos;
int yPos;
int index;
bool movable;
bool selected;

В этом классе также есть несколько методов для управления этими точками (функции получения и установки для полей и т. Д.). Но я не реализую функцию соединения в этом классе, потому что он содержит только информацию об отдельной точке, а не дальше. Мне нравится, как ведут себя точки, если я просто добавляю их на сцену. Но теперь мне нужно соединить их так, как я описал ранее, и я не могу понять, как это сделать.

Как лучше всего хранить баллы? Как именно я должен реализовать связи между точками? Любая возможная помощь приветствуется.

P.s. если в этом вопросе нужна моя реализация класса MeasurePoint, я ее отредактирую.

1 Ответ

2 голосов
/ 10 июня 2019

Используйте QGraphicsPathItem , чтобы нарисовать сплайн. Затем используйте метод QPainterPath::cubicTo для создания связей между точками.

Вам необходимо вычислить контрольные точки, чтобы нарисовать плавную кривую Безье между двумя точками.

Вы можете найти множество лучших библиотек, основанных на кривых Quadratic Bezier, но ниже приведен краткий пример, который использует типы Qt и наивный подход, чтобы понять, как получить контрольные точки:

QPair<QPointF, QPointF> controlPoints(QPointF const& p0, QPointF const& p1, QPointF const& p2, qreal t=0.25)
{
    QPair<QPointF, QPointF> pair;
    qreal d01 = qSqrt( ( p1.x() - p0.x() ) * ( p1.x() - p0.x() ) + ( p1.y() - p0.y() ) * ( p1.y() - p0.y() ) );
    qreal d12 = qSqrt( ( p2.x() - p1.x() ) * ( p2.x() - p1.x() ) + ( p2.y() - p1.y() ) * ( p2.y() - p1.y() ) );

    qreal fa = t * d01 / ( d01 + d12 );
    qreal fb = t * d12 / ( d01 + d12 );

    qreal c1x = p1.x() - fa * ( p2.x() - p0.x() );
    qreal c1y = p1.y() - fa * ( p2.y() - p0.y() );
    qreal c2x = p1.x() + fb * ( p2.x() - p0.x() );
    qreal c2y = p1.y() + fb * ( p2.y() - p0.y() );

    pair.first = QPointF( c1x, c1y );
    pair.second = QPointF( c2x, c2y );

    return pair;
}

Затем просмотрите список точек, чтобы создать QPainterPath:

QPainterPath BackgroundBuilder::buildPath(QList<QPointF> const& points)
{
    QPainterPath pth;

    QPair<QPointF, QPointF> pair = controlPoints(points.at(0), points.at(1), points.at(2));
    QPointF p0 = pair.second;
    pth.moveTo(0, 0);
    pth.lineTo(p0);
    for (int i = 2; i != points.count() - 1; ++i)
    {
        QPair<QPointF, QPointF> pair = controlPoints( points.at(i - 1), points.at(i), points.at(i + 1));
        pth.cubicTo( p0, pair.first, points.at( i ) );
        p0 = pair.second;
    }
    return pth;
}

Возможно, вам потребуется преобразовать MeasurePoint в QPointF, чтобы этот код был общим:

QList<QPointF> points;
QList<MeasurePoint*> measures = ...;
for (MeasurePoint* measure: measures)
{
    points << QPointF(measure.xPos, measure.yPos);
}
...