Revision control

++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qquick3dskin_p.h"
++ #include "qquick3dobject_p.h"
++ #include "qquick3dscenemanager_p.h"
++
++ #include <QtQuick3DRuntimeRender/private/qssgrendergraphobject_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderskin_p.h>
++
++ #include <QtQuick3DUtils/private/qssgutils_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype Skin
++ \inherits Object3D
++ \inqmlmodule QtQuick3D
++ \brief Defines a skinning animation.
++
++ A skin defines how a model can be animated using \l {Vertex Skinning}
++ {skeletal animation}. It contains a list of \l {Node}s and an optional list
++ of the Inverse Bind Pose Matrices.
++ Each \l {Node}'s transform becomes a transform of the bone with the
++ corresponding index in the list.
++
++ \qml
++ Skin {
++ id: skin0
++ joints: [
++ node0,
++ node1,
++ node2
++ ]
++ inverseBindPoses: [
++ Qt.matrix4x4(...),
++ Qt.matrix4x4(...),
++ Qt.matrix4x4(...)
++ ]
++ }
++ \endqml
++
++ \note \l {Skeleton} and \l {Joint} will be deprecated.
++ */
++
++ QQuick3DSkin::QQuick3DSkin(QQuick3DObject *parent)
++ : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Skin)), parent)
++ {
++ }
++
++ QQuick3DSkin::~QQuick3DSkin()
++ {
++ for (auto &conn : m_jointsConnections) {
++ disconnect(conn.first);
++ disconnect(conn.second);
++ }
++ }
++
++ /*!
++ \qmlproperty List<QtQuick3D::Node> Skin::joints
++
++ This property contains a list of nodes used for a hierarchy of joints.
++ The order in the list becomes the index of the joint, which is used in the
++ \c SkinSemantic \l {QQuick3DGeometry::addAttribute}{custom geometry attribute}.
++
++ \note A value 'undefined' will be ignored and if a node which doesn't exist is
++ described, the result is unpredictable.
++
++ \sa {QQuick3DGeometry::addAttribute}, {Qt Quick 3D - Simple Skinning Example}
++ */
++ QQmlListProperty<QQuick3DNode> QQuick3DSkin::joints()
++ {
++ return QQmlListProperty<QQuick3DNode>(this,
++ nullptr,
++ QQuick3DSkin::qmlAppendJoint,
++ QQuick3DSkin::qmlJointsCount,
++ QQuick3DSkin::qmlJointAt,
++ QQuick3DSkin::qmlClearJoints);
++ }
++
++ #define POS4BONETRANS(x) (sizeof(float) * 16 * (x) * 2)
++ #define POS4BONENORM(x) (sizeof(float) * 16 * ((x) * 2 + 1))
++
++ void QQuick3DSkin::qmlAppendJoint(QQmlListProperty<QQuick3DNode> *list, QQuick3DNode *joint)
++ {
++ if (joint == nullptr)
++ return;
++ QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
++ if (!self->m_jointsConnections.contains(joint)) {
++ self->m_jointsConnections[joint] =
++ std::make_pair(
++ connect(
++ joint, &QQuick3DNode::sceneTransformChanged,
++ self, [self, joint]() {
++ self->m_dirtyJoints.insert(joint);
++ self->update();
++ }),
++ connect(
++ joint, &QQuick3DNode::destroyed,
++ self, [self, joint]() {
++ self->m_dirtyJoints.remove(joint);
++ self->m_removedJoints.insert(joint);
++ self->update();
++ })
++ );
++ }
++
++ self->m_joints.push_back(joint);
++ self->m_dirtyJoints.insert(joint);
++ self->update();
++ }
++
++ QQuick3DNode *QQuick3DSkin::qmlJointAt(QQmlListProperty<QQuick3DNode> *list, qsizetype index)
++ {
++ QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
++ return self->m_joints.at(index);
++ }
++
++ qsizetype QQuick3DSkin::qmlJointsCount(QQmlListProperty<QQuick3DNode> *list)
++ {
++ QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
++ return self->m_joints.size();
++ }
++
++ void QQuick3DSkin::qmlClearJoints(QQmlListProperty<QQuick3DNode> *list)
++ {
++ QQuick3DSkin *self = static_cast<QQuick3DSkin *>(list->object);
++ for (auto &conn : self->m_jointsConnections) {
++ disconnect(conn.first);
++ disconnect(conn.second);
++ }
++ self->m_jointsConnections.clear();
++
++ self->m_joints.clear();
++ self->m_boneData.clear();
++ self->m_dirtyJoints.clear();
++ self->m_removedJoints.clear();
++ self->update();
++ }
++
++
++ /*!
++ \qmlproperty List<matrix4x4> Skin::inverseBindPoses
++
++ This property contains a list of Inverse Bind Pose matrixes used for the
++ skinning animation. Each inverseBindPose matrix means the inverse of the
++ global transform of the corresponding node in \l {Skin::joints},
++ used initially.
++
++ \note This property is an optional property. That is, if some or all of the
++ matrices are not set, identity values will be used.
++ */
++ QList<QMatrix4x4> QQuick3DSkin::inverseBindPoses() const
++ {
++ return m_inverseBindPoses;
++ }
++
++ void QQuick3DSkin::setInverseBindPoses(const QList<QMatrix4x4> &poses)
++ {
++ if (m_inverseBindPoses == poses)
++ return;
++
++ m_updatedByNewInverseBindPoses = qMax(poses.size(), m_inverseBindPoses.size());
++ m_inverseBindPoses = poses;
++
++ emit inverseBindPosesChanged();
++ update();
++ }
++
++ QSSGRenderGraphObject *QQuick3DSkin::updateSpatialNode(QSSGRenderGraphObject *node)
++ {
++ if (!node) {
++ markAllDirty();
++ node = new QSSGRenderSkin();
++ }
++ QQuick3DObject::updateSpatialNode(node);
++ auto skinNode = static_cast<QSSGRenderSkin *>(node);
++
++ if (!m_removedJoints.empty()) {
++ for (int i = m_joints.size() - 1; i >= 0; --i) {
++ const auto &joint = m_joints.at(i);
++ if (m_removedJoints.contains(joint)) {
++ m_joints.removeAt(i);
++ m_boneData.remove(POS4BONETRANS(i),
++ sizeof(float) * 16 * 2);
++ }
++ }
++ m_removedJoints.clear();
++ }
++
++ if (skinNode->boneCount != quint32(m_joints.size())) {
++ skinNode->boneCount = quint32(m_joints.size());
++ const int boneTexWidth = qCeil(qSqrt(skinNode->boneCount * 4 * 2));
++ const int textureSizeInBytes = boneTexWidth * boneTexWidth * 16; //NB: Assumes RGBA32F set above (16 bytes per color)
++ m_boneData.resize(textureSizeInBytes);
++ skinNode->setSize(QSize(boneTexWidth, boneTexWidth));
++ }
++
++ if (m_updatedByNewInverseBindPoses > 0 || !m_dirtyJoints.empty()) {
++ for (int i = 0; i < m_joints.size(); ++i) {
++ const auto &joint = m_joints.at(i);
++ if (i < m_updatedByNewInverseBindPoses || m_dirtyJoints.contains(joint)) {
++ QMatrix4x4 jointGlobal = joint->sceneTransform();
++ if (m_inverseBindPoses.size() > i)
++ jointGlobal *= m_inverseBindPoses.at(i);
++ memcpy(m_boneData.data() + POS4BONETRANS(i),
++ reinterpret_cast<const void *>(jointGlobal.constData()),
++ sizeof(float) * 16);
++ memcpy(m_boneData.data() + POS4BONENORM(i),
++ reinterpret_cast<const void *>(QMatrix4x4(jointGlobal.normalMatrix()).constData()),
++ sizeof(float) * 11);
++ }
++ }
++ m_dirtyJoints.clear();
++ m_updatedByNewInverseBindPoses = 0;
++ }
++
++ skinNode->setTextureData(m_boneData);
++ return node;
++ }
++
++ QT_END_NAMESPACE