How to correctly rotate a QGraphicsItem around different anchors in Qt C++
-
I am working on a custom
QGraphicsItem
that has two anchor points, and I want to be able to rotate the item around these anchors when the user interacts with them. I have implemented amousePressEvent
andmouseMoveEvent
to detect which anchor the user clicked on, set the rotation anchor point, and compute the angle of rotation.Here is a simplified version of my code:
void MyView::mousePressEvent(QGraphicsSceneMouseEvent *event) { _tapPoint = event->pos(); auto cp1 = _anchor1.center(); // get center point of anchor 1 auto cp2 = _anchor2.center(); // get center point of anchor 2 // Anchor 1 clicked if (_anchor1.contains(_tapPoint)) { setTransformOriginPoint(cp2.x(), cp2.y()); // set rotation anchor to anchor 2 _viewState = ANCHOR1; } // Anchor 2 clicked else if (_anchor2.contains(_tapPoint)) { setTransformOriginPoint(cp1.x(), cp1.y()); // set rotation anchor to anchor 1 _viewState = ANCHOR2; } // View clicked else { QGraphicsItem::mousePressEvent(event); _viewState = VIEW; } } void MyView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { auto p = event->pos(); switch (_viewState) { case ANCHOR1: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor2.y(), _width)); setRotation(rotation() - angle); // rotate the item around anchor 2 break; } case ANCHOR2: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor1.y(), _width)); setRotation(rotation() + angle); // rotate the item around anchor 1 break; } default: QGraphicsItem::mouseMoveEvent(event); // move the item normally } }
However, when I try to rotate the item from one anchor point (around the other) and then rotate it again from the other anchor point, it jumps back to another position! I am not sure why this is happening.
As you can see in this video, when I first rotate the view it works, but when I try to rotate it from the other anchor, its position jumps to another position!
Question: What could be causing this issue, and how can I modify my code to achieve the desired behavior of smoothly rotating around different anchor points?
Annex. Here is the complete code to reproduce this issue:
MyView.h
class MyView : public QGraphicsItem { public: MyView(float xPos, float yPos, float width, float height, QGraphicsItem *parent = nullptr); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; [[nodiscard]] QRectF boundingRect() const override; enum ViewState { ANCHOR1, ANCHOR2, VIEW }; protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; private: float _width, _height; QRectF _anchor1, _anchor2; QPointF _tapPoint; ViewState _viewState; };
MyView.cpp
static constexpr float ANCHOR_RADIUS = 10; MyView::MyView(float xPos, float yPos, float width, float height, QGraphicsItem *parent) : QGraphicsItem(parent), _width(width), _height(height), _viewState(VIEW) { setPos(xPos, yPos); setFlag(ItemIsMovable); auto diameter = 2 * ANCHOR_RADIUS; _anchor1.setRect(0, 0, diameter, diameter); _anchor2.setRect(width - diameter, 0, diameter, diameter); } void MyView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { auto diameter = 2 * ANCHOR_RADIUS; _anchor1.setRect(0, 0, diameter, diameter); _anchor2.setRect(_width - diameter, 0, diameter, diameter); // Anchor 1 and 2 coordinate auto c1 = _anchor1.center(); auto c2 = _anchor2.center(); painter->drawLine(static_cast<int> (c1.x()), static_cast<int>(c1.y()), static_cast<int>(c2.x()), static_cast<int>(c2.y())); painter->drawEllipse(_anchor1); painter->drawEllipse(_anchor2); } QRectF MyView::boundingRect() const { return {0, 0, static_cast<qreal>(_width), static_cast<qreal>(_height)}; } void MyView::mousePressEvent(QGraphicsSceneMouseEvent *event) { _tapPoint = event->pos(); auto cp1 = _anchor1.center(); // get center point of anchor 1 auto cp2 = _anchor2.center(); // get center point of anchor 2 // Anchor 1 clicked if (_anchor1.contains(_tapPoint)) { setTransformOriginPoint(cp2.x(), cp2.y()); // set rotation anchor to anchor 2 _viewState = ANCHOR1; } // anchor 2 clicked else if (_anchor2.contains(_tapPoint)) { setTransformOriginPoint(cp1.x(), cp1.y()); // set rotation anchor to anchor 1 _viewState = ANCHOR2; } // View clicked else { QGraphicsItem::mousePressEvent(event); _viewState = VIEW; } } void MyView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { auto p = event->pos(); switch (_viewState) { case ANCHOR1: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor2.y(), _width)); setRotation(rotation() - angle); // rotate the item around anchor 2 break; } case ANCHOR2: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor1.y(), _width)); setRotation(rotation() + angle); // rotate the item around anchor 1 break; } default: QGraphicsItem::mouseMoveEvent(event); // move the item normally } }
MainWindow.h
class MainWindow : public QMainWindow{ public: explicit MainWindow(QWidget* parent = nullptr); private: QGraphicsView* view; QGraphicsScene* scene; };
MainWindow.cpp
static constexpr int WIDTH = 500; static constexpr int HEIGHT = 500; MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) { scene = new QGraphicsScene(this); scene->setSceneRect(QRectF(0, 0, WIDTH, HEIGHT)); view = new QGraphicsView(scene); view->setRenderHint(QPainter::Antialiasing); auto layout = new QVBoxLayout; layout->addWidget(view); auto widget = new QWidget; widget->setLayout(layout); setCentralWidget(widget); auto myView = new MyView(100, 100, 100, 20); scene->addItem(myView); }
main.cpp
int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return QApplication::exec(); }
-
It might be a little late, but maybe someone else will find this information useful in the future. I am pretty new to Qt, so please correct me if my explanations are not correct. I believe that the problem lies in the changing of the origin point itself. Because your two anchor points exist relative to the coordinates of your MyView item, they will also change their position every time you change the origin point. This is probably the reason why you get this weird jumping behavior. Another way of implementing the desired behavior would be to translate the whole MyView item. So you could implement the following modifications to your existing code. First, change the mousePressEvent method so that you no longer change the origin.
MyView::mousePressEvent
void MyView::mousePressEvent(QGraphicsSceneMouseEvent *event) { _tapPoint = event->pos(); auto cp1 = _anchor1.center(); // get center point of anchor 1 auto cp2 = _anchor2.center(); // get center point of anchor 2 // Anchor 1 clicked if (_anchor1.contains(_tapPoint)) { _viewState = ANCHOR1; } // anchor 2 clicked else if (_anchor2.contains(_tapPoint)) { _viewState = ANCHOR2; } // View clicked else { QGraphicsItem::mousePressEvent(event); _viewState = VIEW; } }
Secondly, use a transformation matrix to rotate the whole item.
MyView::mouseMoveEvent
void MyView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { auto p = event->pos(); auto cp1 = _anchor1.center(); // get center point of anchor 1 auto cp2 = _anchor2.center(); // get center point of anchor 2 QTransform transformMatrix; switch (_viewState) { case ANCHOR1: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor2.y(), _width)); transformMatrix.translate(cp2.x(), cp2.y()); transformMatrix.rotate(-angle); transformMatrix.translate(-cp2.x(), -cp2.y()); setTransform(transformMatrix, true); break; } case ANCHOR2: { // calculate the angle of the rotation based on the mouse touch auto angle = qRadiansToDegrees(qAtan2(p.y() - _anchor1.y(), _width)); transformMatrix.translate(cp1.x(), cp1.y()); transformMatrix.rotate(angle); transformMatrix.translate(-cp1.x(), -cp1.y()); setTransform(transformMatrix, true); break; } default: QGraphicsItem::mouseMoveEvent(event); // move the item normally } }
In this solution, I just change the rotation origin manually by first translating the whole item by the coordinates of the center point of the selected anchor.
-