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 | } |
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 | */ |
67 | QQmlListProperty<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 | |
80 | void 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 | |
98 | void 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 | |
112 | void 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 | |
133 | QQuick3DNode *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 | |
139 | qsizetype QQuick3DSkin::qmlJointsCount(QQmlListProperty<QQuick3DNode> *list) |
140 | { |
141 | QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object); |
142 | return self->m_joints.size(); |
143 | } |
144 | |
145 | void 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 | */ |
168 | QList<QMatrix4x4> QQuick3DSkin::inverseBindPoses() const |
169 | { |
170 | return m_inverseBindPoses; |
171 | } |
172 | |
173 | void 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 | |
196 | void QQuick3DSkin::markDirty() |
197 | { |
198 | if (!m_dirty) { |
199 | m_dirty = true; |
200 | update(); |
201 | } |
202 | } |
203 | |
204 | |
205 | void QQuick3DSkin::markAllDirty() |
206 | { |
207 | m_dirty = true; |
208 | QQuick3DObject::markAllDirty(); |
209 | } |
210 | |
211 | QSSGRenderGraphObject *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 | |
233 | QT_END_NAMESPACE |
234 | |