diff options
author | Antti Määttä <antti.maatta@qt.io> | 2016-09-16 14:10:01 +0300 |
---|---|---|
committer | Sean Harmer <sean.harmer@kdab.com> | 2017-01-31 11:41:59 +0000 |
commit | 59f8fec8a41606b3185fe3a4e276978e3e1ed5ef (patch) | |
tree | f939c83813794a78157bf488a459ac57fe9ab1c2 /src/plugins/sceneparsers | |
parent | 79ec93e56d9067674c108544ef3af041644318d7 (diff) |
Animation support for Qt3D
Modded assimp loader to load animations and to load submeshes into child
entities. Added keyframeanimation for node animations and morphing mesh.
Also added animation group for controlling multiple nodes as one animation
and animation controller to select and play animations.
Assimp loader adds QKeyFrameAnimations targeting an entity as child
objects of that entity, the QAnimationController finds them when the entity
is set and creates QAnimationGroups from based on the animation name.
Change-Id: I7e2e7a4479af49f8023b4a359b2b3118efdaa0da
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
Diffstat (limited to 'src/plugins/sceneparsers')
-rw-r--r-- | src/plugins/sceneparsers/assimp/assimpimporter.cpp | 349 | ||||
-rw-r--r-- | src/plugins/sceneparsers/assimp/assimpimporter.h | 8 |
2 files changed, 350 insertions, 7 deletions
diff --git a/src/plugins/sceneparsers/assimp/assimpimporter.cpp b/src/plugins/sceneparsers/assimp/assimpimporter.cpp index dba13a291..66c219fe9 100644 --- a/src/plugins/sceneparsers/assimp/assimpimporter.cpp +++ b/src/plugins/sceneparsers/assimp/assimpimporter.cpp @@ -51,9 +51,12 @@ #include <Qt3DRender/qattribute.h> #include <Qt3DRender/qtexture.h> #include <Qt3DRender/qtextureimagedatagenerator.h> +#include <Qt3DExtras/qmorphphongmaterial.h> #include <Qt3DExtras/qdiffusemapmaterial.h> #include <Qt3DExtras/qdiffusespecularmapmaterial.h> #include <Qt3DExtras/qphongmaterial.h> +#include <Qt3DExtras/qkeyframeanimation.h> +#include <Qt3DExtras/qmorphinganimation.h> #include <QFileInfo> #include <QColor> #include <qmath.h> @@ -413,7 +416,15 @@ Qt3DCore::QEntity *AssimpImporter::scene(const QString &id) // Builds the Qt3D scene using the Assimp aiScene // and the various dicts filled previously by parse - return node(rootNode); + Qt3DCore::QEntity *n = node(rootNode); + if (m_scene->m_animations.size() > 0) { + qWarning() << "No target found for " << m_scene->m_animations.size() << " animations!"; + + for (Qt3DExtras::QKeyframeAnimation *anim : m_scene->m_animations) + delete anim; + m_scene->m_animations.clear(); + } + return n; } /*! @@ -429,6 +440,17 @@ Qt3DCore::QEntity *AssimpImporter::node(const QString &id) return node(n); } +template <typename T> +void findAnimationsForNode(QVector<T *> &animations, QVector<T *> &result, const QString &name) +{ + for (T *anim : animations) { + if (anim->targetName() == name) { + result.push_back(anim); + animations.removeAll(anim); + } + } +} + /*! * Returns a Node from an Assimp aiNode \a node. */ @@ -442,13 +464,54 @@ Qt3DCore::QEntity *AssimpImporter::node(aiNode *node) // Add Meshes to the node for (uint i = 0; i < node->mNumMeshes; i++) { uint meshIdx = node->mMeshes[i]; + QMaterial *material = nullptr; QGeometryRenderer *mesh = m_scene->m_meshes[meshIdx]; // mesh material uint materialIndex = m_scene->m_aiScene->mMeshes[meshIdx]->mMaterialIndex; + if (m_scene->m_materials.contains(materialIndex)) - entityNode->addComponent(m_scene->m_materials[materialIndex]); - // mesh - entityNode->addComponent(mesh); + material = m_scene->m_materials[materialIndex]; + + QList<Qt3DExtras::QMorphingAnimation *> morphingAnimations + = mesh->findChildren<Qt3DExtras::QMorphingAnimation *>(); + if (morphingAnimations.size() > 0) { + material = new Qt3DExtras::QMorphPhongMaterial(entityNode); + + QVector<Qt3DExtras::QMorphingAnimation *> animations; + findAnimationsForNode<Qt3DExtras::QMorphingAnimation>(m_scene->m_morphAnimations, + animations, + aiStringToQString(node->mName)); + const auto morphTargetList = morphingAnimations.at(0)->morphTargetList(); + for (Qt3DExtras::QMorphingAnimation *anim : animations) { + anim->setParent(entityNode); + anim->setTarget(mesh); + anim->setMorphTargets(morphTargetList); + } + + for (int j = 0; j < animations.size(); ++j) { + QObject::connect(animations[j], &Qt3DExtras::QMorphingAnimation::interpolatorChanged, + (Qt3DExtras::QMorphPhongMaterial *)material, + &Qt3DExtras::QMorphPhongMaterial::setInterpolator); + } + morphingAnimations[0]->deleteLater(); + } + + if (node->mNumMeshes == 1) { + if (material) + entityNode->addComponent(material); + // mesh + entityNode->addComponent(mesh); + } else { + QEntity *childEntity = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity"); + if (material) + childEntity->addComponent(material); + childEntity->addComponent(mesh); + childEntity->setParent(entityNode); + + Qt3DCore::QTransform *transform + = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform"); + childEntity->addComponent(transform); + } } // Add Children to Node @@ -467,6 +530,16 @@ Qt3DCore::QEntity *AssimpImporter::node(aiNode *node) transform->setMatrix(qTransformMatrix); entityNode->addComponent(transform); + QVector<Qt3DExtras::QKeyframeAnimation *> animations; + findAnimationsForNode<Qt3DExtras::QKeyframeAnimation>(m_scene->m_animations, + animations, + aiStringToQString(node->mName)); + + for (Qt3DExtras::QKeyframeAnimation *anim : animations) { + anim->setTarget(transform); + anim->setParent(entityNode); + } + // Add Camera if (m_scene->m_cameras.contains(node)) m_scene->m_cameras[node]->setParent(entityNode); @@ -497,7 +570,6 @@ void AssimpImporter::readSceneFile(const QString &path) m_scene->m_aiScene = m_scene->m_importer->ReadFile(path.toUtf8().constData(), aiProcess_SortByPType| aiProcess_Triangulate| - aiProcess_JoinIdenticalVertices| aiProcess_GenSmoothNormals| aiProcess_FlipUVs); if (m_scene->m_aiScene == nullptr) { @@ -713,8 +785,136 @@ void AssimpImporter::loadMesh(uint meshIndex) m_scene->m_meshes[meshIndex] = geometryRenderer; + if (mesh->mNumAnimMeshes > 0) { + + aiAnimMesh *animesh = mesh->mAnimMeshes[0]; + + if (animesh->mNumVertices != mesh->mNumVertices) + return; + + Qt3DExtras::QMorphingAnimation *morphingAnimation + = new Qt3DExtras::QMorphingAnimation(geometryRenderer); + QVector<QString> names; + QVector<Qt3DExtras::QMorphTarget *> targets; + uint voff = 0; + uint noff = 0; + uint tanoff = 0; + uint texoff = 0; + uint coloff = 0; + uint offset = 0; + if (animesh->mVertices) { + names.push_back(VERTICES_ATTRIBUTE_NAME); + offset += 3; + } + if (animesh->mNormals) { + names.push_back(NORMAL_ATTRIBUTE_NAME); + noff = offset; + offset += 3; + } + if (animesh->mTangents) { + names.push_back(TANGENT_ATTRIBUTE_NAME); + tanoff = offset; + offset += 3; + } + if (animesh->mTextureCoords[0]) { + names.push_back(TEXTCOORD_ATTRIBUTE_NAME); + texoff = offset; + offset += 2; + } + if (animesh->mColors[0]) { + names.push_back(COLOR_ATTRIBUTE_NAME); + coloff = offset; + } + + ushort clumpSize = (animesh->mVertices ? 3 : 0) + + (animesh->mNormals ? 3 : 0) + + (animesh->mTangents ? 3 : 0) + + (animesh->mColors[0] ? 4 : 0) + + (animesh->mTextureCoords[0] ? 2 : 0); + + + for (uint i = 0; i < mesh->mNumAnimMeshes; i++) { + aiAnimMesh *animesh = mesh->mAnimMeshes[i]; + Qt3DExtras::QMorphTarget *target = new Qt3DExtras::QMorphTarget(geometryRenderer); + targets.push_back(target); + QVector<QAttribute *> attributes; + QByteArray targetBufferArray; + targetBufferArray.resize(clumpSize * mesh->mNumVertices * sizeof(float)); + float *dst = reinterpret_cast<float *>(targetBufferArray.data()); + + for (uint j = 0; j < mesh->mNumVertices; j++) { + if (animesh->mVertices) { + *dst++ = animesh->mVertices[j].x; + *dst++ = animesh->mVertices[j].y; + *dst++ = animesh->mVertices[j].z; + } + if (animesh->mNormals) { + *dst++ = animesh->mNormals[j].x; + *dst++ = animesh->mNormals[j].y; + *dst++ = animesh->mNormals[j].z; + } + if (animesh->mTangents) { + *dst++ = animesh->mTangents[j].x; + *dst++ = animesh->mTangents[j].y; + *dst++ = animesh->mTangents[j].z; + } + if (animesh->mTextureCoords[0]) { + *dst++ = animesh->mTextureCoords[0][j].x; + *dst++ = animesh->mTextureCoords[0][j].y; + } + if (animesh->mColors[0]) { + *dst++ = animesh->mColors[0][j].r; + *dst++ = animesh->mColors[0][j].g; + *dst++ = animesh->mColors[0][j].b; + *dst++ = animesh->mColors[0][j].a; + } + } + + Qt3DRender::QBuffer *targetBuffer + = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer"); + targetBuffer->setData(targetBufferArray); + targetBuffer->setParent(meshGeometry); + + if (animesh->mVertices) { + attributes.push_back(createAttribute(targetBuffer, VERTICES_ATTRIBUTE_NAME, + QAttribute::Float, 3, + animesh->mNumVertices, voff * sizeof(float), + clumpSize * sizeof(float), meshGeometry)); + } + if (animesh->mNormals) { + attributes.push_back(createAttribute(targetBuffer, NORMAL_ATTRIBUTE_NAME, + QAttribute::Float, 3, + animesh->mNumVertices, noff * sizeof(float), + clumpSize * sizeof(float), meshGeometry)); + } + if (animesh->mTangents) { + attributes.push_back(createAttribute(targetBuffer, TANGENT_ATTRIBUTE_NAME, + QAttribute::Float, 3, + animesh->mNumVertices, tanoff * sizeof(float), + clumpSize * sizeof(float), meshGeometry)); + } + if (animesh->mTextureCoords[0]) { + attributes.push_back(createAttribute(targetBuffer, TEXTCOORD_ATTRIBUTE_NAME, + QAttribute::Float, 2, + animesh->mNumVertices, texoff * sizeof(float), + clumpSize * sizeof(float), meshGeometry)); + } + if (animesh->mColors[0]) { + attributes.push_back(createAttribute(targetBuffer, COLOR_ATTRIBUTE_NAME, + QAttribute::Float, 4, + animesh->mNumVertices, coloff * sizeof(float), + clumpSize * sizeof(float), meshGeometry)); + } + target->setAttributes(attributes); + } + morphingAnimation->setMorphTargets(targets); + morphingAnimation->setTargetName(aiStringToQString(mesh->mName)); + morphingAnimation->setTarget(geometryRenderer); + } + qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Mesh " << aiStringToQString(mesh->mName) - << " Vertices " << mesh->mNumVertices << " Faces " << mesh->mNumFaces << " Indices " << indices; + << " Vertices " << mesh->mNumVertices << " Faces " + << mesh->mNumFaces << " Indices " << indices; } /*! @@ -790,10 +990,145 @@ void AssimpImporter::loadCamera(uint cameraIndex) m_scene->m_cameras[cameraNode] = camera; } +int findTimeIndex(const QVector<float> ×, float time) { + for (int i = 0; i < times.size(); i++) { + if (qFuzzyCompare(times[i], time)) + return i; + } + return -1; +} + +void insertAtTime(QVector<float> &positions, QVector<Qt3DCore::QTransform *> &tranforms, + Qt3DCore::QTransform *t, float time) +{ + if (positions.size() == 0) { + positions.push_back(time); + tranforms.push_back(t); + } else if (time < positions.first()) { + positions.push_front(time); + tranforms.push_front(t); + } else if (time > positions.last()) { + positions.push_back(time); + tranforms.push_back(t); + } else { + qWarning() << "Insert new key in the middle of the keyframe not implemented."; + } +} + // OPTIONAL void AssimpImporter::loadAnimation(uint animationIndex) { - Q_UNUSED(animationIndex); + aiAnimation *assimpAnim = m_scene->m_aiScene->mAnimations[animationIndex]; + qCDebug(AssimpImporterLog) << "load Animation: "<< aiStringToQString(assimpAnim->mName); + double tickScale = 1.0; + if (!qFuzzyIsNull(assimpAnim->mTicksPerSecond)) + tickScale = 1.0 / assimpAnim->mTicksPerSecond; + + /* keyframe animations */ + for (uint i = 0; i < assimpAnim->mNumChannels; ++i) { + aiNodeAnim *nodeAnim = assimpAnim->mChannels[i]; + aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(nodeAnim->mNodeName); + + Qt3DExtras::QKeyframeAnimation *kfa = new Qt3DExtras::QKeyframeAnimation(); + QVector<float> positions; + QVector<Qt3DCore::QTransform*> transforms; + if ((nodeAnim->mNumPositionKeys > 1) + || !(nodeAnim->mNumPositionKeys == 1 && nodeAnim->mPositionKeys[0].mValue.x == 0 + && nodeAnim->mPositionKeys[0].mValue.y == 0 + && nodeAnim->mPositionKeys[0].mValue.z == 0)) { + for (uint j = 0; j < nodeAnim->mNumPositionKeys; j++) { + positions.push_back(nodeAnim->mPositionKeys[j].mTime); + Qt3DCore::QTransform *t = new Qt3DCore::QTransform(); + t->setTranslation(QVector3D(nodeAnim->mPositionKeys[j].mValue.x, + nodeAnim->mPositionKeys[j].mValue.y, + nodeAnim->mPositionKeys[j].mValue.z)); + transforms.push_back(t); + } + } + if ((nodeAnim->mNumRotationKeys > 1) || + !(nodeAnim->mNumRotationKeys == 1 && nodeAnim->mRotationKeys[0].mValue.x == 0 + && nodeAnim->mRotationKeys[0].mValue.y == 0 + && nodeAnim->mRotationKeys[0].mValue.z == 0 + && nodeAnim->mRotationKeys[0].mValue.w == 1)) { + for (uint j = 0; j < nodeAnim->mNumRotationKeys; j++) { + int index = findTimeIndex(positions, nodeAnim->mRotationKeys[j].mTime); + if (index >= 0) { + Qt3DCore::QTransform *t = transforms[index]; + t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w, + nodeAnim->mRotationKeys[j].mValue.x, + nodeAnim->mRotationKeys[j].mValue.y, + nodeAnim->mRotationKeys[j].mValue.z)); + } else { + Qt3DCore::QTransform *t = new Qt3DCore::QTransform(); + t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w, + nodeAnim->mRotationKeys[j].mValue.x, + nodeAnim->mRotationKeys[j].mValue.y, + nodeAnim->mRotationKeys[j].mValue.z)); + insertAtTime(positions, transforms, t, nodeAnim->mRotationKeys[j].mTime); + } + } + } + if ((nodeAnim->mNumScalingKeys > 1) + || !(nodeAnim->mNumScalingKeys == 1 && nodeAnim->mScalingKeys[0].mValue.x == 1 + && nodeAnim->mScalingKeys[0].mValue.y == 1 + && nodeAnim->mScalingKeys[0].mValue.z == 1)) { + for (uint j = 0; j < nodeAnim->mNumScalingKeys; j++) { + int index = findTimeIndex(positions, nodeAnim->mScalingKeys[j].mTime); + if (index >= 0) { + Qt3DCore::QTransform *t = transforms[index]; + t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x, + nodeAnim->mScalingKeys[j].mValue.y, + nodeAnim->mScalingKeys[j].mValue.z)); + } else { + Qt3DCore::QTransform *t = new Qt3DCore::QTransform(); + t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x, + nodeAnim->mScalingKeys[j].mValue.y, + nodeAnim->mScalingKeys[j].mValue.z)); + insertAtTime(positions, transforms, t, nodeAnim->mScalingKeys[j].mTime); + } + } + } + for (int j = 0; j < positions.size(); ++j) + positions[j] = positions[j] * tickScale; + kfa->setFramePositions(positions); + kfa->setKeyframes(transforms); + kfa->setAnimationName(QString(assimpAnim->mName.C_Str())); + kfa->setTargetName(QString(targetNode->mName.C_Str())); + m_scene->m_animations.push_back(kfa); + } + /* mesh morph animations */ + for (uint i = 0; i < assimpAnim->mNumMorphMeshChannels; ++i) { + aiMeshMorphAnim *morphAnim = assimpAnim->mMorphMeshChannels[i]; + aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(morphAnim->mName); + aiMesh *mesh = m_scene->m_aiScene->mMeshes[targetNode->mMeshes[0]]; + + Qt3DExtras::QMorphingAnimation *morphingAnimation = new Qt3DExtras::QMorphingAnimation; + QVector<float> positions; + positions.resize(morphAnim->mNumKeys); + // set so that weights array is allocated to correct size in morphingAnimation + morphingAnimation->setTargetPositions(positions); + for (unsigned int j = 0; j < morphAnim->mNumKeys; ++j) { + aiMeshMorphKey &key = morphAnim->mKeys[j]; + positions[j] = key.mTime * tickScale; + + QVector<float> weights; + weights.resize(key.mNumValuesAndWeights); + for (int k = 0; k < weights.size(); k++) { + const unsigned int value = key.mValues[k]; + if (value < key.mNumValuesAndWeights) + weights[value] = key.mWeights[k]; + } + morphingAnimation->setWeights(j, weights); + } + + morphingAnimation->setTargetPositions(positions); + morphingAnimation->setAnimationName(QString(assimpAnim->mName.C_Str())); + morphingAnimation->setTargetName(QString(targetNode->mName.C_Str())); + morphingAnimation->setMethod((mesh->mMethod == aiMorphingMethod_MORPH_NORMALIZED) + ? Qt3DExtras::QMorphingAnimation::Normalized + : Qt3DExtras::QMorphingAnimation::Relative); + m_scene->m_morphAnimations.push_back(morphingAnimation); + } } /*! diff --git a/src/plugins/sceneparsers/assimp/assimpimporter.h b/src/plugins/sceneparsers/assimp/assimpimporter.h index fb60713b8..0d2c0c9e5 100644 --- a/src/plugins/sceneparsers/assimp/assimpimporter.h +++ b/src/plugins/sceneparsers/assimp/assimpimporter.h @@ -61,6 +61,7 @@ #include <QMap> #include <QDir> +#include <QVector> #include <QLoggingCategory> QT_BEGIN_NAMESPACE @@ -71,6 +72,11 @@ namespace Qt3DCore { class QCamera; } +namespace Qt3DExtras { +class QKeyframeAnimation; +class QMorphingAnimation; +} + namespace Qt3DRender { class QMaterial; @@ -138,6 +144,8 @@ private: QMap<QString, QAbstractTexture *> m_materialTextures; QMap<aiNode*, Qt3DCore::QEntity*> m_cameras; QHash<aiTextureType, QString> m_textureToParameterName; + QVector<Qt3DExtras::QKeyframeAnimation *> m_animations; + QVector<Qt3DExtras::QMorphingAnimation *> m_morphAnimations; // QMap<aiNode*, Light*> m_lights; }; |