Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to correctly rotate a QGraphicsItem around different anchors in Qt C++
QtWS25 Last Chance

How to correctly rotate a QGraphicsItem around different anchors in Qt C++

Scheduled Pinned Locked Moved Unsolved General and Desktop
2 Posts 2 Posters 579 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    Abdo21
    wrote on 6 Apr 2023, 22:47 last edited by Abdo21 4 Jun 2023, 22:58
    #1

    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 a mousePressEvent and mouseMoveEvent 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!

    demo.gif

    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();
    }
    
    1 Reply Last reply
    1
    • P Offline
      P Offline
      PBraeuer
      wrote on 24 Jun 2024, 08:27 last edited by
      #2

      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.

      1 Reply Last reply
      1
      • Pl45m4P Pl45m4 referenced this topic on 1 Dec 2024, 13:13

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved