1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qblendanimationnode_p.h"
5
6#include <QVariant>
7#include <QVector2D>
8#include <QVector3D>
9#include <QVector4D>
10#include <QQuaternion>
11#include <QColor>
12#include <QRect>
13#include <QRectF>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \qmltype BlendAnimationNode
19 \inherits QBlendTreeNode
20 \nativetype QBlendAnimationNode
21 \inqmlmodule QtQuick.Timeline.BlendTrees
22 \ingroup qtqmltypes
23
24 \brief A blend tree node that blends between two animation sources.
25
26 BlendAnimationNode is a blend tree node that blends between two animation
27 sources based on a weight value. The weight value can be animated to
28 dynamically blend between the two animation sources.
29*/
30
31/*!
32 \qmlproperty BlendTreeNode BlendAnimationNode::source1
33
34 This property holds the first animation source.
35*/
36
37/*!
38 \qmlproperty BlendTreeNode BlendAnimationNode::source2
39
40 This property holds the second animation source.
41*/
42
43/*!
44 \qmlproperty real BlendAnimationNode::weight
45
46 This property holds the weight value used to blend between the two animation
47 sources.
48 The weight value determines how much of the first animation source is blended
49 with the second animation source. A weight value of \c 0.0 means the first
50 animation source is used exclusively, a weight value of \c 1.0 means the
51 second animation source is used exclusively, and a weight value of \c 0.5 means
52 both animation sources are blended equally. The default value is \c 0.5.
53*/
54
55QBlendAnimationNode::QBlendAnimationNode(QObject *parent)
56 : QBlendTreeNode(parent)
57{
58 connect(sender: this,
59 signal: &QBlendAnimationNode::weightChanged,
60 context: this,
61 slot: &QBlendAnimationNode::handleInputFrameDataChanged);
62}
63
64QBlendTreeNode *QBlendAnimationNode::source1() const
65{
66 return m_source1;
67}
68
69void QBlendAnimationNode::setSource1(QBlendTreeNode *newSource1)
70{
71 if (m_source1 == newSource1)
72 return;
73
74 if (m_source1) {
75 disconnect(m_source1OutputConnection);
76 disconnect(m_source1DestroyedConnection);
77 }
78
79 m_source1 = newSource1;
80
81 if (m_source1) {
82 m_source1OutputConnection = connect(sender: m_source1,
83 signal: &QBlendTreeNode::frameDataChanged,
84 context: this,
85 slot: &QBlendAnimationNode::handleInputFrameDataChanged);
86 m_source1DestroyedConnection = connect(sender: m_source1,
87 signal: &QObject::destroyed,
88 context: this,
89 slot: [this] { setSource1(nullptr);});
90 }
91 Q_EMIT source1Changed();
92}
93
94QBlendTreeNode *QBlendAnimationNode::source2() const
95{
96 return m_source2;
97}
98
99void QBlendAnimationNode::setSource2(QBlendTreeNode *newSource2)
100{
101 if (m_source2 == newSource2)
102 return;
103
104 if (m_source2) {
105 disconnect(m_source2OutputConnection);
106 disconnect(m_source2DestroyedConnection);
107 }
108
109 m_source2 = newSource2;
110
111 if (m_source2) {
112 m_source2OutputConnection = connect(sender: m_source2,
113 signal: &QBlendTreeNode::frameDataChanged,
114 context: this,
115 slot: &QBlendAnimationNode::handleInputFrameDataChanged);
116 m_source2DestroyedConnection = connect(sender: m_source2,
117 signal: &QObject::destroyed,
118 context: this,
119 slot: [this] { setSource2(nullptr);});
120 }
121
122 Q_EMIT source2Changed();
123}
124
125qreal QBlendAnimationNode::weight() const
126{
127 return m_weight;
128}
129
130void QBlendAnimationNode::setWeight(qreal newWeight)
131{
132 if (qFuzzyCompare(p1: m_weight, p2: newWeight))
133 return;
134 m_weight = newWeight;
135 Q_EMIT weightChanged();
136}
137
138static QVariant lerp(const QVariant &first, const QVariant &second, float weight)
139{
140 // Don't bother with weights if there is no data (for now)
141 if (first.isNull())
142 return second;
143 else if (second.isNull())
144 return first;
145
146 const QMetaType type = first.metaType();
147 switch (type.id()) {
148 case QMetaType::Bool:
149 return QVariant((1.0f - weight) * first.toBool() + weight * second.toBool() >= 0.5f);
150 case QMetaType::Int:
151 return QVariant((1.0f - weight) * first.toInt() + weight * second.toInt());
152 case QMetaType::Float:
153 return QVariant((1.0f - weight) * first.toFloat() + weight * second.toFloat());
154 case QMetaType::Double:
155 return QVariant((1.0 - weight) * first.toDouble() + weight * second.toDouble());
156 case QMetaType::QVector2D: {
157 QVector2D firstVec = first.value<QVector2D>();
158 QVector2D secondVec = second.value<QVector2D>();
159 return QVariant::fromValue<QVector2D>(value: firstVec * (1.0f - weight) + secondVec * weight);
160 }
161 case QMetaType::QVector3D: {
162 QVector3D firstVec = first.value<QVector3D>();
163 QVector3D secondVec = second.value<QVector3D>();
164 return QVariant::fromValue<QVector3D>(value: firstVec * (1.0f - weight) + secondVec * weight);
165 }
166 case QMetaType::QVector4D: {
167 QVector4D firstVec = first.value<QVector4D>();
168 QVector4D secondVec = second.value<QVector4D>();
169 return QVariant::fromValue<QVector4D>(value: firstVec * (1.0f - weight) + secondVec * weight);
170 }
171 case QMetaType::QQuaternion: {
172 QQuaternion firstQuat = first.value<QQuaternion>();
173 QQuaternion secondQuat = second.value<QQuaternion>();
174 return QVariant::fromValue<QQuaternion>(value: QQuaternion::nlerp(q1: firstQuat, q2: secondQuat, t: weight));
175 }
176 case QMetaType::QColor: {
177 QColor firstColor = first.value<QColor>();
178 QColor secondColor = second.value<QColor>();
179 int r = (1.0f - weight) * firstColor.red() + weight * secondColor.red();
180 int g = (1.0f - weight) * firstColor.green() + weight * secondColor.green();
181 int b = (1.0f - weight) * firstColor.blue() + weight * secondColor.blue();
182 int a = (1.0f - weight) * firstColor.alpha() + weight * secondColor.alpha();
183 return QVariant::fromValue<QColor>(value: QColor(r, g, b, a));
184 }
185 case QMetaType::QRect: {
186 QRect firstRect = first.value<QRect>();
187 QRect secondRect = second.value<QRect>();
188 int x = (1.0f - weight) * firstRect.x() + weight * secondRect.x();
189 int y = (1.0f - weight) * firstRect.y() + weight * secondRect.y();
190 int width = (1.0f - weight) * firstRect.width() + weight * secondRect.width();
191 int height = (1.0f - weight) * firstRect.height() + weight * secondRect.height();
192 return QVariant::fromValue<QRect>(value: QRect(x, y, width, height));
193 }
194 case QMetaType::QRectF: {
195 QRectF firstRectF = first.value<QRectF>();
196 QRectF secondRectF = second.value<QRectF>();
197 qreal x = (1.0 - weight) * firstRectF.x() + weight * secondRectF.x();
198 qreal y = (1.0 - weight) * firstRectF.y() + weight * secondRectF.y();
199 qreal width = (1.0 - weight) * firstRectF.width() + weight * secondRectF.width();
200 qreal height = (1.0 - weight) * firstRectF.height() + weight * secondRectF.height();
201 return QVariant::fromValue<QRectF>(value: QRectF(x, y, width, height));
202 }
203 default:
204 // Unsupported type, return an invalid QVariant
205 return QVariant();
206 }
207
208}
209
210void QBlendAnimationNode::handleInputFrameDataChanged()
211{
212 const QHash<QQmlProperty, QVariant> &frameData1 = m_source1 ? m_source1->frameData() : QHash<QQmlProperty, QVariant>();
213 const QHash<QQmlProperty, QVariant> &frameData2 = m_source2 ? m_source2->frameData() : QHash<QQmlProperty, QVariant>();
214
215 // Do the LERP blending here
216 if (m_weight <= 0.0) {
217 // all source1
218 m_frameData = frameData1;
219 } else if (m_weight >= 1.0) {
220 // all source2
221 m_frameData = frameData2;
222 } else {
223 // is a mix
224 QHash<QQmlProperty, QPair<QVariant, QVariant>> allData;
225 const auto &keys1 = frameData1.keys();
226 for (const auto &property : keys1)
227 allData.insert(key: property, value: QPair<QVariant, QVariant>(frameData1[property], QVariant()));
228 const auto &keys2 = frameData2.keys();
229 for (const auto &property : keys2) {
230 // first check if property is already in all data, and if so modify the pair to include this value
231 if (allData.contains(key: property)) {
232 allData[property].second = frameData2[property];
233 } else {
234 allData.insert(key: property, value: QPair<QVariant, QVariant>(QVariant(), frameData2[property]));
235 }
236 }
237
238 QHash<QQmlProperty, QVariant> newFrameData;
239
240 const auto &keys = allData.keys();
241 for (const auto &property : keys) {
242 const auto &dataPair = allData[property];
243 newFrameData.insert(key: property, value: lerp(first: dataPair.first, second: dataPair.second, weight: m_weight));
244 }
245 m_frameData = newFrameData;
246 }
247
248 Q_EMIT frameDataChanged();
249}
250
251QT_END_NAMESPACE
252

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtquicktimeline/src/timeline/blendtrees/qblendanimationnode.cpp