Search code examples
qtanimationqgraphicsitemqpainterpath

Animate ellipses along QPainterPath


I have a bunch of ellipses that initially are lined on top of a path and should move along the QPainterPath. I have it working for the first ellipse but I can't figure out how to get the correct position for the other ellipses.

Is there a way to check if it passed the end of the path and move it back to the beginning?

class Animation : public QAbstractAnimation
{
  public:
    Animation(const QPainterPath& path, QObject *parent = Q_NULLPTR);
    virtual void updateCurrentTime(int ms) override;
    virtual int duration() const override;
    QPainterPath mPath;
    QVector<EllipseGraphicsItem*> mAnimationElements;
};


Animation::Animation (const QPainterPath& path, QObject *parent) : QAbstractAnimation(parent)
, mPath(path)
{
    qreal pos = 0;
    qreal length = mPath.length();
    while (pos < length)
    {
        qreal percent = path.percentAtLength(pos);
        QPointF pointAtPercent = path.pointAtPercent(percent);
        pos += 40;
        EllipseGraphicsItem * item = new EllipseGraphicsItem(parentItem());
        mAnimationElements.append(item);
        item->setPos(pointAtPercent);
    }
}

void Animation::updateCurrentTime(int ms)
{
     QPointF point = mPath.pointAtPercent(qreal(ms) / 6000);

     if (mAnimationElements.size() > 0)
          mAnimationElements[0]->setPos(point);

     for (int i = 0; i < mAnimationElements.size(); i++) {
         // how to update each circle's position?
     }
}

Start the animation:

QPainterPath path;
path.moveTo(10, 10);
path.lineTo(QPointF(500, 10));
path.lineTo(QPointF(500, 700));
path.lineTo(QPointF(10, 700));
Animation *animation = new Animation(path, this);
animation->setLoopCount(-1);
animation->start();

Solution

  • Imho, it would be easier to use a QGraphicsObject with a QPropertyAnimation:

    Use a property that varies between 0 and the length of the path and place your elements by calculating their positions from its value and their position in the list.

    A quick example :

    class AnimatedEllipses: public QGraphicsObject
    {
        Q_OBJECT
        Q_PROPERTY(int progress READ progress WRITE setProgress)
    private:
        QGraphicsPathItem path;
        QList<QGraphicsEllipseItem*> ellipses;
        int propProgress;
    public:
        int progress() const { return propProgress;}
        void setProgress(int value)
        {
            propProgress = value;
            int index = 0;
            for (QGraphicsEllipseItem* ellipse: ellipses)
            {
                // Keep value between 0 and length.
                int lgt = (propProgress + index * 40) % int(path.path().length());
                qreal percent = path.path().percentAtLength(lgt);
                ++index;
                ellipse->setPos(path.path().pointAtPercent(percent));
            }
        }
        AnimatedEllipses(QPainterPath const& path): QGraphicsObject(), path(path), propProgress(0)
        {
            qreal pos = 0;
            qreal length = path.length();
            while (pos < length)
            {
                qreal percent = path.percentAtLength(pos);
                QPointF pointAtPercent = path.pointAtPercent(percent);
                pos += 40;
                QGraphicsEllipseItem * item = new QGraphicsEllipseItem(-10, -10, 20, 20, this);
                item->setPos(pointAtPercent);
                ellipses << item;
            }
    
            QPropertyAnimation* animation = new QPropertyAnimation(this, "progress");
            animation->setStartValue(0);
            animation->setEndValue(length);
            animation->setDuration(10000);
            animation->setLoopCount(-1);
            animation->start();
        }
    
    
    
        // QGraphicsItem interface
    public:
        QRectF boundingRect() const { return path.boundingRect();}
        void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget){}
    };
    

    The modulo allows you to create an infinte loop for each ellipse.