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 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*/
71QQmlListProperty<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
84void 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
113QQuick3DNode *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
119qsizetype QQuick3DSkin::qmlJointsCount(QQmlListProperty<QQuick3DNode> *list)
120{
121 QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
122 return self->m_joints.size();
123}
124
125void 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*/
153QList<QMatrix4x4> QQuick3DSkin::inverseBindPoses() const
154{
155 return m_inverseBindPoses;
156}
157
158void 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
170QSSGRenderGraphObject *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
222QT_END_NAMESPACE
223

Provided by KDAB

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

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