diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2d7cf4ca5e..3a49b1cc82 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -623,6 +623,12 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) { return value; } +void MyAvatar::setEnableRigAnimations(bool isEnabled) { + Settings settings; + settings.setValue("enableRig", isEnabled); + _rig->setEnableRig(isEnabled); +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a0cb40cc45..13223b66b9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -72,6 +72,7 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role); Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url); void clearJointAnimationPriorities(); + Q_INVOKABLE void setEnableRigAnimations(bool isEnabled); // get/set avatar data void saveData(); diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index 605fb25f1c..ebf0e29b97 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -49,7 +49,7 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { _jointMappings.clear(); } -void AnimationHandle::setRunning(bool running) { +void AnimationHandle::setRunning(bool running, bool doRestoreJoints) { if (running && isRunning()) { // if we're already running, this is the same as a restart setFrameIndex(getFirstFrame()); @@ -62,7 +62,9 @@ void AnimationHandle::setRunning(bool running) { } } else { _rig->removeRunningAnimation(getAnimationHandlePointer()); - restoreJoints(); + if (doRestoreJoints) { + restoreJoints(); + } replaceMatchingPriorities(0.0f); } emit runningChanged(isRunning()); @@ -71,7 +73,9 @@ void AnimationHandle::setRunning(bool running) { AnimationHandle::AnimationHandle(RigPointer rig) : QObject(rig.get()), _rig(rig), - _priority(1.0f) + _priority(1.0f), + _fade(0.0f), + _fadePerSecond(0.0f) { } diff --git a/libraries/animation/src/AnimationHandle.h b/libraries/animation/src/AnimationHandle.h index 42e564944e..65f7423aa5 100644 --- a/libraries/animation/src/AnimationHandle.h +++ b/libraries/animation/src/AnimationHandle.h @@ -64,6 +64,10 @@ public: void setPriority(float priority); float getPriority() const { return _priority; } 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); const QStringList& getMaskedJoints() const { return _maskedJoints; } @@ -87,7 +91,7 @@ public: void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getLastFrame() const { return _animationLoop.getLastFrame(); } - void setRunning(bool running); + void setRunning(bool running, bool restoreJoints = true); bool isRunning() const { return _animationLoop.isRunning(); } void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); } @@ -111,7 +115,7 @@ signals: public slots: void start() { setRunning(true); } - void stop() { setRunning(false); } + void stop() { setRunning(false); _fadePerSecond = _fade = 0.0f; } private: @@ -120,7 +124,9 @@ private: QString _role; QUrl _url; 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; QVector _jointMappings; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index bc781feaa5..ec34e90b5c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -80,8 +80,8 @@ void Rig::startAnimation(const QString& url, float fps, float priority, handle->start(); } -void 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) { +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) { // check for a configured animation for the role //qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically; foreach (const AnimationHandlePointer& candidate, _animationHandles) { @@ -89,7 +89,7 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, if (startAutomatically) { candidate->start(); } - return; + return candidate; } } AnimationHandlePointer handle = createAnimationHandle(); @@ -131,16 +131,18 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, if (startAutomatically) { handle->start(); } + return handle; } void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, 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) { foreach (const AnimationHandlePointer& handle, getRunningAnimations()) { 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 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; } } @@ -444,13 +446,48 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } 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) { - handle->setMix(1.0f / ++nAnimationsSoFar); - handle->setPriority(1.0); + float fadePerSecond = handle->getFadePerSecond(); + 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); } - + for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i, parentTransform); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 1dbda23760..113d097f96 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -86,9 +86,9 @@ public: float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); void stopAnimationByRole(const QString& role); - void 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 lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); + 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 lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); float initJointStates(QVector states, glm::mat4 parentTransform, int rootJointIndex,