1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dskin_p.h"
5#include "qquick3dobject_p.h"
6#include "qquick3dscenemanager_p.h"
7
8#include <QtQuick3DRuntimeRender/private/qssgrendergraphobject_p.h>
9#include <QtQuick3DRuntimeRender/private/qssgrenderskin_p.h>
10
11#include <QtQuick3DUtils/private/qssgutils_p.h>
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype Skin
17 \inherits Object3D
18 \inqmlmodule QtQuick3D
19 \brief Defines a skinning animation.
20
21 A skin defines how a model can be animated using \l {Vertex Skinning}
22 {skeletal animation}. It contains a list of \l {Node}s and an optional list
23 of the Inverse Bind Pose Matrices.
24 Each \l {Node}'s transform becomes a transform of the bone with the
25 corresponding index in the list.
26
27 \qml
28 Skin {
29 id: skin0
30 joints: [
31 node0,
32 node1,
33 node2
34 ]
35 inverseBindPoses: [
36 Qt.matrix4x4(...),
37 Qt.matrix4x4(...),
38 Qt.matrix4x4(...)
39 ]
40 }
41 \endqml
42
43 \note \l {Skeleton} and \l {Joint} will be deprecated.
44*/
45
46QQuick3DSkin::QQuick3DSkin(QQuick3DObject *parent)
47 : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Skin)), parent)
48{
49}
50
51QQuick3DSkin::~QQuick3DSkin()
52{
53}
54
55/*!
56 \qmlproperty List<QtQuick3D::Node> Skin::joints
57
58 This property contains a list of nodes used for a hierarchy of joints.
59 The order in the list becomes the index of the joint, which is used in the
60 \c SkinSemantic \l {QQuick3DGeometry::addAttribute}{custom geometry attribute}.
61
62 \note A value 'undefined' will be ignored and if a node which doesn't exist is
63 described, the result is unpredictable.
64
65 \sa {QQuick3DGeometry::addAttribute}, {Qt Quick 3D - Simple Skinning Example}
66*/
67QQmlListProperty<QQuick3DNode> QQuick3DSkin::joints()
68{
69 return QQmlListProperty<QQuick3DNode>(this,
70 nullptr,
71 QQuick3DSkin::qmlAppendJoint,
72 QQuick3DSkin::qmlJointsCount,
73 QQuick3DSkin::qmlJointAt,
74 QQuick3DSkin::qmlClearJoints);
75}
76
77#define POS4BONETRANS(x) (sizeof(float) * 16 * (x) * 2)
78#define POS4BONENORM(x) (sizeof(float) * 16 * ((x) * 2 + 1))
79
80void QQuick3DSkin::onJointChanged(QQuick3DNode *node)
81{
82 for (int i = 0; i < m_joints.size(); ++i) {
83 if (m_joints.at(i) == node) {
84 QMatrix4x4 jointGlobal = m_joints.at(i)->sceneTransform();
85 if (m_inverseBindPoses.size() > i)
86 jointGlobal *= m_inverseBindPoses.at(i);
87 memcpy(dest: m_boneData.data() + POS4BONETRANS(i),
88 src: reinterpret_cast<const void *>(jointGlobal.constData()),
89 n: sizeof(float) * 16);
90 memcpy(dest: m_boneData.data() + POS4BONENORM(i),
91 src: reinterpret_cast<const void *>(QMatrix4x4(jointGlobal.normalMatrix()).constData()),
92 n: sizeof(float) * 11);
93 markDirty();
94 }
95 }
96}
97
98void QQuick3DSkin::onJointDestroyed(QObject *object)
99{
100 for (int i = 0; i < m_joints.size(); ++i) {
101 if (m_joints.at(i) == object) {
102 m_joints.removeAt(i);
103 // remove both transform and normal together
104 m_boneData.remove(POS4BONETRANS(i),
105 len: sizeof(float) * 16 * 2);
106 markDirty();
107 break;
108 }
109 }
110}
111
112void QQuick3DSkin::qmlAppendJoint(QQmlListProperty<QQuick3DNode> *list, QQuick3DNode *joint)
113{
114 if (joint == nullptr)
115 return;
116 QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
117 int index = self->m_joints.size();
118 self->m_joints.push_back(t: joint);
119 QMatrix4x4 jointGlobal = joint->sceneTransform();
120 if (index < self->m_inverseBindPoses.size())
121 jointGlobal *= self->m_inverseBindPoses.at(i: index);
122 self->m_boneData.append(s: reinterpret_cast<const char *>(jointGlobal.constData()),
123 len: sizeof(float) * 16);
124 self->m_boneData.append(s: reinterpret_cast<const char *>(QMatrix4x4(jointGlobal.normalMatrix()).constData()),
125 len: sizeof(float) * 16);
126 self->markDirty();
127
128 connect(sender: joint, signal: &QQuick3DNode::sceneTransformChanged, context: self,
129 slot: [self, joint]() { self->onJointChanged(node: joint); });
130 connect(sender: joint, signal: &QQuick3DNode::destroyed, context: self, slot: &QQuick3DSkin::onJointDestroyed);
131}
132
133QQuick3DNode *QQuick3DSkin::qmlJointAt(QQmlListProperty<QQuick3DNode> *list, qsizetype index)
134{
135 QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
136 return self->m_joints.at(i: index);
137}
138
139qsizetype QQuick3DSkin::qmlJointsCount(QQmlListProperty<QQuick3DNode> *list)
140{
141 QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
142 return self->m_joints.size();
143}
144
145void QQuick3DSkin::qmlClearJoints(QQmlListProperty<QQuick3DNode> *list)
146{
147 QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
148 for (const auto &joint : std::as_const(t&: self->m_joints)) {
149 joint->disconnect(receiver: self, SLOT(onJointDestroyed(QObject*)));
150 }
151 self->m_joints.clear();
152 self->m_boneData.clear();
153 self->markDirty();
154}
155
156
157/*!
158 \qmlproperty List<matrix4x4> Skin::inverseBindPoses
159
160 This property contains a list of Inverse Bind Pose matrixes used for the
161 skinning animation. Each inverseBindPose matrix means the inverse of the
162 global transform of the corresponding node in \l {Skin::joints},
163 used initially.
164
165 \note This property is an optional property. That is, if some or all of the
166 matrices are not set, identity values will be used.
167*/
168QList<QMatrix4x4> QQuick3DSkin::inverseBindPoses() const
169{
170 return m_inverseBindPoses;
171}
172
173void QQuick3DSkin::setInverseBindPoses(const QList<QMatrix4x4> &poses)
174{
175 if (m_inverseBindPoses == poses)
176 return;
177
178 m_inverseBindPoses = poses;
179
180 for (int i = 0; i < m_joints.size(); ++i) {
181 QMatrix4x4 jointGlobal = m_joints.at(i)->sceneTransform();
182 if (m_inverseBindPoses.size() > i)
183 jointGlobal *= m_inverseBindPoses.at(i);
184 memcpy(dest: m_boneData.data() + POS4BONETRANS(i),
185 src: reinterpret_cast<const void *>(jointGlobal.constData()),
186 n: sizeof(float) * 16);
187 memcpy(dest: m_boneData.data() + POS4BONENORM(i),
188 src: reinterpret_cast<const void *>(QMatrix4x4(jointGlobal.normalMatrix()).constData()),
189 n: sizeof(float) * 11);
190 }
191
192 markDirty();
193 emit inverseBindPosesChanged();
194}
195
196void QQuick3DSkin::markDirty()
197{
198 if (!m_dirty) {
199 m_dirty = true;
200 update();
201 }
202}
203
204
205void QQuick3DSkin::markAllDirty()
206{
207 m_dirty = true;
208 QQuick3DObject::markAllDirty();
209}
210
211QSSGRenderGraphObject *QQuick3DSkin::updateSpatialNode(QSSGRenderGraphObject *node)
212{
213 if (!node) {
214 markAllDirty();
215 node = new QSSGRenderSkin();
216 }
217 QQuick3DObject::updateSpatialNode(node);
218 auto skinNode = static_cast<QSSGRenderSkin *>(node);
219
220 if (m_dirty) {
221 m_dirty = false;
222 const int boneTexWidth = qCeil(v: qSqrt(v: m_joints.size() * 4 * 2));
223 const int textureSizeInBytes = boneTexWidth * boneTexWidth * 16; //NB: Assumes RGBA32F set above (16 bytes per color)
224 m_boneData.resize(size: textureSizeInBytes);
225 skinNode->setSize(QSize(boneTexWidth, boneTexWidth));
226 skinNode->setTextureData(m_boneData);
227 skinNode->boneCount = m_joints.size();
228 }
229
230 return node;
231}
232
233QT_END_NAMESPACE
234

source code of qtquick3d/src/quick3d/qquick3dskin.cpp