diff options
Diffstat (limited to 'src/animation/backend/animationutils.cpp')
-rw-r--r-- | src/animation/backend/animationutils.cpp | 431 |
1 files changed, 333 insertions, 98 deletions
diff --git a/src/animation/backend/animationutils.cpp b/src/animation/backend/animationutils.cpp index 65a455144..1f675f271 100644 --- a/src/animation/backend/animationutils.cpp +++ b/src/animation/backend/animationutils.cpp @@ -37,6 +37,9 @@ #include "animationutils_p.h" #include <Qt3DAnimation/private/handler_p.h> #include <Qt3DAnimation/private/managers_p.h> +#include <Qt3DAnimation/private/clipblendnode_p.h> +#include <Qt3DAnimation/private/clipblendnodevisitor_p.h> +#include <Qt3DAnimation/private/clipblendvalue_p.h> #include <Qt3DCore/qpropertyupdatedchange.h> #include <Qt3DCore/private/qpropertyupdatedchangebase_p.h> #include <QtGui/qvector2d.h> @@ -46,17 +49,60 @@ #include <QtCore/qvariant.h> #include <Qt3DAnimation/private/animationlogging_p.h> +#include <numeric> + QT_BEGIN_NAMESPACE namespace Qt3DAnimation { namespace Animation { -double AnimationUtils::localTimeFromGlobalTime(double t_global, - double t_start_global, - double playbackRate, - double duration, - int loopCount, - int ¤tLoop) +int componentsForType(int type) +{ + int componentCount = 1; + switch (type) { + case QVariant::Double: + componentCount = 1; + break; + + case QVariant::Vector2D: + componentCount = 2; + break; + + case QVariant::Vector3D: + componentCount = 3; + break; + + case QVariant::Vector4D: + case QVariant::Quaternion: + componentCount = 4; + break; + + default: + qWarning() << "Unhandled animation type"; + } + + return componentCount; +} + +ClipEvaluationData evaluationDataForClip(AnimationClip *clip, + const AnimatorEvaluationData &animatorData) +{ + // global time values expected in seconds + ClipEvaluationData result; + result.localTime = localTimeFromGlobalTime(animatorData.globalTime, animatorData.startTime, + animatorData.playbackRate, clip->duration(), + animatorData.loopCount, result.currentLoop); + result.isFinalFrame = isFinalFrame(result.localTime, clip->duration(), + result.currentLoop, animatorData.loopCount); + return result; +} + +double localTimeFromGlobalTime(double t_global, + double t_start_global, + double playbackRate, + double duration, + int loopCount, + int ¤tLoop) { double t_local = playbackRate * (t_global - t_start_global); double loopNumber = 0; @@ -89,60 +135,48 @@ double AnimationUtils::localTimeFromGlobalTime(double t_global, return t_local; } -QVector<int> AnimationUtils::channelsToIndices(const ChannelGroup &channelGroup, int dataType, int offset) +double phaseFromGlobalTime(double t_global, double t_start_global, + double playbackRate, double duration, + int loopCount, int ¤tLoop) { - static const QStringList standardSuffixes = (QStringList() - << QLatin1String("x") - << QLatin1String("y") - << QLatin1String("z") - << QLatin1String("w")); - static const QStringList quaternionSuffixes = (QStringList() - << QLatin1String("w") - << QLatin1String("x") - << QLatin1String("y") - << QLatin1String("z")); + const double t_local = localTimeFromGlobalTime(t_global, t_start_global, playbackRate, + duration, loopCount, currentLoop); + return t_local / duration; +} + +ComponentIndices channelComponentsToIndices(const Channel &channel, int dataType, int offset) +{ +#if defined Q_COMPILER_UNIFORM_INIT + static const QVector<char> standardSuffixes = { 'X', 'Y', 'Z', 'W' }; + static const QVector<char> quaternionSuffixes = { 'W', 'X', 'Y', 'Z' }; +#else + static const QVector<char> standardSuffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); + static const QVector<char> quaternionSuffixes = (QVector<char>() << 'W' << 'X' << 'Y' << 'Z'); +#endif if (dataType != QVariant::Quaternion) - return channelsToIndicesHelper(channelGroup, dataType, offset, standardSuffixes); + return channelComponentsToIndicesHelper(channel, dataType, offset, standardSuffixes); else - return channelsToIndicesHelper(channelGroup, dataType, offset, quaternionSuffixes); - + return channelComponentsToIndicesHelper(channel, dataType, offset, quaternionSuffixes); } -QVector<int> AnimationUtils::channelsToIndicesHelper(const ChannelGroup &channelGroup, int dataType, int offset, const QStringList &suffixes) +ComponentIndices channelComponentsToIndicesHelper(const Channel &channel, + int dataType, + int offset, + const QVector<char> &suffixes) { - int expectedChannelCount = 1; - switch (dataType) { - case QVariant::Double: - expectedChannelCount = 1; - break; - - case QVariant::Vector2D: - expectedChannelCount = 2; - break; - - case QVariant::Vector3D: - expectedChannelCount = 3; - break; - - case QVariant::Vector4D: - case QVariant::Quaternion: - expectedChannelCount = 4; - break; - - default: - qWarning() << "Unhandled animation type"; - } - - const int foundChannelCount = channelGroup.channels.size(); - if (foundChannelCount != expectedChannelCount) { - qWarning() << "Data type expects" << expectedChannelCount - << "but found" << foundChannelCount << "channels in the animation clip"; + const int expectedComponentCount = componentsForType(dataType); + const int actualComponentCount = channel.channelComponents.size(); + if (actualComponentCount != expectedComponentCount) { + qWarning() << "Data type expects" << expectedComponentCount + << "but found" << actualComponentCount << "components in the animation clip"; } - QVector<int> indices(expectedChannelCount); - for (int i = 0; i < expectedChannelCount; ++i) { - int index = suffixes.indexOf(channelGroup.channels[i].name); + ComponentIndices indices(expectedComponentCount); + for (int i = 0; i < expectedComponentCount; ++i) { + const QString &componentName = channel.channelComponents[i].name; + char suffix = componentName.at(componentName.length() - 1).toLatin1(); + int index = suffixes.indexOf(suffix); if (index != -1) indices[i] = index + offset; else @@ -151,57 +185,35 @@ QVector<int> AnimationUtils::channelsToIndicesHelper(const ChannelGroup &channel return indices; } -QVector<float> AnimationUtils::evaluateAtGlobalTime(AnimationClip *clip, - qint64 globalTime, - qint64 startTime, - int loopCount, - int ¤tLoop, - bool &finalFrame) -{ - // Calculate local time from global time - const double t_global = double(globalTime) / 1.0e9; - const double t_start_global = double(startTime) / 1.0e9; - const double playbackRate = 1.0; // Assume standard playback rate for now - const double duration = clip->duration(); - - const double localTime = localTimeFromGlobalTime(t_global, t_start_global, - playbackRate, duration, - loopCount, currentLoop); - return AnimationUtils::evaluateAtLocalTime(clip, localTime, - currentLoop, loopCount, - finalFrame); -} - -QVector<float> AnimationUtils::evaluateAtLocalTime(AnimationClip *clip, float localTime, - int currentLoop, int loopCount, - bool &finalFrame) +ClipResults evaluateClipAtLocalTime(AnimationClip *clip, float localTime) { QVector<float> channelResults; Q_ASSERT(clip); - // TODO: Uncomment when we add loopCount property - if (localTime >= clip->duration() - && loopCount != 0 - && currentLoop == loopCount - 1) - finalFrame = true; - // Ensure we have enough storage to hold the evaluations channelResults.resize(clip->channelCount()); // Iterate over channels and evaluate the fcurves - const QVector<ChannelGroup> &channelGroups = clip->channelGroups(); + const QVector<Channel> &channels = clip->channels(); int i = 0; - for (const ChannelGroup &channelGroup : channelGroups) { - for (const auto channel : qAsConst(channelGroup.channels)) - channelResults[i++] = channel.fcurve.evaluateAtTime(localTime); + for (const Channel &channel : channels) { + for (const auto &channelComponent : qAsConst(channel.channelComponents)) + channelResults[i++] = channelComponent.fcurve.evaluateAtTime(localTime); } return channelResults; } -QVector<Qt3DCore::QSceneChangePtr> AnimationUtils::preparePropertyChanges(Qt3DCore::QNodeId peerId, - const QVector<MappingData> &mappingDataVec, - const QVector<float> &channelResults, - bool finalFrame) +ClipResults evaluateClipAtPhase(AnimationClip *clip, float phase) +{ + // Calculate the clip local time from the phase and clip duration + const double localTime = phase * clip->duration(); + return evaluateClipAtLocalTime(clip, localTime); +} + +QVector<Qt3DCore::QSceneChangePtr> preparePropertyChanges(Qt3DCore::QNodeId animatorId, + const QVector<MappingData> &mappingDataVec, + const QVector<float> &channelResults, + bool finalFrame) { QVector<Qt3DCore::QSceneChangePtr> changes; // Iterate over the mappings @@ -212,7 +224,7 @@ QVector<Qt3DCore::QSceneChangePtr> AnimationUtils::preparePropertyChanges(Qt3DCo e->setPropertyName(mappingData.propertyName); // Handle intermediate updates vs final flag properly - Qt3DCore::QPropertyUpdatedChangeBasePrivate::get(e.data())->m_isFinal = finalFrame; + Qt3DCore::QPropertyUpdatedChangeBasePrivate::get(e.data())->m_isIntermediate = !finalFrame; // Build the new value from the channel/fcurve evaluation results QVariant v; @@ -269,21 +281,24 @@ QVector<Qt3DCore::QSceneChangePtr> AnimationUtils::preparePropertyChanges(Qt3DCo // If it's the final frame, notify the frontend that we've stopped if (finalFrame) { - auto e = Qt3DCore::QPropertyUpdatedChangePtr::create(peerId); + auto e = Qt3DCore::QPropertyUpdatedChangePtr::create(animatorId); e->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); e->setPropertyName("running"); e->setValue(false); - Qt3DCore::QPropertyUpdatedChangeBasePrivate::get(e.data())->m_isFinal = true; changes.push_back(e); } return changes; } -QVector<AnimationUtils::MappingData> AnimationUtils::buildPropertyMappings(Handler *handler, const AnimationClip *clip, const ChannelMapper *mapper) +//TODO: Remove this and use new implementation below for both the unblended +// and blended animation cases. +QVector<MappingData> buildPropertyMappings(Handler *handler, + const AnimationClip *clip, + const ChannelMapper *mapper) { QVector<MappingData> mappingDataVec; ChannelMappingManager *mappingManager = handler->channelMappingManager(); - const QVector<ChannelGroup> &channelGroups = clip->channelGroups(); + const QVector<Channel> &channels = clip->channels(); // Iterate over the mappings in the mapper object for (const Qt3DCore::QNodeId mappingId : mapper->mappingIds()) { @@ -308,13 +323,13 @@ QVector<AnimationUtils::MappingData> AnimationUtils::buildPropertyMappings(Handl const QString channelName = mapping->channelName(); int channelGroupIndex = 0; bool foundMatch = false; - for (const ChannelGroup &channelGroup : channelGroups) { - if (channelGroup.name == channelName) { + for (const Channel &channel : channels) { + if (channel.name == channelName) { foundMatch = true; - const int channelBaseIndex = clip->channelBaseIndex(channelGroupIndex); + const int channelBaseIndex = clip->channelComponentBaseIndex(channelGroupIndex); // Within this group, match channel names with index ordering - mappingData.channelIndices = channelsToIndices(channelGroup, mappingData.type, channelBaseIndex); + mappingData.channelIndices = channelComponentsToIndices(channel, mappingData.type, channelBaseIndex); // Store the mapping data mappingDataVec.push_back(mappingData); @@ -330,6 +345,226 @@ QVector<AnimationUtils::MappingData> AnimationUtils::buildPropertyMappings(Handl return mappingDataVec; } +QVector<MappingData> buildPropertyMappings(const QVector<ChannelMapping*> &channelMappings, + const QVector<ChannelNameAndType> &channelNamesAndTypes, + const QVector<ComponentIndices> &channelComponentIndices) +{ + QVector<MappingData> mappingDataVec; + mappingDataVec.reserve(channelMappings.size()); + + // Iterate over the mappings + for (const auto mapping : channelMappings) { + // Populate the data we need, easy stuff first + MappingData mappingData; + mappingData.targetId = mapping->targetId(); + mappingData.propertyName = mapping->propertyName(); + mappingData.type = mapping->type(); + + if (mappingData.type == static_cast<int>(QVariant::Invalid)) { + qWarning() << "Unknown type for node id =" << mappingData.targetId + << "and property =" << mapping->property(); + continue; + } + + // Try to find matching channel name and type + const ChannelNameAndType nameAndType = { mapping->channelName(), mapping->type() }; + const int index = channelNamesAndTypes.indexOf(nameAndType); + if (index != -1) { + // We got one! + mappingData.channelIndices = channelComponentIndices[index]; + mappingDataVec.push_back(mappingData); + } + } + + return mappingDataVec; +} + +QVector<ChannelNameAndType> buildRequiredChannelsAndTypes(Handler *handler, + const ChannelMapper *mapper) +{ + ChannelMappingManager *mappingManager = handler->channelMappingManager(); + const QVector<Qt3DCore::QNodeId> mappingIds = mapper->mappingIds(); + + // Reserve enough storage assuming each mapping is for a different channel. + // May be overkill but avoids potential for multiple allocations + QVector<ChannelNameAndType> namesAndTypes; + namesAndTypes.reserve(mappingIds.size()); + + // Iterate through the mappings and add ones not already used by an earlier mapping. + // We could add them all then sort and remove duplicates. However, our approach has the + // advantage of keeping the blend tree format more consistent with the mapping + // orderings which will have better cache locality when generating events. + for (const Qt3DCore::QNodeId mappingId : mappingIds) { + // Get the mapping object + ChannelMapping *mapping = mappingManager->lookupResource(mappingId); + Q_ASSERT(mapping); + + // Get the name and type + const ChannelNameAndType nameAndType{ mapping->channelName(), mapping->type() }; + + // Add if not already contained + if (!namesAndTypes.contains(nameAndType)) + namesAndTypes.push_back(nameAndType); + } + + return namesAndTypes; +} + +QVector<ComponentIndices> assignChannelComponentIndices(const QVector<ChannelNameAndType> &namesAndTypes) +{ + QVector<ComponentIndices> channelComponentIndices; + channelComponentIndices.reserve(namesAndTypes.size()); + + int baseIndex = 0; + for (const auto &entry : namesAndTypes) { + // Populate indices in order + const int componentCount = componentsForType(entry.type); + ComponentIndices indices(componentCount); + std::iota(indices.begin(), indices.end(), baseIndex); + + // Append to the results + channelComponentIndices.push_back(indices); + + // Increment baseIndex + baseIndex += componentCount; + } + + return channelComponentIndices; +} + +QVector<Qt3DCore::QNodeId> gatherValueNodesToEvaluate(Handler *handler, + Qt3DCore::QNodeId blendTreeRootId) +{ + Q_ASSERT(handler); + Q_ASSERT(blendTreeRootId.isNull() == false); + + // We need the ClipBlendNodeManager to be able to lookup nodes from their Ids + ClipBlendNodeManager *nodeManager = handler->clipBlendNodeManager(); + + // Visit the tree in a pre-order manner and collect the dependencies + QVector<Qt3DCore::QNodeId> clipIds; + ClipBlendNodeVisitor visitor(nodeManager, + ClipBlendNodeVisitor::PreOrder, + ClipBlendNodeVisitor::VisitOnlyDependencies); + + auto func = [&clipIds, nodeManager] (ClipBlendNode *blendNode) { + const auto dependencyIds = blendNode->currentDependencyIds(); + for (const auto dependencyId : dependencyIds) { + // Look up the blend node and if it's a value type (clip), + // add it to the set of value node ids that need to be evaluated + ClipBlendNode *node = nodeManager->lookupNode(dependencyId); + if (node && node->blendType() == ClipBlendNode::ValueType) + clipIds.append(dependencyId); + } + }; + visitor.traverse(blendTreeRootId, func); + + // Sort and remove duplicates + std::sort(clipIds.begin(), clipIds.end()); + auto last = std::unique(clipIds.begin(), clipIds.end()); + clipIds.erase(last, clipIds.end()); + return clipIds; +} + +ComponentIndices generateClipFormatIndices(const QVector<ChannelNameAndType> &targetChannels, + const QVector<ComponentIndices> &targetIndices, + const AnimationClip *clip) +{ + Q_ASSERT(targetChannels.size() == targetIndices.size()); + + // Reserve enough storage for all the format indices + int indexCount = 0; + for (const auto targetIndexVec : targetIndices) + indexCount += targetIndexVec.size(); + ComponentIndices format; + format.resize(indexCount); + + + // Iterate through the target channels + const int channelCount = targetChannels.size(); + auto formatIt = format.begin(); + for (int i = 0; i < channelCount; ++i) { + // Find the index of the channel from the clip + const ChannelNameAndType &targetChannel = targetChannels[i]; + const int clipChannelIndex = clip->channelIndex(targetChannel.name); + + // TODO: Ensure channel in the clip has enough components to map to the type. + // Requires some improvements to the clip data structure first. + // TODO: I don't think we need the targetIndices, only the number of components + // for each target channel. Check once blend tree is complete. + const int componentCount = targetIndices[i].size(); + + if (clipChannelIndex != -1) { + // Found a matching channel in the clip. Get the base channel + // component index and populate the format indices for this channel. + const int baseIndex = clip->channelComponentBaseIndex(clipChannelIndex); + std::iota(formatIt, formatIt + componentCount, baseIndex); + + } else { + // No such channel in this clip. We'll use default values when + // mapping from the clip to the formatted clip results. + std::fill(formatIt, formatIt + componentCount, -1); + } + + formatIt += componentCount; + } + + return format; +} + +ClipResults formatClipResults(const ClipResults &rawClipResults, + const ComponentIndices &format) +{ + // Resize the output to match the number of indices + const int elementCount = format.size(); + ClipResults formattedClipResults(elementCount); + + // Perform a gather operation to format the data + // TODO: For large numbers of components do this in parallel with + // for e.g. a parallel_for() like construct + for (int i = 0; i < elementCount; ++i) { + const float value = format[i] != -1 ? rawClipResults[format[i]] : 0.0f; + formattedClipResults[i] = value; + } + + return formattedClipResults; +} + +ClipResults evaluateBlendTree(Handler *handler, + BlendedClipAnimator *animator, + Qt3DCore::QNodeId blendTreeRootId) +{ + Q_ASSERT(handler); + Q_ASSERT(blendTreeRootId.isNull() == false); + const Qt3DCore::QNodeId animatorId = animator->peerId(); + + // We need the ClipBlendNodeManager to be able to lookup nodes from their Ids + ClipBlendNodeManager *nodeManager = handler->clipBlendNodeManager(); + + // Visit the tree in a post-order manner and for each interior node call + // blending function. We only need to visit the nodes that affect the blend + // tree at this time. + ClipBlendNodeVisitor visitor(nodeManager, + ClipBlendNodeVisitor::PostOrder, + ClipBlendNodeVisitor::VisitOnlyDependencies); + + // TODO: When jobs can spawn other jobs we could evaluate subtrees of + // the blend tree in parallel. Since it's just a dependency tree, it maps + // simply onto the dependencies between jobs. + auto func = [animatorId] (ClipBlendNode *blendNode) { + // Look up the blend node and if it's an interior node, perform + // the blend operation + if (blendNode->blendType() != ClipBlendNode::ValueType) + blendNode->blend(animatorId); + }; + visitor.traverse(blendTreeRootId, func); + + // The clip results stored in the root node for this animator + // now represent the result of the blend tree evaluation + ClipBlendNode *blendTreeRootNode = nodeManager->lookupNode(blendTreeRootId); + Q_ASSERT(blendTreeRootNode); + return blendTreeRootNode->clipResults(animatorId); +} } // Animation } // Qt3DAnimation |