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 | |
13 | QT_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 | |
46 | QQuick3DSkin::QQuick3DSkin(QQuick3DObject *parent) |
47 | : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Skin)), parent) |
48 | { |
49 | } |
50 | |
51 | QQuick3DSkin::~QQuick3DSkin() |
52 | { |
53 | for (const auto &conn : m_jointsConnections) { |
54 | disconnect(conn.first); |
55 | disconnect(conn.second); |
56 | } |
57 | } |
58 | |
59 | /*! |
60 | \qmlproperty List<QtQuick3D::Node> Skin::joints |
61 | |
62 | This property contains a list of nodes used for a hierarchy of joints. |
63 | The order in the list becomes the index of the joint, which is used in the |
64 | \c SkinSemantic \l {QQuick3DGeometry::addAttribute}{custom geometry attribute}. |
65 | |
66 | \note A value 'undefined' will be ignored and if a node which doesn't exist is |
67 | described, the result is unpredictable. |
68 | |
69 | \sa {QQuick3DGeometry::addAttribute}, {Qt Quick 3D - Simple Skinning Example} |
70 | */ |
71 | QQmlListProperty<QQuick3DNode> QQuick3DSkin::joints() |
72 | { |
73 | return QQmlListProperty<QQuick3DNode>(this, |
74 | nullptr, |
75 | QQuick3DSkin::qmlAppendJoint, |
76 | QQuick3DSkin::qmlJointsCount, |
77 | QQuick3DSkin::qmlJointAt, |
78 | QQuick3DSkin::qmlClearJoints); |
79 | } |
80 | |
81 | #define POS4BONETRANS(x) (sizeof(float) * 16 * (x) * 2) |
82 | #define POS4BONENORM(x) (sizeof(float) * 16 * ((x) * 2 + 1)) |
83 | |
84 | void QQuick3DSkin::qmlAppendJoint(QQmlListProperty<QQuick3DNode> *list, QQuick3DNode *joint) |
85 | { |
86 | if (joint == nullptr) |
87 | return; |
88 | QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object); |
89 | if (!self->m_jointsConnections.contains(key: joint)) { |
90 | self->m_jointsConnections[joint] = |
91 | std::make_pair( |
92 | x: connect( |
93 | sender: joint, signal: &QQuick3DNode::sceneTransformChanged, |
94 | context: self, slot: [self, joint]() { |
95 | self->m_dirtyJoints.insert(value: joint); |
96 | self->update(); |
97 | }), |
98 | y: connect( |
99 | sender: joint, signal: &QQuick3DNode::destroyed, |
100 | context: self, slot: [self, joint]() { |
101 | self->m_dirtyJoints.remove(value: joint); |
102 | self->m_removedJoints.insert(value: joint); |
103 | self->update(); |
104 | }) |
105 | ); |
106 | } |
107 | |
108 | self->m_joints.push_back(t: joint); |
109 | self->m_dirtyJoints.insert(value: joint); |
110 | self->update(); |
111 | } |
112 | |
113 | QQuick3DNode *QQuick3DSkin::qmlJointAt(QQmlListProperty<QQuick3DNode> *list, qsizetype index) |
114 | { |
115 | QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object); |
116 | return self->m_joints.at(i: index); |
117 | } |
118 | |
119 | qsizetype QQuick3DSkin::qmlJointsCount(QQmlListProperty<QQuick3DNode> *list) |
120 | { |
121 | QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object); |
122 | return self->m_joints.size(); |
123 | } |
124 | |
125 | void QQuick3DSkin::qmlClearJoints(QQmlListProperty<QQuick3DNode> *list) |
126 | { |
127 | QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object); |
128 | for (const auto &conn : self->m_jointsConnections) { |
129 | disconnect(conn.first); |
130 | disconnect(conn.second); |
131 | } |
132 | self->m_jointsConnections.clear(); |
133 | |
134 | self->m_joints.clear(); |
135 | self->m_boneData.clear(); |
136 | self->m_dirtyJoints.clear(); |
137 | self->m_removedJoints.clear(); |
138 | self->update(); |
139 | } |
140 | |
141 | |
142 | /*! |
143 | \qmlproperty List<matrix4x4> Skin::inverseBindPoses |
144 | |
145 | This property contains a list of Inverse Bind Pose matrixes used for the |
146 | skinning animation. Each inverseBindPose matrix means the inverse of the |
147 | global transform of the corresponding node in \l {Skin::joints}, |
148 | used initially. |
149 | |
150 | \note This property is an optional property. That is, if some or all of the |
151 | matrices are not set, identity values will be used. |
152 | */ |
153 | QList<QMatrix4x4> QQuick3DSkin::inverseBindPoses() const |
154 | { |
155 | return m_inverseBindPoses; |
156 | } |
157 | |
158 | void QQuick3DSkin::setInverseBindPoses(const QList<QMatrix4x4> &poses) |
159 | { |
160 | if (m_inverseBindPoses == poses) |
161 | return; |
162 | |
163 | m_updatedByNewInverseBindPoses = qMax(a: poses.size(), b: m_inverseBindPoses.size()); |
164 | m_inverseBindPoses = poses; |
165 | |
166 | emit inverseBindPosesChanged(); |
167 | update(); |
168 | } |
169 | |
170 | QSSGRenderGraphObject *QQuick3DSkin::updateSpatialNode(QSSGRenderGraphObject *node) |
171 | { |
172 | if (!node) { |
173 | markAllDirty(); |
174 | node = new QSSGRenderSkin(); |
175 | } |
176 | QQuick3DObject::updateSpatialNode(node); |
177 | auto skinNode = static_cast<QSSGRenderSkin *>(node); |
178 | |
179 | if (!m_removedJoints.empty()) { |
180 | for (int i = m_joints.size() - 1; i >= 0; --i) { |
181 | const auto &joint = m_joints.at(i); |
182 | if (m_removedJoints.contains(value: joint)) { |
183 | m_joints.removeAt(i); |
184 | m_boneData.remove(POS4BONETRANS(i), |
185 | len: sizeof(float) * 16 * 2); |
186 | } |
187 | } |
188 | m_removedJoints.clear(); |
189 | } |
190 | |
191 | if (skinNode->boneCount != quint32(m_joints.size())) { |
192 | skinNode->boneCount = quint32(m_joints.size()); |
193 | const int boneTexWidth = qCeil(v: qSqrt(v: skinNode->boneCount * 4 * 2)); |
194 | const int textureSizeInBytes = boneTexWidth * boneTexWidth * 16; //NB: Assumes RGBA32F set above (16 bytes per color) |
195 | m_boneData.resize(size: textureSizeInBytes); |
196 | skinNode->setSize(QSize(boneTexWidth, boneTexWidth)); |
197 | } |
198 | |
199 | if (m_updatedByNewInverseBindPoses > 0 || !m_dirtyJoints.empty()) { |
200 | for (int i = 0; i < m_joints.size(); ++i) { |
201 | const auto &joint = m_joints.at(i); |
202 | if (i < m_updatedByNewInverseBindPoses || m_dirtyJoints.contains(value: joint)) { |
203 | QMatrix4x4 jointGlobal = joint->sceneTransform(); |
204 | if (m_inverseBindPoses.size() > i) |
205 | jointGlobal *= m_inverseBindPoses.at(i); |
206 | memcpy(dest: m_boneData.data() + POS4BONETRANS(i), |
207 | src: reinterpret_cast<const void *>(jointGlobal.constData()), |
208 | n: sizeof(float) * 16); |
209 | memcpy(dest: m_boneData.data() + POS4BONENORM(i), |
210 | src: reinterpret_cast<const void *>(QMatrix4x4(jointGlobal.normalMatrix()).constData()), |
211 | n: sizeof(float) * 11); |
212 | } |
213 | } |
214 | m_dirtyJoints.clear(); |
215 | m_updatedByNewInverseBindPoses = 0; |
216 | } |
217 | |
218 | skinNode->setTextureData(m_boneData); |
219 | return node; |
220 | } |
221 | |
222 | QT_END_NAMESPACE |
223 | |