Revision control

Copy as Markdown

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qblendanimationnode_p.h"
#include <QVariant>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
#include <QQuaternion>
#include <QColor>
#include <QRect>
#include <QRectF>
QT_BEGIN_NAMESPACE
/*!
\qmltype BlendAnimationNode
\inherits BlendTreeNode
\nativetype QBlendAnimationNode
\inqmlmodule QtQuick.Timeline.BlendTrees
\ingroup qtqmltypes
\brief A blend tree node that blends between two animation sources.
BlendAnimationNode is a blend tree node that blends between two animation
sources based on a weight value. The weight value can be animated to
dynamically blend between the two animation sources.
*/
/*!
\qmlproperty BlendTreeNode BlendAnimationNode::source1
This property holds the first animation source.
*/
/*!
\qmlproperty BlendTreeNode BlendAnimationNode::source2
This property holds the second animation source.
*/
/*!
\qmlproperty real BlendAnimationNode::weight
This property holds the weight value used to blend between the two animation
sources.
The weight value determines how much of the first animation source is blended
with the second animation source. A weight value of \c 0.0 means the first
animation source is used exclusively, a weight value of \c 1.0 means the
second animation source is used exclusively, and a weight value of \c 0.5 means
both animation sources are blended equally. The default value is \c 0.5.
*/
QBlendAnimationNode::QBlendAnimationNode(QObject *parent)
: QBlendTreeNode(parent)
{
connect(this,
&QBlendAnimationNode::weightChanged,
this,
&QBlendAnimationNode::handleInputFrameDataChanged);
}
QBlendTreeNode *QBlendAnimationNode::source1() const
{
return m_source1;
}
void QBlendAnimationNode::setSource1(QBlendTreeNode *newSource1)
{
if (m_source1 == newSource1)
return;
if (m_source1) {
disconnect(m_source1OutputConnection);
disconnect(m_source1DestroyedConnection);
}
m_source1 = newSource1;
if (m_source1) {
m_source1OutputConnection = connect(m_source1,
&QBlendTreeNode::frameDataChanged,
this,
&QBlendAnimationNode::handleInputFrameDataChanged);
m_source1DestroyedConnection = connect(m_source1,
&QObject::destroyed,
this,
[this] { setSource1(nullptr);});
}
Q_EMIT source1Changed();
}
QBlendTreeNode *QBlendAnimationNode::source2() const
{
return m_source2;
}
void QBlendAnimationNode::setSource2(QBlendTreeNode *newSource2)
{
if (m_source2 == newSource2)
return;
if (m_source2) {
disconnect(m_source2OutputConnection);
disconnect(m_source2DestroyedConnection);
}
m_source2 = newSource2;
if (m_source2) {
m_source2OutputConnection = connect(m_source2,
&QBlendTreeNode::frameDataChanged,
this,
&QBlendAnimationNode::handleInputFrameDataChanged);
m_source2DestroyedConnection = connect(m_source2,
&QObject::destroyed,
this,
[this] { setSource2(nullptr);});
}
Q_EMIT source2Changed();
}
qreal QBlendAnimationNode::weight() const
{
return m_weight;
}
void QBlendAnimationNode::setWeight(qreal newWeight)
{
if (qFuzzyCompare(m_weight, newWeight))
return;
m_weight = newWeight;
Q_EMIT weightChanged();
}
static QVariant lerp(const QVariant &first, const QVariant &second, float weight)
{
// Don't bother with weights if there is no data (for now)
if (first.isNull())
return second;
else if (second.isNull())
return first;
const QMetaType type = first.metaType();
switch (type.id()) {
case QMetaType::Bool:
return QVariant((1.0f - weight) * first.toBool() + weight * second.toBool() >= 0.5f);
case QMetaType::Int:
return QVariant((1.0f - weight) * first.toInt() + weight * second.toInt());
case QMetaType::Float:
return QVariant((1.0f - weight) * first.toFloat() + weight * second.toFloat());
case QMetaType::Double:
return QVariant((1.0 - weight) * first.toDouble() + weight * second.toDouble());
case QMetaType::QVector2D: {
QVector2D firstVec = first.value<QVector2D>();
QVector2D secondVec = second.value<QVector2D>();
return QVariant::fromValue<QVector2D>(firstVec * (1.0f - weight) + secondVec * weight);
}
case QMetaType::QVector3D: {
QVector3D firstVec = first.value<QVector3D>();
QVector3D secondVec = second.value<QVector3D>();
return QVariant::fromValue<QVector3D>(firstVec * (1.0f - weight) + secondVec * weight);
}
case QMetaType::QVector4D: {
QVector4D firstVec = first.value<QVector4D>();
QVector4D secondVec = second.value<QVector4D>();
return QVariant::fromValue<QVector4D>(firstVec * (1.0f - weight) + secondVec * weight);
}
case QMetaType::QQuaternion: {
QQuaternion firstQuat = first.value<QQuaternion>();
QQuaternion secondQuat = second.value<QQuaternion>();
return QVariant::fromValue<QQuaternion>(QQuaternion::nlerp(firstQuat, secondQuat, weight));
}
case QMetaType::QColor: {
QColor firstColor = first.value<QColor>();
QColor secondColor = second.value<QColor>();
int r = (1.0f - weight) * firstColor.red() + weight * secondColor.red();
int g = (1.0f - weight) * firstColor.green() + weight * secondColor.green();
int b = (1.0f - weight) * firstColor.blue() + weight * secondColor.blue();
int a = (1.0f - weight) * firstColor.alpha() + weight * secondColor.alpha();
return QVariant::fromValue<QColor>(QColor(r, g, b, a));
}
case QMetaType::QRect: {
QRect firstRect = first.value<QRect>();
QRect secondRect = second.value<QRect>();
int x = (1.0f - weight) * firstRect.x() + weight * secondRect.x();
int y = (1.0f - weight) * firstRect.y() + weight * secondRect.y();
int width = (1.0f - weight) * firstRect.width() + weight * secondRect.width();
int height = (1.0f - weight) * firstRect.height() + weight * secondRect.height();
return QVariant::fromValue<QRect>(QRect(x, y, width, height));
}
case QMetaType::QRectF: {
QRectF firstRectF = first.value<QRectF>();
QRectF secondRectF = second.value<QRectF>();
qreal x = (1.0 - weight) * firstRectF.x() + weight * secondRectF.x();
qreal y = (1.0 - weight) * firstRectF.y() + weight * secondRectF.y();
qreal width = (1.0 - weight) * firstRectF.width() + weight * secondRectF.width();
qreal height = (1.0 - weight) * firstRectF.height() + weight * secondRectF.height();
return QVariant::fromValue<QRectF>(QRectF(x, y, width, height));
}
default:
// Unsupported type, return an invalid QVariant
return QVariant();
}
}
void QBlendAnimationNode::handleInputFrameDataChanged()
{
const QHash<QQmlProperty, QVariant> &frameData1 = m_source1 ? m_source1->frameData() : QHash<QQmlProperty, QVariant>();
const QHash<QQmlProperty, QVariant> &frameData2 = m_source2 ? m_source2->frameData() : QHash<QQmlProperty, QVariant>();
// Do the LERP blending here
if (m_weight <= 0.0) {
// all source1
m_frameData = frameData1;
} else if (m_weight >= 1.0) {
// all source2
m_frameData = frameData2;
} else {
// is a mix
QHash<QQmlProperty, QPair<QVariant, QVariant>> allData;
const auto &keys1 = frameData1.keys();
for (const auto &property : keys1)
allData.insert(property, QPair<QVariant, QVariant>(frameData1[property], QVariant()));
const auto &keys2 = frameData2.keys();
for (const auto &property : keys2) {
// first check if property is already in all data, and if so modify the pair to include this value
if (allData.contains(property)) {
allData[property].second = frameData2[property];
} else {
allData.insert(property, QPair<QVariant, QVariant>(QVariant(), frameData2[property]));
}
}
QHash<QQmlProperty, QVariant> newFrameData;
const auto &keys = allData.keys();
for (const auto &property : keys) {
const auto &dataPair = allData[property];
newFrameData.insert(property, lerp(dataPair.first, dataPair.second, m_weight));
}
m_frameData = newFrameData;
}
Q_EMIT frameDataChanged();
}
QT_END_NAMESPACE