1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qkeyframeanimation.h"
5#include "Qt3DAnimation/private/qkeyframeanimation_p.h"
6
7#include <cmath>
8
9QT_BEGIN_NAMESPACE
10
11namespace Qt3DAnimation {
12
13/*!
14 \class Qt3DAnimation::QKeyframeAnimation
15 \brief A class implementing simple keyframe animation to a QTransform.
16 \inmodule Qt3DAnimation
17 \since 5.9
18 \inherits Qt3DAnimation::QAbstractAnimation
19
20 A Qt3DAnimation::QKeyframeAnimation class implements simple keyframe animation
21 that can be used to animate \l QTransform. The keyframes consists of multiple
22 timed QTransforms, which are interpolated and applied to the target \l QTransform.
23 \l QEasingCurve is used between keyframes to control the interpolator. RepeatMode
24 can be set for when the position set to the QKeyframeAnimation is below or above
25 the values defined in the keyframe positions.
26*/
27
28/*!
29 \qmltype KeyframeAnimation
30 \brief A type implementing simple keyframe animation to a Transform.
31 \inqmlmodule Qt3D.Animation
32 \since 5.9
33 \inherits AbstractAnimation
34 \instantiates Qt3DAnimation::QKeyframeAnimation
35
36 A KeyframeAnimation type implements simple keyframe animation
37 that can be used to animate \l Transform. The keyframes consists of multiple
38 timed \l {Qt3D.Core::Transform}s, which are interpolated and applied
39 to the target Transform. EasingCurve is used between keyframes to control
40 the interpolator. RepeatMode can be set for when the position set to the
41 KeyframeAnimation is less or or greater than the values defined in the keyframe positions.
42*/
43
44/*!
45 \property Qt3DAnimation::QKeyframeAnimation::framePositions
46 Holds the positions of the keyframes. Each position in the list specifies the position
47 of the corresponding keyframe with the same index. The values must be in an ascending order.
48 Values can be positive or negative and do not have any predefined unit.
49*/
50/*!
51 \property Qt3DAnimation::QKeyframeAnimation::target
52 Holds the target QTransform the animation is applied to.
53*/
54/*!
55 \property Qt3DAnimation::QKeyframeAnimation::easing
56 Holds the easing curve of the interpolator between keyframes.
57*/
58/*!
59 \property Qt3DAnimation::QKeyframeAnimation::targetName
60 Holds the name of the target transform. This is a convenience property making it
61 easier to match the target transform to the keyframe animation. The name
62 is usually same as the name of the parent entity of the target transform, but
63 does not have to be.
64*/
65/*!
66 \property Qt3DAnimation::QKeyframeAnimation::startMode
67 Holds the repeat mode for the position values less than the first frame position.
68*/
69/*!
70 \property Qt3DAnimation::QKeyframeAnimation::endMode
71 Holds the repeat mode for the position values greater than the last frame position.
72*/
73/*!
74 \enum QKeyframeAnimation::RepeatMode
75
76 This enumeration specifies how position values outside keyframe values are handled.
77 \value None The animation is not applied to the target transform.
78 \value Constant The edge keyframe value is used.
79 \value Repeat The animation is repeated.
80*/
81/*!
82 \qmlproperty list<real> KeyframeAnimation::framePositions
83 Holds the positions of the keyframes. Each position in the list specifies the position
84 of the corresponding keyframe. The values must be in an ascending order. Values can
85 be positive or negative and do not have any predefined unit.
86*/
87/*!
88 \qmlproperty Transform KeyframeAnimation::target
89 Holds the target Transform the animation is applied to.
90*/
91/*!
92 \qmlproperty EasingCurve KeyframeAnimation::easing
93 Holds the easing curve of the interpolator between keyframes.
94*/
95/*!
96 \qmlproperty string KeyframeAnimation::targetName
97 Holds the name of the target transform. This is a convenience property making it
98 easier to match the target transform to the keyframe animation. The name
99 is usually same as the name of the parent entity of the target transform, but
100 does not have to be.
101*/
102/*!
103 \qmlproperty enumeration KeyframeAnimation::startMode
104 Holds the repeat mode for the position values less than the first frame position.
105 \list
106 \li None
107 \li Constant
108 \li Repeat
109 \endlist
110*/
111/*!
112 \qmlproperty enumeration KeyframeAnimation::endMode
113 Holds the repeat mode for the position values greater than the last frame position.
114 \list
115 \li None
116 \li Constant
117 \li Repeat
118 \endlist
119*/
120/*!
121 \qmlproperty list<Transform> KeyframeAnimation::keyframes
122 Holds the list of keyframes in the keyframe animation.
123*/
124
125QKeyframeAnimationPrivate::QKeyframeAnimationPrivate()
126 : QAbstractAnimationPrivate(QAbstractAnimation::KeyframeAnimation)
127 , m_target(nullptr)
128 , m_minposition(0.0f)
129 , m_maxposition(0.0f)
130 , m_startMode(QKeyframeAnimation::Constant)
131 , m_endMode(QKeyframeAnimation::Constant)
132{
133
134}
135
136/*!
137 Constructs an QKeyframeAnimation with \a parent.
138*/
139QKeyframeAnimation::QKeyframeAnimation(QObject *parent)
140 : QAbstractAnimation(*new QKeyframeAnimationPrivate(), parent)
141{
142 Q_D(QKeyframeAnimation);
143 d->m_positionConnection = QObject::connect(sender: this, signal: &QAbstractAnimation::positionChanged,
144 context: this, slot: &QKeyframeAnimation::updateAnimation);
145}
146
147
148void QKeyframeAnimation::setFramePositions(const QList<float> &positions)
149{
150 Q_D(QKeyframeAnimation);
151 d->m_framePositions = positions;
152 d->m_position = -1.0f;
153 if (d->m_framePositions.size() == 0) {
154 d->m_minposition = d->m_maxposition = 0.0f;
155 return;
156 }
157 d->m_minposition = d->m_framePositions.first();
158 d->m_maxposition = d->m_framePositions.last();
159 float lastPos = d->m_minposition;
160 for (float p : std::as_const(t&: d->m_framePositions)) {
161 if (p < lastPos || p > d->m_maxposition)
162 qWarning() << "positions not ordered correctly";
163 lastPos = p;
164 }
165 setDuration(d->m_maxposition);
166}
167
168/*!
169 Sets the \a keyframes of the animation. Old keyframes are cleared.
170 */
171void QKeyframeAnimation::setKeyframes(const QList<Qt3DCore::QTransform *> &keyframes)
172{
173 Q_D(QKeyframeAnimation);
174 d->m_keyframes = keyframes;
175}
176
177// slerp which allows long path
178QQuaternion lslerp(QQuaternion q1, QQuaternion q2, float t)
179{
180 QQuaternion ret;
181 // Handle the easy cases first.
182 if (t <= 0.0f)
183 return q1;
184 else if (t >= 1.0f)
185 return q2;
186
187 float cos = qBound(min: -1.0f, val: QQuaternion::dotProduct(q1, q2), max: 1.0f);
188 float angle = std::acos(x: cos);
189 float sin = std::sin(x: angle);
190 if (!qFuzzyIsNull(f: sin)) {
191 float a = std::sin(x: (1.0 - t) * angle) / sin;
192 float b = std::sin(x: t * angle) / sin;
193 ret = (q1 * a + q2 * b).normalized();
194 } else {
195 ret = q1 * (1.0f-t) + q2 * t;
196 }
197 return ret;
198}
199
200void QKeyframeAnimationPrivate::calculateFrame(float position)
201{
202 if (m_target && m_framePositions.size() > 0
203 && m_keyframes.size() == m_framePositions.size()) {
204 if (position < m_minposition) {
205 if (m_startMode == QKeyframeAnimation::None) {
206 return;
207 } else if (m_startMode == QKeyframeAnimation::Constant) {
208 m_target->setRotation(m_keyframes.first()->rotation());
209 m_target->setScale3D(m_keyframes.first()->scale3D());
210 m_target->setTranslation(m_keyframes.first()->translation());
211 return;
212 } else {
213 // must be repeat
214 position = std::fmod(x: -(position - m_minposition), y: m_maxposition - m_minposition)
215 + m_minposition;
216 }
217 } else if (position >= m_maxposition) {
218 if (m_endMode == QKeyframeAnimation::None) {
219 return;
220 } else if (m_endMode == QKeyframeAnimation::Constant) {
221 m_target->setRotation(m_keyframes.last()->rotation());
222 m_target->setScale3D(m_keyframes.last()->scale3D());
223 m_target->setTranslation(m_keyframes.last()->translation());
224 return;
225 } else {
226 // must be repeat
227 position = std::fmod(x: position - m_minposition, y: m_maxposition - m_minposition)
228 + m_minposition;
229 }
230 }
231 if (position >= m_minposition && position < m_maxposition) {
232 for (int i = 0; i < m_framePositions.size() - 1; i++) {
233 if (position >= m_framePositions.at(i)
234 && position < m_framePositions.at(i: i+1)) {
235 float ip = (position - m_framePositions.at(i))
236 / (m_framePositions.at(i: i+1) - m_framePositions.at(i));
237 float eIp = m_easing.valueForProgress(progress: ip);
238 float eIip = 1.0f - eIp;
239
240 Qt3DCore::QTransform *a = m_keyframes.at(i);
241 Qt3DCore::QTransform *b = m_keyframes.at(i: i+1);
242
243 QVector3D s = a->scale3D() * eIip + b->scale3D() * eIp;
244 QVector3D t = a->translation() * eIip + b->translation() * eIp;
245 QQuaternion r = QQuaternion::slerp(q1: a->rotation(), q2: b->rotation(), t: eIp);
246
247 m_target->setRotation(r);
248 m_target->setScale3D(s);
249 m_target->setTranslation(t);
250 return;
251 }
252 }
253 }
254 }
255}
256
257void QKeyframeAnimation::updateAnimation(float position)
258{
259 Q_D(QKeyframeAnimation);
260 d->calculateFrame(position);
261}
262
263QList<float> QKeyframeAnimation::framePositions() const
264{
265 Q_D(const QKeyframeAnimation);
266 return d->m_framePositions;
267}
268
269/*!
270 Returns the list of keyframes.
271 */
272QList<Qt3DCore::QTransform *> QKeyframeAnimation::keyframeList() const
273{
274 Q_D(const QKeyframeAnimation);
275 return d->m_keyframes;
276}
277
278void QKeyframeAnimation::setTarget(Qt3DCore::QTransform *target)
279{
280 Q_D(QKeyframeAnimation);
281 if (d->m_target != target) {
282 d->m_target = target;
283 emit targetChanged(target: d->m_target);
284 d->m_position = -1.0f;
285
286 if (target) {
287 d->m_baseScale = target->scale3D();
288 d->m_baseTranslation = target->translation();
289 d->m_baseRotation = target->rotation();
290 }
291 }
292}
293
294QKeyframeAnimation::RepeatMode QKeyframeAnimation::startMode() const
295{
296 Q_D(const QKeyframeAnimation);
297 return d->m_startMode;
298}
299
300QKeyframeAnimation::RepeatMode QKeyframeAnimation::endMode() const
301{
302 Q_D(const QKeyframeAnimation);
303 return d->m_endMode;
304}
305
306void QKeyframeAnimation::setEasing(const QEasingCurve &easing)
307{
308 Q_D(QKeyframeAnimation);
309 if (d->m_easing != easing) {
310 d->m_easing = easing;
311 emit easingChanged(easing);
312 }
313}
314
315void QKeyframeAnimation::setTargetName(const QString &name)
316{
317 Q_D(QKeyframeAnimation);
318 if (d->m_targetName != name) {
319 d->m_targetName = name;
320 emit targetNameChanged(name);
321 }
322}
323
324void QKeyframeAnimation::setStartMode(QKeyframeAnimation::RepeatMode mode)
325{
326 Q_D(QKeyframeAnimation);
327 if (d->m_startMode != mode) {
328 d->m_startMode = mode;
329 emit startModeChanged(startMode: mode);
330 }
331}
332
333void QKeyframeAnimation::setEndMode(QKeyframeAnimation::RepeatMode mode)
334{
335 Q_D(QKeyframeAnimation);
336 if (mode != d->m_endMode) {
337 d->m_endMode = mode;
338 emit endModeChanged(endMode: mode);
339 }
340}
341
342/*!
343 Adds new \a keyframe at the end of the animation. The QTransform can
344 be added to the animation multiple times.
345 */
346void QKeyframeAnimation::addKeyframe(Qt3DCore::QTransform *keyframe)
347{
348 Q_D(QKeyframeAnimation);
349 d->m_keyframes.push_back(t: keyframe);
350}
351
352/*!
353 Removes a \a keyframe from the animation. If the same QTransform
354 is set as keyframe multiple times, all occurrences are removed.
355 */
356void QKeyframeAnimation::removeKeyframe(Qt3DCore::QTransform *keyframe)
357{
358 Q_D(QKeyframeAnimation);
359 d->m_keyframes.removeAll(t: keyframe);
360}
361
362QString QKeyframeAnimation::targetName() const
363{
364 Q_D(const QKeyframeAnimation);
365 return d->m_targetName;
366}
367
368QEasingCurve QKeyframeAnimation::easing() const
369{
370 Q_D(const QKeyframeAnimation);
371 return d->m_easing;
372}
373
374Qt3DCore::QTransform *QKeyframeAnimation::target() const
375{
376 Q_D(const QKeyframeAnimation);
377 return d->m_target;
378}
379
380} // Qt3DAnimation
381
382QT_END_NAMESPACE
383
384#include "moc_qkeyframeanimation.cpp"
385

source code of qt3d/src/animation/frontend/qkeyframeanimation.cpp