diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml new file mode 100644 index 0000000000..35ed3799a6 --- /dev/null +++ b/interface/resources/qml/AnimStats.qml @@ -0,0 +1,157 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import '.' + +Item { + id: animStats + + anchors.leftMargin: 300 + objectName: "StatsItem" + property int modality: Qt.NonModal + implicitHeight: row.height + implicitWidth: row.width + + Component.onCompleted: { + animStats.parentChanged.connect(fill); + fill(); + } + Component.onDestruction: { + animStats.parentChanged.disconnect(fill); + } + + function fill() { + // This will cause a warning at shutdown, need to find another way to remove + // the warning other than filling the anchors to the parent + anchors.horizontalCenter = parent.horizontalCenter + } + + Hifi.AnimStats { + id: root + objectName: "AnimStats" + implicitHeight: row.height + implicitWidth: row.width + + anchors.horizontalCenter: parent.horizontalCenter + readonly property string bgColor: "#AA111111" + + Row { + id: row + spacing: 8 + + Rectangle { + width: firstCol.width + 8; + height: firstCol.height + 8; + color: root.bgColor; + + Column { + id: firstCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "State Machines:---------------------------------------------------------------------------" + } + ListView { + width: firstCol.width + height: root.animStateMachines.length * 15 + visible: root.animStateMchines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + } + } + + Rectangle { + width: secondCol.width + 8 + height: secondCol.height + 8 + color: root.bgColor; + + Column { + id: secondCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "Anim Vars:--------------------------------------------------------------------------------" + } + + ListView { + width: secondCol.width + height: root.animVars.length * 15 + visible: root.animVars.length > 0; + model: root.animVars + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + } + } + + Rectangle { + width: thirdCol.width + 8 + height: thirdCol.height + 8 + color: root.bgColor; + + Column { + id: thirdCol + spacing: 4; x: 4; y: 4; + + StatText { + text: "Alpha Values:--------------------------------------------------------------------------" + } + + ListView { + width: thirdCol.width + height: root.animAlphaValues.length * 15 + visible: root.animAlphaValues.length > 0; + model: root.animAlphaValues + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + + } + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } + } + +} diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index d5cfcff1e2..1a29ce87df 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -192,21 +192,6 @@ Item { StatText { text: "Yaw: " + root.yaw.toFixed(1) } - StatText { - visible: root.animStackNames.length > 0; - text: "Anim Stack Names:" - } - ListView { - width: geoCol.width - height: root.animStackNames.length * 15 - visible: root.animStackNames.length > 0; - model: root.animStackNames - delegate: StatText { - text: modelData.length > 30 - ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) - : modelData - } - } StatText { visible: root.expanded; text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fe67ec5cb..40edcdbd76 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -195,6 +195,7 @@ #include "ui/SnapshotAnimated.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +#include "ui/AnimStats.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" @@ -3081,8 +3082,10 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { Stats::show(); + AnimStats::show(); auto surfaceContext = DependencyManager::get()->getSurfaceContext(); surfaceContext->setContextProperty("Stats", Stats::getInstance()); + surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance()); #if !defined(Q_OS_ANDROID) auto offscreenUi = DependencyManager::get(); @@ -4618,6 +4621,7 @@ void Application::idle() { checkChangeCursor(); Stats::getInstance()->updateStats(); + AnimStats::getInstance()->updateStats(); // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a6ba983ab5..f7be6d17ef 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -737,6 +737,7 @@ Menu::Menu() { // Developer > Stats addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); // Settings > Enable Speech Control API #if defined(Q_OS_MAC) || defined(Q_OS_WIN) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c4ea3734f5..031ee2561c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -197,6 +197,7 @@ namespace MenuOption { const QString SMIEyeTracking = "SMI Eye Tracking"; const QString SparseTextureManagement = "Enable Sparse Texture Management"; const QString Stats = "Show Statistics"; + const QString AnimStats = "Show Animation Stats"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString ThirdPerson = "Third Person"; diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp new file mode 100644 index 0000000000..7b4778e365 --- /dev/null +++ b/interface/src/ui/AnimStats.cpp @@ -0,0 +1,141 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimStats.h" + +#include +#include +#include "Menu.h" + +HIFI_QML_DEF(AnimStats) + +static AnimStats* INSTANCE{ nullptr }; + +AnimStats* AnimStats::getInstance() { + Q_ASSERT(INSTANCE); + return INSTANCE; +} + +AnimStats::AnimStats(QQuickItem* parent) : QQuickItem(parent) { + INSTANCE = this; +} + +void AnimStats::updateStats(bool force) { + QQuickItem* parent = parentItem(); + if (!force) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::AnimStats)) { + if (parent->isVisible()) { + parent->setVisible(false); + } + return; + } else if (!parent->isVisible()) { + parent->setVisible(true); + } + } + + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager->getMyAvatar(); + auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); + + // update animation debug alpha values + QStringList newAnimAlphaValues; + qint64 now = usecTimestampNow(); + for (auto& iter : debugAlphaMap) { + QString key = iter.first; + float alpha = std::get<0>(iter.second); + + auto prevIter = _prevDebugAlphaMap.find(key); + if (prevIter != _prevDebugAlphaMap.end()) { + float prevAlpha = std::get<0>(iter.second); + if (prevAlpha != alpha) { + // change detected: reset timer + _animAlphaValueChangedTimers[key] = now; + } + } else { + // new value: start timer + _animAlphaValueChangedTimers[key] = now; + } + + AnimNodeType type = std::get<1>(iter.second); + if (type == AnimNodeType::Clip) { + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 1.0f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + newAnimAlphaValues << QString::number(grayScale, 'f', 2) + "|" + key + ": " + QString::number(alpha, 'f', 3); + } + } + } + + _animAlphaValues = newAnimAlphaValues; + _prevDebugAlphaMap = debugAlphaMap; + + emit animAlphaValuesChanged(); + + // update animation anim vars + _animVarsList.clear(); + auto animVars = myAvatar->getSkeletonModel()->getRig().getAnimVars().toDebugMap(); + for (auto& iter : animVars) { + QString key = iter.first; + QString value = iter.second; + + auto prevIter = _prevAnimVars.find(key); + if (prevIter != _prevAnimVars.end()) { + QString prevValue = prevIter->second; + if (value != prevValue) { + // change detected: reset timer + _animVarChangedTimers[key] = now; + } + } else { + // new value: start timer + _animVarChangedTimers[key] = now; + } + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 0.5f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + _animVarsList << QString::number(grayScale, 'f', 2) + "|" + key + ": " + value; + } + } + _prevAnimVars = animVars; + emit animVarsChanged(); + + // animation state machines + _animStateMachines.clear(); + auto stateMachineMap = myAvatar->getSkeletonModel()->getRig().getStateMachineMap(); + for (auto& iter : stateMachineMap) { + _animStateMachines << iter.second; + } + emit animStateMachinesChanged(); +} + + diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h new file mode 100644 index 0000000000..1a6795b498 --- /dev/null +++ b/interface/src/ui/AnimStats.h @@ -0,0 +1,55 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimStats_h +#define hifi_AnimStats_h + +#include +#include + +class AnimStats : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL + + Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) + Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) + Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) + +public: + static AnimStats* getInstance(); + + AnimStats(QQuickItem* parent = nullptr); + + void updateStats(bool force = false); + + QStringList animAlphaValues() { return _animAlphaValues; } + QStringList animVars() { return _animVarsList; } + QStringList animStateMachines() { return _animStateMachines; } + +public slots: + void forceUpdateStats() { updateStats(true); } + +signals: + + void animAlphaValuesChanged(); + void animVarsChanged(); + void animStateMachinesChanged(); + +private: + QStringList _animAlphaValues; + AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame + std::map _animAlphaValueChangedTimers; // last time alpha value has changed + + QStringList _animVarsList; + std::map _prevAnimVars; // anim vars from previous frame + std::map _animVarChangedTimers; // last time animVar value has changed. + + QStringList _animStateMachines; +}; + +#endif // hifi_AnimStats_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 1b26c9b621..d51b90f15d 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -207,14 +207,6 @@ void Stats::updateStats(bool force) { // Third column, avatar stats auto myAvatar = avatarManager->getMyAvatar(); - auto animStack = myAvatar->getSkeletonModel()->getRig().getAnimStack(); - - _animStackNames.clear(); - for (auto animStackIterator = animStack.begin(); animStackIterator != animStack.end(); ++animStackIterator) { - _animStackNames << animStackIterator->first + ": " + QString::number(animStackIterator->second,'f',3); - } - emit animStackNamesChanged(); - glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getWorldVelocity()), 0.01f); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index a0453a09c5..ae608cfddb 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -134,7 +134,6 @@ private: \ * @property {number} batchFrameTime - Read-only. * @property {number} engineFrameTime - Read-only. * @property {number} avatarSimulationTime - Read-only. - * @property {string[]} animStackNames - Read-only. * * * @property {number} x @@ -292,7 +291,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) - Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged) STATS_PROPERTY(int, stylusPicksCount, 0) STATS_PROPERTY(int, rayPicksCount, 0) @@ -326,7 +324,6 @@ public: } QStringList downloadUrls () { return _downloadUrls; } - QStringList animStackNames() { return _animStackNames; } public slots: void forceUpdateStats() { updateStats(true); } @@ -1028,13 +1025,6 @@ signals: */ void avatarSimulationTimeChanged(); - /**jsdoc - * Triggered when the value of the animStackNames property changes. - * @function Stats.animStackNamesChanged - * @returns {Signal} - */ - void animStackNamesChanged(); - /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. * @function Stats.rectifiedTextureCountChanged @@ -1049,7 +1039,6 @@ signals: */ void decimatedTextureCountChanged(); - // QQuickItem signals. /**jsdoc @@ -1336,7 +1325,6 @@ private: QString _monospaceFont; const AudioIOStats* _audioStats; QStringList _downloadUrls = QStringList(); - QStringList _animStackNames = QStringList(); }; #endif // hifi_Stats_h diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index e2d79f864d..17ca88123f 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -27,7 +27,7 @@ AnimBlendLinear::~AnimBlendLinear() { const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { _alpha = animVars.lookup(_alphaVar, _alpha); - float parentAlpha = _animStack[_id]; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -35,7 +35,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con } } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, context, dt, triggersOut); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); @@ -48,12 +48,12 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { weight2 = alpha; weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), weight1 * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } processOutputJoints(triggersOut); diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 42098eb072..07e1c17f77 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -62,9 +62,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, speed = animVars.lookup("moveForwardSpeed", speed); } _alpha = calculateAlpha(speed, _characteristicSpeeds); - float parentAlpha = _animStack[_id]; - - _animStack["speed"] = speed; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -77,7 +75,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float prevDeltaTime, nextDeltaTime; setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { auto clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); auto prevPoseIndex = glm::floor(clampedAlpha); @@ -87,17 +85,11 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - // weights are for animation stack debug purposes only. - float weight1 = 0.0f; - float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { - weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { - weight2 = alpha; - weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), (1.0f - alpha) * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), alpha * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e8bf5c34eb..c455dd9c8f 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -14,8 +14,27 @@ #include #include +#include +#include +#include + +enum class AnimNodeType { + Clip = 0, + BlendLinear, + BlendLinearMove, + Overlay, + StateMachine, + Manipulator, + InverseKinematics, + DefaultPose, + TwoBoneIK, + PoleVectorConstraint, + NumTypes +}; + class AnimContext { public: + AnimContext() {} AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); @@ -25,6 +44,39 @@ public: const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } + float getDebugAlpha(const QString& key) const { + auto it = _debugAlphaMap.find(key); + if (it != _debugAlphaMap.end()) { + return std::get<0>(it->second); + } else { + return 1.0f; + } + } + + using DebugAlphaMapValue = std::tuple; + using DebugAlphaMap = std::map; + + void setDebugAlpha(const QString& key, float alpha, AnimNodeType type) const { + _debugAlphaMap[key] = DebugAlphaMapValue(alpha, type); + } + + const DebugAlphaMap& getDebugAlphaMap() const { + return _debugAlphaMap; + } + + using DebugStateMachineMapValue = QString; + using DebugStateMachineMap = std::map; + + void addStateMachineInfo(const QString& stateMachineName, const QString& currentState, const QString& previousState, bool duringInterp, float alpha) const { + if (duringInterp) { + _stateMachineMap[stateMachineName] = QString("%1: %2 -> %3 (%4)").arg(stateMachineName).arg(previousState).arg(currentState).arg(QString::number(alpha, 'f', 2)); + } else { + _stateMachineMap[stateMachineName] = QString("%1: %2").arg(stateMachineName).arg(currentState); + } + } + + const DebugStateMachineMap& getStateMachineMap() const { return _stateMachineMap; } + protected: bool _enableDebugDrawIKTargets { false }; @@ -32,6 +84,10 @@ protected: bool _enableDebugDrawIKChains { false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; + + // used for debugging internal state of animation system. + mutable DebugAlphaMap _debugAlphaMap; + mutable DebugStateMachineMap _stateMachineMap; }; #endif // hifi_AnimContext_h diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp index a8e76617ae..f055e6b473 100644 --- a/libraries/animation/src/AnimNode.cpp +++ b/libraries/animation/src/AnimNode.cpp @@ -12,10 +12,6 @@ #include -std::map AnimNode::_animStack = { - {"none", 0.0f} -}; - AnimNode::Pointer AnimNode::getParent() { return _parent.lock(); } diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 1f14889ce4..1a12bb8ddb 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -36,19 +36,7 @@ class QJsonObject; class AnimNode : public std::enable_shared_from_this { public: - enum class Type { - Clip = 0, - BlendLinear, - BlendLinearMove, - Overlay, - StateMachine, - Manipulator, - InverseKinematics, - DefaultPose, - TwoBoneIK, - PoleVectorConstraint, - NumTypes - }; + using Type = AnimNodeType; using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; @@ -84,7 +72,6 @@ public: } void setCurrentFrame(float frame); - const std::map getAnimStack() { return _animStack; } template bool traverse(F func) { @@ -127,9 +114,6 @@ protected: std::weak_ptr _parent; std::vector _outputJointNames; - // global available to Stats.h - static std::map _animStack; - // no copies AnimNode(const AnimNode&) = delete; AnimNode& operator=(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 90fd425ae5..7f46cd614a 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -23,9 +23,7 @@ AnimStateMachine::~AnimStateMachine() { const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - if (_id.contains("userAnimStateMachine")) { - _animStack.clear(); - } + float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); if (_currentState->getID() != desiredStateID) { @@ -33,8 +31,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co bool foundState = false; for (auto& state : _states) { if (state->getID() == desiredStateID) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, state); foundState = true; break; @@ -48,8 +44,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co // evaluate currentState transitions auto desiredState = evaluateTransitions(animVars); if (desiredState != _currentState) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, desiredState); } @@ -57,17 +51,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co auto currentStateNode = _children[_currentState->getChildIndex()]; assert(currentStateNode); - if (!_previousStateID.contains("none")) { - _animStack[_previousStateID] = 1.0f - _alpha; - } - if (_duringInterp) { _alpha += _alphaVel * dt; - if (_alpha > 1.0f) { - _animStack[_currentState->getID()] = 1.0f; - } else { - _animStack[_currentState->getID()] = _alpha; - } if (_alpha < 1.0f) { AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; @@ -88,26 +73,27 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]); } + context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; - if (_animStack.count(_previousStateID) > 0) { - _animStack.erase(_previousStateID); - } - _previousStateID = "none"; _prevPoses.clear(); _nextPoses.clear(); } } + if (!_duringInterp) { - _animStack[_currentState->getID()] = 1.0f; + context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); } processOutputJoints(triggersOut); + context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); + return _poses; } void AnimStateMachine::setCurrentState(State::Pointer state) { + _previousState = _currentState ? _currentState : state; _currentState = state; } @@ -152,7 +138,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; #endif - _currentState = desiredState; + setCurrentState(desiredState); } AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const { diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index b20e5203a1..713c659a1d 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -138,9 +138,9 @@ protected: float _alpha = 0.0f; AnimPoseVec _prevPoses; AnimPoseVec _nextPoses; - QString _previousStateID { "none" }; State::Pointer _currentState; + State::Pointer _previousState; std::vector _states; QString _currentStateVar; diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 483a7999c9..509462984a 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -67,6 +67,7 @@ QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, } return target; } + void AnimVariantMap::copyVariantsFrom(const AnimVariantMap& other) { for (auto& pair : other._map) { _map[pair.first] = pair.second; @@ -124,3 +125,43 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { } } } + +std::map AnimVariantMap::toDebugMap() const { + std::map result; + for (auto& pair : _map) { + switch (pair.second.getType()) { + case AnimVariant::Type::Bool: + result[pair.first] = QString("%1").arg(pair.second.getBool()); + break; + case AnimVariant::Type::Int: + result[pair.first] = QString("%1").arg(pair.second.getInt()); + break; + case AnimVariant::Type::Float: + result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3); + break; + case AnimVariant::Type::Vec3: { + glm::vec3 value = pair.second.getVec3(); + result[pair.first] = QString("(%1, %2, %3)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)); + break; + } + case AnimVariant::Type::Quat: { + glm::quat value = pair.second.getQuat(); + result[pair.first] = QString("(%1, %2, %3, %4)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)). + arg(QString::number(value.w, 'f', 3)); + break; + } + case AnimVariant::Type::String: + result[pair.first] = pair.second.getString(); + break; + default: + assert(("invalid AnimVariant::Type", false)); + } + } + return result; +} diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index d383b5abb8..0f921984b1 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -235,6 +235,9 @@ public: void animVariantMapFromScriptValue(const QScriptValue& object); void copyVariantsFrom(const AnimVariantMap& other); + // For stat debugging. + std::map toDebugMap() const; + #ifdef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 13cf165dac..91d4e0f9d3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1061,8 +1061,10 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index accbcccbbc..48f00d4e5d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -222,7 +222,10 @@ public: // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; - const std::map getAnimStack() { return _animNode->getAnimStack(); } + // used to debug animation playback + const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } + const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } + const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: @@ -388,6 +391,9 @@ protected: int _rigId; bool _headEnabled { false }; + + AnimContext _lastContext; + AnimVariantMap _lastAnimVars; }; #endif /* defined(__hifi__Rig__) */