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

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