Merge pull request #5506 from howard-stearns/smooth-faded-animations

Smooth fading of animations in and out.
This commit is contained in:
Anthony Thibault 2015-08-05 12:02:44 -07:00
commit 49be7c49b7
6 changed files with 73 additions and 19 deletions

View file

@ -623,6 +623,12 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) {
return value; return value;
} }
void MyAvatar::setEnableRigAnimations(bool isEnabled) {
Settings settings;
settings.setValue("enableRig", isEnabled);
_rig->setEnableRig(isEnabled);
}
void MyAvatar::loadData() { void MyAvatar::loadData() {
Settings settings; Settings settings;
settings.beginGroup("Avatar"); settings.beginGroup("Avatar");

View file

@ -72,6 +72,7 @@ public:
Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role); Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url); Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url);
void clearJointAnimationPriorities(); void clearJointAnimationPriorities();
Q_INVOKABLE void setEnableRigAnimations(bool isEnabled);
// get/set avatar data // get/set avatar data
void saveData(); void saveData();

View file

@ -49,7 +49,7 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
_jointMappings.clear(); _jointMappings.clear();
} }
void AnimationHandle::setRunning(bool running) { void AnimationHandle::setRunning(bool running, bool doRestoreJoints) {
if (running && isRunning()) { if (running && isRunning()) {
// if we're already running, this is the same as a restart // if we're already running, this is the same as a restart
setFrameIndex(getFirstFrame()); setFrameIndex(getFirstFrame());
@ -62,7 +62,9 @@ void AnimationHandle::setRunning(bool running) {
} }
} else { } else {
_rig->removeRunningAnimation(getAnimationHandlePointer()); _rig->removeRunningAnimation(getAnimationHandlePointer());
restoreJoints(); if (doRestoreJoints) {
restoreJoints();
}
replaceMatchingPriorities(0.0f); replaceMatchingPriorities(0.0f);
} }
emit runningChanged(isRunning()); emit runningChanged(isRunning());
@ -71,7 +73,9 @@ void AnimationHandle::setRunning(bool running) {
AnimationHandle::AnimationHandle(RigPointer rig) : AnimationHandle::AnimationHandle(RigPointer rig) :
QObject(rig.get()), QObject(rig.get()),
_rig(rig), _rig(rig),
_priority(1.0f) _priority(1.0f),
_fade(0.0f),
_fadePerSecond(0.0f)
{ {
} }

View file

@ -64,6 +64,10 @@ public:
void setPriority(float priority); void setPriority(float priority);
float getPriority() const { return _priority; } float getPriority() const { return _priority; }
void setMix(float mix) { _mix = mix; } void setMix(float mix) { _mix = mix; }
void setFade(float fade) { _fade = fade; }
float getFade() const { return _fade; }
void setFadePerSecond(float fadePerSecond) { _fadePerSecond = fadePerSecond; }
float getFadePerSecond() const { return _fadePerSecond; }
void setMaskedJoints(const QStringList& maskedJoints); void setMaskedJoints(const QStringList& maskedJoints);
const QStringList& getMaskedJoints() const { return _maskedJoints; } const QStringList& getMaskedJoints() const { return _maskedJoints; }
@ -87,7 +91,7 @@ public:
void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
float getLastFrame() const { return _animationLoop.getLastFrame(); } float getLastFrame() const { return _animationLoop.getLastFrame(); }
void setRunning(bool running); void setRunning(bool running, bool restoreJoints = true);
bool isRunning() const { return _animationLoop.isRunning(); } bool isRunning() const { return _animationLoop.isRunning(); }
void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); } void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); }
@ -111,7 +115,7 @@ signals:
public slots: public slots:
void start() { setRunning(true); } void start() { setRunning(true); }
void stop() { setRunning(false); } void stop() { setRunning(false); _fadePerSecond = _fade = 0.0f; }
private: private:
@ -120,7 +124,9 @@ private:
QString _role; QString _role;
QUrl _url; QUrl _url;
float _priority; float _priority;
float _mix; float _mix; // How much of this animation to blend against what is already there. 1.0 sets to just this animation.
float _fade; // How far are we into full strength. 0.0 uses none of this animation, 1.0 (the max) is as much as possible.
float _fadePerSecond; // How fast should _fade change? +1.0 means _fade is increasing to 1.0 in 1 second. Negative is fading out.
QStringList _maskedJoints; QStringList _maskedJoints;
QVector<int> _jointMappings; QVector<int> _jointMappings;

View file

@ -80,8 +80,8 @@ void Rig::startAnimation(const QString& url, float fps, float priority,
handle->start(); handle->start();
} }
void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority, AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) { bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) {
// check for a configured animation for the role // check for a configured animation for the role
//qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically; //qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically;
foreach (const AnimationHandlePointer& candidate, _animationHandles) { foreach (const AnimationHandlePointer& candidate, _animationHandles) {
@ -89,7 +89,7 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps,
if (startAutomatically) { if (startAutomatically) {
candidate->start(); candidate->start();
} }
return; return candidate;
} }
} }
AnimationHandlePointer handle = createAnimationHandle(); AnimationHandlePointer handle = createAnimationHandle();
@ -131,16 +131,18 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps,
if (startAutomatically) { if (startAutomatically) {
handle->start(); handle->start();
} }
return handle;
} }
void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true); AnimationHandlePointer handle = addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true);
handle->setFadePerSecond(1.0f); // For now. Could be individualized later.
} }
void Rig::stopAnimationByRole(const QString& role) { void Rig::stopAnimationByRole(const QString& role) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) { foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getRole() == role) { if (handle->getRole() == role) {
handle->stop(); handle->setFadePerSecond(-1.0f); // For now. Could be individualized later.
} }
} }
} }
@ -166,7 +168,7 @@ bool Rig::isRunningAnimation(AnimationHandlePointer animationHandle) {
} }
bool Rig::isRunningRole(const QString& role) { //obviously, there are more efficient ways to do this bool Rig::isRunningRole(const QString& role) { //obviously, there are more efficient ways to do this
for (auto animation : _runningAnimations) { for (auto animation : _runningAnimations) {
if (animation->getRole() == role) { if ((animation->getRole() == role) && (animation->getFadePerSecond() >= 0.0f)) { // Don't count those being faded out
return true; return true;
} }
} }
@ -444,13 +446,48 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
} }
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) { void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
int nAnimationsSoFar = 0;
// First normalize the fades so that they sum to 1.0.
// update the fade data in each animation (not normalized as they are an independent propert of animation)
foreach (const AnimationHandlePointer& handle, _runningAnimations) { foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setMix(1.0f / ++nAnimationsSoFar); float fadePerSecond = handle->getFadePerSecond();
handle->setPriority(1.0); float fade = handle->getFade();
if (fadePerSecond != 0.0f) {
fade += fadePerSecond * deltaTime;
if ((0.0f >= fade) || (fade >= 1.0f)) {
fade = glm::clamp(fade, 0.0f, 1.0f);
handle->setFadePerSecond(0.0f);
}
handle->setFade(fade);
if (fade <= 0.0f) { // stop any finished animations now
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
}
}
}
// sum the remaining fade data
float fadeTotal = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
fadeTotal += handle->getFade();
}
float fadeSumSoFar = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setPriority(1.0f);
float normalizedFade = handle->getFade() / fadeTotal;
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
// The formula here for mix is based on the idea that, at each step:
// fadeSum is to normalizedFade, as (1 - mix) is to mix
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
// Then we solve for mix.
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
assert((0.0f <= mix) && (mix <= 1.0f));
fadeSumSoFar += normalizedFade;
handle->setMix(mix);
handle->simulate(deltaTime); handle->simulate(deltaTime);
} }
for (int i = 0; i < _jointStates.size(); i++) { for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform); updateJointState(i, parentTransform);
} }

View file

@ -86,9 +86,9 @@ public:
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
void stopAnimationByRole(const QString& role); void stopAnimationByRole(const QString& role);
void addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, AnimationHandlePointer addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform, float initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
int rootJointIndex, int rootJointIndex,