Merge pull request #13956 from hyperlogic/feature/improve-anim-stats

Move animation stats into its own Menu
This commit is contained in:
John Conklin II 2018-09-07 14:51:46 -07:00 committed by GitHub
commit 472675f9a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 488 additions and 98 deletions

View file

@ -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;
}
}
}
}

View file

@ -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, " +

View file

@ -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<OffscreenUi>()->getSurfaceContext();
surfaceContext->setContextProperty("Stats", Stats::getInstance());
surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance());
#if !defined(Q_OS_ANDROID)
auto offscreenUi = DependencyManager::get<OffscreenUi>();
@ -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

View file

@ -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)

View file

@ -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";

View file

@ -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 <avatar/AvatarManager.h>
#include <OffscreenUi.h>
#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<AvatarManager>();
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();
}

View file

@ -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 <OffscreenQmlElement.h>
#include <AnimContext.h>
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<QString, qint64> _animAlphaValueChangedTimers; // last time alpha value has changed
QStringList _animVarsList;
std::map<QString, QString> _prevAnimVars; // anim vars from previous frame
std::map<QString, qint64> _animVarChangedTimers; // last time animVar value has changed.
QStringList _animStateMachines;
};
#endif // hifi_AnimStats_h

View file

@ -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);

View file

@ -134,7 +134,6 @@ private: \
* @property {number} batchFrameTime - <em>Read-only.</em>
* @property {number} engineFrameTime - <em>Read-only.</em>
* @property {number} avatarSimulationTime - <em>Read-only.</em>
* @property {string[]} animStackNames - <em>Read-only.</em>
*
*
* @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 <code>animStackNames</code> property changes.
* @function Stats.animStackNamesChanged
* @returns {Signal}
*/
void animStackNamesChanged();
/**jsdoc
* Triggered when the value of the <code>rectifiedTextureCount</code> 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

View file

@ -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);

View file

@ -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());
}
}

View file

@ -14,8 +14,27 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QString>
#include <QStringList>
#include <map>
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<float, AnimNodeType>;
using DebugAlphaMap = std::map<QString, DebugAlphaMapValue>;
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<QString, DebugStateMachineMapValue>;
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

View file

@ -12,10 +12,6 @@
#include <QtGlobal>
std::map<QString, float> AnimNode::_animStack = {
{"none", 0.0f}
};
AnimNode::Pointer AnimNode::getParent() {
return _parent.lock();
}

View file

@ -36,19 +36,7 @@ class QJsonObject;
class AnimNode : public std::enable_shared_from_this<AnimNode> {
public:
enum class Type {
Clip = 0,
BlendLinear,
BlendLinearMove,
Overlay,
StateMachine,
Manipulator,
InverseKinematics,
DefaultPose,
TwoBoneIK,
PoleVectorConstraint,
NumTypes
};
using Type = AnimNodeType;
using Pointer = std::shared_ptr<AnimNode>;
using ConstPointer = std::shared_ptr<const AnimNode>;
@ -84,7 +72,6 @@ public:
}
void setCurrentFrame(float frame);
const std::map<QString, float> getAnimStack() { return _animStack; }
template <typename F>
bool traverse(F func) {
@ -127,9 +114,6 @@ protected:
std::weak_ptr<AnimNode> _parent;
std::vector<QString> _outputJointNames;
// global available to Stats.h
static std::map<QString, float> _animStack;
// no copies
AnimNode(const AnimNode&) = delete;
AnimNode& operator=(const AnimNode&) = delete;

View file

@ -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 {

View file

@ -138,9 +138,9 @@ protected:
float _alpha = 0.0f;
AnimPoseVec _prevPoses;
AnimPoseVec _nextPoses;
QString _previousStateID { "none" };
State::Pointer _currentState;
State::Pointer _previousState;
std::vector<State::Pointer> _states;
QString _currentStateVar;

View file

@ -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<QString, QString> AnimVariantMap::toDebugMap() const {
std::map<QString, QString> 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;
}

View file

@ -235,6 +235,9 @@ public:
void animVariantMapFromScriptValue(const QScriptValue& object);
void copyVariantsFrom(const AnimVariantMap& other);
// For stat debugging.
std::map<QString, QString> toDebugMap() const;
#ifdef NDEBUG
void dump() const {
qCDebug(animation) << "AnimVariantMap =";

View file

@ -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);

View file

@ -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<QString, float> 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__) */